mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-10-22 00:26:18 +02:00
refactor(color_button): merge ColorButton into ColorButtonNative
This commit is contained in:
1 parent
68d5093002
commit
824979f7c4
11 files changed
+70
-168
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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
|
||||
@@ -104,15 +104,15 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="ColorButton" name="color_scatter"/>
|
||||
<widget class="ColorButtonNative" name="color_scatter"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ColorButton</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>color_button</header>
|
||||
<class>ColorButtonNative</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>color_button_native</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
|
||||
Whitespace-only changes.
@@ -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()
|
||||
@@ -1 +0,0 @@
|
||||
{'files': ['color_button.py']}
|
||||
@@ -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 = """
|
||||
<ui language='c++'>
|
||||
<widget class='ColorButton' name='color_button'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user