diff --git a/bec_widgets/cli/designer_plugins.py b/bec_widgets/cli/designer_plugins.py
index 9c51d995..18427e9d 100644
--- a/bec_widgets/cli/designer_plugins.py
+++ b/bec_widgets/cli/designer_plugins.py
@@ -24,7 +24,6 @@ designer_plugins = {
"BeamlineStateManager",
),
"BecConsole": ("bec_widgets.widgets.editors.bec_console.bec_console", "BecConsole"),
- "ColorButton": ("bec_widgets.widgets.utility.visual.color_button.color_button", "ColorButton"),
"ColorButtonNative": (
"bec_widgets.widgets.utility.visual.color_button_native.color_button_native",
"ColorButtonNative",
@@ -132,7 +131,6 @@ widget_icons = {
"BECStatusBox": "widgets",
"BeamlineStateManager": "format_list_bulleted",
"BecConsole": "terminal",
- "ColorButton": "colors",
"ColorButtonNative": "colors",
"ColormapSelector": "palette",
"DapComboBox": "data_exploration",
diff --git a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py
index e67aecaf..2f2a166f 100644
--- a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py
+++ b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py
@@ -1,5 +1,6 @@
import os
+from qtpy.QtGui import QColor
from qtpy.QtWidgets import QFrame, QScrollArea, QVBoxLayout, QWidget
from bec_widgets.utils.error_popups import SafeSlot
@@ -55,7 +56,7 @@ class MotorMapSettings(SettingWidget):
def connect_all_signals(self):
for widget in self.ui_widget_list:
WidgetIO.connect_widget_change_signal(widget, self.set_property)
- self.ui.color_scatter.color_selected.connect(
+ self.ui.color_scatter.color_changed.connect(
lambda color: self.target_widget.setProperty("color_scatter", color)
)
@@ -98,8 +99,7 @@ class MotorMapSettings(SettingWidget):
if widget_to_set is None:
return
if widget_to_set is self.ui.color_scatter:
- # Update the color scatter button
- self.ui.color_scatter.set_color(value)
+ self._show_color(value)
return
# Block signals to avoid triggering set_property again
was_blocked = widget_to_set.blockSignals(True)
@@ -115,7 +115,18 @@ class MotorMapSettings(SettingWidget):
value = getattr(self.target_widget, property_name)
WidgetIO.set_value(widget, value)
- self.ui.color_scatter.set_color(self.target_widget.color)
+ self._show_color(self.target_widget.color)
+
+ def _show_color(self, color: tuple | str):
+ """
+ Show a color on the color button without sending it back to the target widget.
+
+ Args:
+ color(tuple|str): The color to show, as an RGBA tuple or a HEX string.
+ """
+ was_blocked = self.ui.color_scatter.blockSignals(True)
+ self.ui.color_scatter.set_color(color)
+ self.ui.color_scatter.blockSignals(was_blocked)
def accept_changes(self):
"""
@@ -126,4 +137,6 @@ class MotorMapSettings(SettingWidget):
value = WidgetIO.get_value(widget)
setattr(self.target_widget, property_name, value)
- self.target_widget.color_scatter = self.ui.color_scatter.get_color()
+ # The button only holds RGB, so keep the target's alpha when the color is unchanged
+ if QColor(self.ui.color_scatter.color).rgb() != self.target_widget.color_scatter.rgb():
+ self.target_widget.color_scatter = self.ui.color_scatter.color
diff --git a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui
index c320a5f0..13602aca 100644
--- a/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui
+++ b/bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui
@@ -104,15 +104,15 @@
-
-
+
- ColorButton
- QWidget
-
+ ColorButtonNative
+ QPushButton
+
diff --git a/bec_widgets/widgets/utility/visual/color_button/__init__.py b/bec_widgets/widgets/utility/visual/color_button/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/bec_widgets/widgets/utility/visual/color_button/color_button.py b/bec_widgets/widgets/utility/visual/color_button/color_button.py
deleted file mode 100644
index 8f0e8b58..00000000
--- a/bec_widgets/widgets/utility/visual/color_button/color_button.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from __future__ import annotations
-
-from typing import Literal
-
-import pyqtgraph as pg
-from qtpy.QtCore import Qt, Signal
-from qtpy.QtWidgets import QHBoxLayout, QWidget
-
-from bec_widgets.utils.error_popups import SafeSlot
-
-
-class ColorButton(QWidget):
- """
- A ColorButton that opens a dialog to select a color. Inherits from pyqtgraph.ColorButton.
- Patches event loop of the ColorDialog, if opened in another QDialog.
- """
-
- color_selected = Signal(str)
-
- PLUGIN = True
- ICON_NAME = "colors"
-
- def __init__(self, parent=None, *args, **kwargs):
- super().__init__(parent=parent, *args, **kwargs)
-
- self.layout = QHBoxLayout(self)
- self.layout.setSpacing(0)
- self.layout.setContentsMargins(0, 0, 0, 0)
- self.layout.setAlignment(Qt.AlignmentFlag.AlignVCenter)
-
- self.button = pg.ColorButton()
- self.button.setFlat(True)
- self.button.clicked.connect(self.select_color)
- self.layout.addWidget(self.button)
-
- @SafeSlot()
- def select_color(self):
- self.origColor = self.button.color()
- self.button.colorDialog.setCurrentColor(self.button.color())
- self.button.colorDialog.open()
- self.button.colorDialog.exec()
- self.color_selected.emit(self.button.color().name())
-
- @SafeSlot(str)
- def set_color(self, color: tuple | str):
- """
- Set the color of the button.
-
- Args:
- color(tuple|str): The color to set.
- """
- self.button.setColor(color)
-
- def get_color(self, format: Literal["RGBA", "HEX"] = "RGBA") -> tuple | str:
- """
- Get the color of the button in the specified format.
-
- Args:
- format(Literal["RGBA", "HEX"]): The format of the returned color.
-
- Returns:
- tuple|str: The color in the specified format.
- """
- if format == "RGBA":
- return self.button.color().getRgb()
- if format == "HEX":
- return self.button.color().name()
-
- def cleanup(self):
- """
- Clean up the ColorButton.
- """
- self.button.colorDialog.close()
- self.button.colorDialog.deleteLater()
diff --git a/bec_widgets/widgets/utility/visual/color_button/color_button.pyproject b/bec_widgets/widgets/utility/visual/color_button/color_button.pyproject
deleted file mode 100644
index 1136989d..00000000
--- a/bec_widgets/widgets/utility/visual/color_button/color_button.pyproject
+++ /dev/null
@@ -1 +0,0 @@
-{'files': ['color_button.py']}
\ No newline at end of file
diff --git a/bec_widgets/widgets/utility/visual/color_button/color_button_plugin.py b/bec_widgets/widgets/utility/visual/color_button/color_button_plugin.py
deleted file mode 100644
index 80398c9c..00000000
--- a/bec_widgets/widgets/utility/visual/color_button/color_button_plugin.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-from qtpy.QtDesigner import QDesignerCustomWidgetInterface
-from qtpy.QtWidgets import QWidget
-
-from bec_widgets.utils.bec_designer import designer_material_icon
-from bec_widgets.widgets.utility.visual.color_button.color_button import ColorButton
-
-DOM_XML = """
-
-
-
-
-"""
-
-
-class ColorButtonPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
- def __init__(self):
- super().__init__()
- self._form_editor = None
-
- def createWidget(self, parent):
- if parent is None:
- return QWidget()
- t = ColorButton(parent)
- return t
-
- def domXml(self):
- return DOM_XML
-
- def group(self):
- return "BEC Visual Utils"
-
- def icon(self):
- return designer_material_icon(ColorButton.ICON_NAME)
-
- def includeFile(self):
- return "color_button"
-
- def initialize(self, form_editor):
- self._form_editor = form_editor
-
- def isContainer(self):
- return False
-
- def isInitialized(self):
- return self._form_editor is not None
-
- def name(self):
- return "ColorButton"
-
- def toolTip(self):
- return ""
-
- def whatsThis(self):
- return self.toolTip()
diff --git a/bec_widgets/widgets/utility/visual/color_button/register_color_button.py b/bec_widgets/widgets/utility/visual/color_button/register_color_button.py
deleted file mode 100644
index 33df0093..00000000
--- a/bec_widgets/widgets/utility/visual/color_button/register_color_button.py
+++ /dev/null
@@ -1,17 +0,0 @@
-def main(): # pragma: no cover
- from qtpy import PYSIDE6
-
- if not PYSIDE6:
- print("PYSIDE6 is not available in the environment. Cannot patch designer.")
- return
- from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
-
- from bec_widgets.widgets.utility.visual.color_button.color_button_plugin import (
- ColorButtonPlugin,
- )
-
- QPyDesignerCustomWidgetCollection.addCustomWidget(ColorButtonPlugin())
-
-
-if __name__ == "__main__": # pragma: no cover
- main()
diff --git a/bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py b/bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py
index 7769a44f..07449de0 100644
--- a/bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py
+++ b/bec_widgets/widgets/utility/visual/color_button_native/color_button_native.py
@@ -33,14 +33,17 @@ class ColorButtonNative(QPushButton):
self.clicked.connect(self._open_color_dialog)
@SafeSlot()
- def set_color(self, color: str | QColor):
+ def set_color(self, color: str | tuple | QColor):
"""Set the button's color and update its appearance.
Args:
- color (str or QColor): The new color to assign.
+ color (str, tuple or QColor): The new color to assign, as a hex string,
+ an RGB(A) tuple or a QColor. The alpha channel is not shown.
"""
if isinstance(color, QColor):
self._color = color.name()
+ elif isinstance(color, (tuple, list)):
+ self._color = QColor(*color).name()
else:
self._color = color
self._update_appearance()
diff --git a/tests/unit_tests/test_color_button_native.py b/tests/unit_tests/test_color_button_native.py
index a3654668..5ff5d1ac 100644
--- a/tests/unit_tests/test_color_button_native.py
+++ b/tests/unit_tests/test_color_button_native.py
@@ -24,6 +24,18 @@ def test_color_button_native(qtbot):
assert cb.color == new_color.name()
+def test_color_button_native_accepts_rgba_tuple(qtbot):
+ """RGB(A) tuples are shown as their hex code; the alpha channel is dropped."""
+ cb = create_widget(qtbot, ColorButtonNative)
+
+ with qtbot.waitSignal(cb.color_changed, timeout=1000) as blocker:
+ cb.set_color((255, 128, 0, 100))
+
+ assert cb.color == "#ff8000"
+ assert cb.text() == "#ff8000"
+ assert blocker.args == ["#ff8000"]
+
+
def test_color_dialog_applies_chosen_color(qtbot, monkeypatch):
"""Clicking the button should open the dialog and apply the selected color."""
cb = create_widget(qtbot, ColorButtonNative)
diff --git a/tests/unit_tests/test_motor_map_next_gen.py b/tests/unit_tests/test_motor_map_next_gen.py
index 152de7e1..b6ac67b7 100644
--- a/tests/unit_tests/test_motor_map_next_gen.py
+++ b/tests/unit_tests/test_motor_map_next_gen.py
@@ -5,6 +5,7 @@ from qtpy.QtTest import QSignalSpy
from bec_widgets.tests.client_mocks import mocked_client
from bec_widgets.widgets.plots.motor_map.motor_map import MotorMap
+from bec_widgets.widgets.plots.motor_map.settings.motor_map_settings import MotorMapSettings
from .conftest import create_widget
@@ -373,18 +374,42 @@ def test_motor_map_settings_dialog(qtbot, mocked_client):
assert mm.motor_map_settings.widget.ui.scatter_size.value() == 10
assert mm.motor_map_settings.widget.ui.background_value.value() == 50
assert mm.motor_map_settings.widget.ui.num_dim_points.value() == 20
- assert mm.motor_map_settings.widget.ui.color_scatter.get_color(format="RGBA") == (
- 255,
- 0,
- 0,
- 255,
- )
+ assert mm.motor_map_settings.widget.ui.color_scatter.color == "#ff0000"
mm.motor_map_settings.close()
qtbot.wait(200)
assert mm.motor_map_settings is None
+def test_motor_map_settings_color_live_sync(qtbot, mocked_client):
+ """The side panel color button and the motor map color follow each other."""
+ mm = create_widget(qtbot, MotorMap, client=mocked_client)
+ settings = create_widget(qtbot, MotorMapSettings, target_widget=mm, popup=False)
+
+ mm.color = (0, 0, 255, 255)
+ assert settings.ui.color_scatter.color == "#0000ff"
+
+ settings.ui.color_scatter.set_color("#00ff00")
+ assert mm.color == (0, 255, 0, 255)
+ assert settings.ui.color_scatter.color == "#00ff00"
+
+
+def test_motor_map_settings_color_accept(qtbot, mocked_client):
+ """Applying the dialog sets a new color and keeps the alpha of an unchanged one."""
+ mm = create_widget(qtbot, MotorMap, client=mocked_client)
+ mm.color = (255, 0, 0, 100)
+ settings = create_widget(qtbot, MotorMapSettings, target_widget=mm, popup=True)
+ assert settings.ui.color_scatter.color == "#ff0000"
+
+ settings.accept_changes()
+ assert mm.color == (255, 0, 0, 100)
+
+ settings.ui.color_scatter.set_color("#00ff00")
+ assert mm.color == (255, 0, 0, 100)
+ settings.accept_changes()
+ assert mm.color == (0, 255, 0, 255)
+
+
def test_motor_map_cleanup_detaches_update_proxy(qtbot, mocked_client):
"""
A plot update still queued in proxy_update_plot when the MotorMap is cleaned up, or