refactor(round_frame): replace RoundedFrame with theme-styled QFrame

This commit is contained in:
wyzula_j committed 2026-10-19 14:15:23 +00:00
1 parent 824979f7c4
commit 09b8360834
8 files changed
+37 -199

No files matched your search

+8 -12
View File
@@ -11,11 +11,13 @@ from qtpy.QtWidgets import (
QApplication,
QComboBox,
QFileDialog,
QFrame,
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QSpacerItem,
QVBoxLayout,
QWidget,
)
@@ -25,7 +27,6 @@ from bec_widgets.utils.container_utils import WidgetContainerUtils
from bec_widgets.utils.error_popups import SafeSlot
from bec_widgets.utils.name_utils import pascal_to_snake
from bec_widgets.utils.plugin_utils import get_plugin_auto_updates
from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.utils.rpc_register import RPCRegister
from bec_widgets.utils.screen_utils import apply_window_geometry, centered_geometry_for_app
from bec_widgets.utils.toolbars.toolbar import ModularToolBar
@@ -46,7 +47,7 @@ MODULE_PATH = os.path.dirname(bec_widgets.__file__)
START_EMPTY_PROFILE_OPTION = "Start Empty (No Profile)"
class LaunchTile(RoundedFrame):
class LaunchTile(QFrame):
DEFAULT_SIZE = (250, 300)
open_signal = Signal()
@@ -60,7 +61,11 @@ class LaunchTile(RoundedFrame):
show_selector: bool = False,
tile_size: tuple[int, int] | None = None,
):
super().__init__(parent=parent, orientation="vertical")
super().__init__(parent=parent)
self.setProperty("skip_settings", True)
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(5, 5, 5, 5)
# Provide a per‑instance TILE_SIZE so the class can compute layout
if tile_size is None:
@@ -574,15 +579,6 @@ class LaunchWindow(BECMainWindow):
window.show()
return window
def apply_theme(self, theme: str):
"""
Change the theme of the application.
"""
for tile in self.tiles.values():
tile.apply_theme(theme)
super().apply_theme(theme)
def _open_auto_update(self):
"""
Open the auto update window.
-124
View File
@@ -1,124 +0,0 @@
import pyqtgraph as pg
from qtpy.QtCore import Property, Qt
from qtpy.QtWidgets import QApplication, QFrame, QHBoxLayout, QVBoxLayout, QWidget
from bec_widgets.widgets.utility.visual.dark_mode_button.dark_mode_button import DarkModeButton
class RoundedFrame(QFrame):
# TODO this should be removed completely in favor of QSS styling, no time now
"""
A custom QFrame with rounded corners and optional theme updates.
The frame can contain any QWidget, however it is mainly designed to wrap PlotWidgets to provide a consistent look and feel with other BEC Widgets.
"""
def __init__(
self,
parent=None,
content_widget: QWidget = None,
background_color: str = None,
orientation: str = "horizontal",
radius: int = 10,
):
QFrame.__init__(self, parent)
self.background_color = background_color
self._radius = radius
# Apply rounded frame styling
self.setProperty("skip_settings", True)
self.setObjectName("roundedFrame")
# Ensure QSS can paint background/border on this widget
self.setAttribute(Qt.WA_StyledBackground, True)
# Create a layout for the frame
if orientation == "vertical":
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(5, 5, 5, 5)
else:
self.layout = QHBoxLayout(self)
self.layout.setContentsMargins(5, 5, 5, 5) # Set 5px margin
# Add the content widget to the layout
if content_widget:
self.layout.addWidget(content_widget)
# Store reference to the content widget
self.content_widget = content_widget
# Automatically apply initial styles to the GraphicalLayoutWidget if applicable
self.apply_plot_widget_style()
self.update_style()
def apply_theme(self, theme: str):
"""Deprecated: RoundedFrame no longer handles theme; styling is QSS-driven."""
self.update_style()
@Property(int)
def radius(self):
"""Radius of the rounded corners."""
return self._radius
@radius.setter
def radius(self, value: int):
self._radius = value
self.update_style()
def update_style(self):
"""
Update the style of the frame based on the background color.
"""
self.setStyleSheet(f"""
QFrame#roundedFrame {{
border-radius: {self._radius}px;
}}
""")
self.apply_plot_widget_style()
def apply_plot_widget_style(self, border: str = "none"):
"""
Let QSS/pyqtgraph handle plot styling; avoid overriding here.
"""
if isinstance(self.content_widget, pg.GraphicsLayoutWidget):
self.content_widget.setStyleSheet("")
class ExampleApp(QWidget): # pragma: no cover
def __init__(self):
super().__init__()
self.setWindowTitle("Rounded Plots Example")
# Main layout
layout = QVBoxLayout(self)
dark_button = DarkModeButton()
# Create PlotWidgets
plot1 = pg.GraphicsLayoutWidget()
plot_item_1 = pg.PlotItem()
plot_item_1.plot([1, 3, 2, 4, 6, 5], pen="r")
plot1.plot_item = plot_item_1
plot2 = pg.GraphicsLayoutWidget()
plot_item_2 = pg.PlotItem()
plot_item_2.plot([1, 2, 4, 8, 16, 32], pen="r")
plot2.plot_item = plot_item_2
# Add to layout (no RoundedFrame wrapper; QSS styles plots)
layout.addWidget(dark_button)
layout.addWidget(plot1)
layout.addWidget(plot2)
self.setLayout(layout)
# Theme flip demo removed; global theming applies automatically
if __name__ == "__main__": # pragma: no cover
app = QApplication([])
window = ExampleApp()
window.show()
app.exec()
@@ -1,16 +1,21 @@
import pyqtgraph as pg
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QFrame, QHBoxLayout
from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.widgets.plots.plot_base import BECViewBox
class ImageROIPlot(RoundedFrame):
class ImageROIPlot(QFrame):
"""
A widget for displaying an image with a region of interest (ROI) overlay.
"""
def __init__(self, parent=None):
super().__init__(parent=parent)
self.setProperty("skip_settings", True)
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
self.layout = QHBoxLayout(self)
self.layout.setContentsMargins(5, 5, 5, 5)
self.content_widget = pg.GraphicsLayoutWidget(self)
self.layout.addWidget(self.content_widget)
@@ -18,9 +23,13 @@ class ImageROIPlot(RoundedFrame):
self.content_widget.addItem(self.plot_item)
self.curve_color = "w"
self.apply_plot_widget_style()
def apply_theme(self, theme: str):
"""
Recolor the live profile curves for the given theme.
Args:
theme(str): "dark" or "light".
"""
if theme == "dark":
self.curve_color = "w"
else:
@@ -30,7 +39,6 @@ class ImageROIPlot(RoundedFrame):
# Frozen reference profiles keep their own (dashed) styling.
continue
curve.setPen(pg.mkPen(self.curve_color, width=3))
super().apply_theme(theme)
def cleanup_pyqtgraph(self):
"""Cleanup pyqtgraph items."""
+8 -4
View File
@@ -10,7 +10,7 @@ from bec_lib import bec_logger
from bec_lib.scan_data_container import ScanDataContainer
from bec_lib.scan_items import ScanItem
from qtpy.QtCore import QPoint, QPointF, Qt, Signal
from qtpy.QtWidgets import QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget
from qtpy.QtWidgets import QFrame, QHBoxLayout, QLabel, QMainWindow, QVBoxLayout, QWidget
from bec_widgets.utils.bec_connector import ConnectionConfig
from bec_widgets.utils.bec_widget import BECWidget
@@ -19,7 +19,6 @@ from bec_widgets.utils.entry_validator import EntryValidator
from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
from bec_widgets.utils.fps_counter import FPSCounter
from bec_widgets.utils.plot_indicator_items import BECArrowItem, BECTickItem
from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.utils.side_panel import SidePanel
from bec_widgets.utils.toolbars.actions import MaterialIconAction
from bec_widgets.utils.toolbars.performance import PerformanceConnection, performance_bundle
@@ -210,15 +209,20 @@ class PlotBase(BECWidget, QWidget):
self._update_theme(None)
def apply_theme(self, theme: str):
self.round_plot_widget.apply_theme(theme)
if hasattr(self, "info_label"):
self.info_label.set_theme(theme)
def _init_ui(self):
self.layout.addWidget(self.layout_manager)
self.round_plot_widget = RoundedFrame(parent=self, content_widget=self.plot_widget)
# Rounded plot background, painted by the theme QSS (QFrame[variant="plot_background"])
self.round_plot_widget = QFrame(parent=self)
self.round_plot_widget.setProperty("variant", "plot_background")
self.round_plot_widget.setProperty("frameless", True)
self.round_plot_widget.setProperty("skip_settings", True)
self.round_plot_widget.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
plot_frame_layout = QHBoxLayout(self.round_plot_widget)
plot_frame_layout.setContentsMargins(5, 5, 5, 5)
plot_frame_layout.addWidget(self.plot_widget)
self.layout_manager.add_widget(self.round_plot_widget)
self.layout_manager.add_widget_relative(self.fps_label, self.round_plot_widget, "top")
@@ -720,7 +720,6 @@ class Waveform(PlotBase):
self.async_signal_update.emit()
self.sync_signal_update.emit()
self.plot_item.enableAutoRange(x=True)
self.round_plot_widget.apply_plot_widget_style() # To keep the correct theme
self._refresh_alignment_state(force_readback=True)
@SafeProperty(str)
@@ -750,7 +749,6 @@ class Waveform(PlotBase):
self.async_signal_update.emit()
self.sync_signal_update.emit()
self.plot_item.enableAutoRange(x=True)
self.round_plot_widget.apply_plot_widget_style()
self._refresh_alignment_state(force_readback=True)
@SafeProperty(str)
@@ -17,7 +17,6 @@ from qtpy.QtWidgets import (
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import get_theme_palette
from bec_widgets.utils.round_frame import RoundedFrame
from bec_widgets.widgets.services.bec_atlas_admin_view.experiment_selection.utils import (
format_name,
format_schedule,
+8
View File
@@ -292,6 +292,14 @@ def test_main_label_point_size_uniform(bec_launch_window):
assert len(point_sizes) == 1, f"Non-uniform main-label point sizes: {point_sizes}"
@pytest.mark.parametrize("theme", ["light", "dark"])
def test_launch_window_theme_update(bec_launch_window, theme):
"""A theme change on the launcher must not log a slot error."""
with mock.patch("bec_widgets.utils.error_popups.logger") as error_logger:
bec_launch_window._update_theme(theme)
error_logger.error.assert_not_called()
def test_launch_window_cleanup_deregisters_registry_callback(bec_launch_window):
"""The turn-off-the-lights callback must not survive the launcher's cleanup:
a later registry broadcast would call into a dying window (shutdown scenario)."""
-51
View File
@@ -1,51 +0,0 @@
import pyqtgraph as pg
import pytest
from bec_widgets.utils.round_frame import RoundedFrame
def cleanup_pyqtgraph(plot_widget):
item = plot_widget.getPlotItem()
item.vb.menu.close()
item.vb.menu.deleteLater()
item.ctrlMenu.close()
item.ctrlMenu.deleteLater()
@pytest.fixture
def basic_rounded_frame(qtbot):
frame = RoundedFrame()
qtbot.addWidget(frame)
qtbot.waitExposed(frame)
yield frame
@pytest.fixture
def plot_rounded_frame(qtbot):
plot_widget = pg.PlotWidget()
plot_widget.plot([0, 1, 2], [2, 1, 0])
frame = RoundedFrame(content_widget=plot_widget)
qtbot.addWidget(frame)
qtbot.waitExposed(frame)
yield frame
cleanup_pyqtgraph(plot_widget)
def test_basic_rounded_frame_initialization(basic_rounded_frame):
assert basic_rounded_frame.radius == 10
assert basic_rounded_frame.content_widget is None
assert basic_rounded_frame.background_color is None
def test_set_radius(basic_rounded_frame):
basic_rounded_frame.radius = 20
assert basic_rounded_frame.radius == 20
def test_apply_plot_widget_style(plot_rounded_frame):
# Verify that a PlotWidget can have its style applied
plot_rounded_frame.apply_plot_widget_style(border="1px solid red")
# Ensure style application did not break anything
assert plot_rounded_frame.content_widget is not None
assert isinstance(plot_rounded_frame.content_widget, pg.PlotWidget)