diff --git a/bec_widgets/widgets/control/device_control/positioner_box/_base/__init__.py b/bec_widgets/widgets/control/device_control/positioner_box/_base/__init__.py deleted file mode 100644 index 007b19a2..00000000 --- a/bec_widgets/widgets/control/device_control/positioner_box/_base/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .positioner_box_base import PositionerBoxBase - -__ALL__ = ["PositionerBoxBase"] diff --git a/bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py index f7cfaf4c..8e8239dd 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py @@ -14,9 +14,9 @@ from qtpy.QtWidgets import QDoubleSpinBox from bec_widgets.utils import UILoader from bec_widgets.utils.colors import apply_theme, get_accent_colors from bec_widgets.utils.error_popups import SafeProperty, SafeSlot -from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase -from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import ( +from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import ( DeviceUpdateUIComponents, + PositionerBoxBase, ) logger = bec_logger.logger @@ -63,10 +63,10 @@ class PositionerBox(PositionerBoxBase): self.ui = UILoader(self).loader(os.path.join(self.current_path, self.ui_file)) - self.addWidget(self.ui) - self.layout.setSpacing(0) - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter) + self.main_layout.addWidget(self.ui) + self.main_layout.setSpacing(0) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignHCenter) ui_min_size = self.ui.minimumSize() ui_min_hint = self.ui.minimumSizeHint() self.setMinimumSize( @@ -115,8 +115,6 @@ class PositionerBox(PositionerBoxBase): return old_device = self._device self._device = value - if not self.label: - self.label = value self.device_changed.emit(old_device, value) @SafeProperty(bool) diff --git a/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py index d57c22c6..b7396b0c 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py @@ -15,9 +15,9 @@ from qtpy.QtWidgets import QDoubleSpinBox from bec_widgets.utils import UILoader from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.error_popups import SafeProperty, SafeSlot -from bec_widgets.widgets.control.device_control.positioner_box._base import PositionerBoxBase -from bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base import ( +from bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base import ( DeviceUpdateUIComponents, + PositionerBoxBase, ) logger = bec_logger.logger @@ -96,9 +96,9 @@ class PositionerBox2D(PositionerBoxBase): def connect_ui(self): """Connect the UI components to signals, data, or routines""" - self.addWidget(self.ui) - self.layout.setSpacing(0) - self.layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.addWidget(self.ui) + self.main_layout.setSpacing(0) + self.main_layout.setContentsMargins(0, 0, 0, 0) def _init_ui(val: QDoubleValidator, device_id: DeviceId): ui = self._device_ui_components_hv(device_id) @@ -200,7 +200,6 @@ class PositionerBox2D(PositionerBoxBase): return old_device = self._device_hor self._device_hor = value - self.label = f"{self._device_hor}, {self._device_ver}" self.device_changed_hor.emit(old_device, value) self._init_device(self.device_hor, self.position_update_hor.emit, self.update_limits_hor) @@ -220,7 +219,6 @@ class PositionerBox2D(PositionerBoxBase): return old_device = self._device_ver self._device_ver = value - self.label = f"{self._device_hor}, {self._device_ver}" self.device_changed_ver.emit(old_device, value) self._init_device(self.device_ver, self.position_update_ver.emit, self.update_limits_ver) diff --git a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_base.py similarity index 94% rename from bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py rename to bec_widgets/widgets/control/device_control/positioner_box/positioner_box_base.py index 317d63dc..03a06f4e 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/positioner_box_base.py @@ -14,10 +14,10 @@ from qtpy.QtWidgets import ( QLineEdit, QPushButton, QVBoxLayout, + QWidget, ) from bec_widgets.utils.bec_widget import BECWidget -from bec_widgets.utils.compact_popup import CompactPopupWidget from bec_widgets.widgets.control.device_control.position_indicator.position_indicator import ( PositionIndicator, ) @@ -43,7 +43,7 @@ class DeviceUpdateUIComponents(TypedDict): units: QLabel -class PositionerBoxBase(BECWidget, CompactPopupWidget): +class PositionerBoxBase(BECWidget, QWidget): """Contains some core logic for positioner box widgets""" current_path = "" @@ -57,7 +57,10 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): parent: The parent widget. device (Positioner): The device to control. """ - super().__init__(parent=parent, layout=QVBoxLayout, **kwargs) + super().__init__(parent=parent, **kwargs) + self.main_layout = QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) self._dialog = None self.get_bec_shortcuts() @@ -173,11 +176,9 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): if is_moving: spinner.start() spinner.setToolTip("Device is moving") - self.set_global_state("warning") else: spinner.stop() spinner.setToolTip("Device is idle") - self.set_global_state("success") else: spinner.setVisible(False) @@ -196,9 +197,8 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): pos = (readback_val - limits[0]) / (limits[1] - limits[0]) position_indicator.set_value(pos) - def _update_limits_ui( - self, limits: tuple[float, float], position_indicator, setpoint_validator - ): + @staticmethod + def _update_limits_ui(limits: tuple[float, float], position_indicator, setpoint_validator): if limits is not None and limits[0] != limits[1]: position_indicator.setToolTip(f"Min: {limits[0]}, Max: {limits[1]}") setpoint_validator.setRange(limits[0], limits[1]) @@ -223,7 +223,8 @@ class PositionerBoxBase(BECWidget, CompactPopupWidget): self.bec_dispatcher.disconnect_slot(slot, MessageEndpoints.device_readback(old_device)) self.bec_dispatcher.connect_slot(slot, MessageEndpoints.device_readback(new_device)) - def _toggle_enable_buttons(self, ui: DeviceUpdateUIComponents, enable: bool) -> None: + @staticmethod + def _toggle_enable_buttons(ui: DeviceUpdateUIComponents, enable: bool) -> None: """Toggle enable/disable on available buttons Args: diff --git a/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.py b/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.py index 6f879838..a85da994 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.py +++ b/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.py @@ -1,6 +1,8 @@ import os from bec_lib.device import Positioner +from qtpy.QtCore import Qt +from qtpy.QtWidgets import QSizePolicy from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox @@ -22,7 +24,82 @@ class PositionerControlLine(PositionerBox): device (Positioner): The device to control. """ self.current_path = os.path.dirname(__file__) + self._indicator_switch_width = 0 + self._horizontal_indicator_width = 0 + self._vertical_indicator_width = 15 + self._indicator_thickness = 10 + self._indicator_is_horizontal = False + self._line_height = self.dimensions[0] super().__init__(parent=parent, device=device, *args, **kwargs) + self._configure_line_layout() + self._update_indicator_orientation() + + def _configure_line_layout(self): + device_box = self.ui.device_box + indicator = self.ui.position_indicator + + self.main_layout.setAlignment(Qt.AlignmentFlag(0)) + self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + self.ui.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + device_box.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + + self._line_height = max( + self.dimensions[0], + self.ui.minimumSizeHint().height(), + self.ui.sizeHint().height(), + device_box.minimumSizeHint().height(), + device_box.sizeHint().height(), + ) + device_box.setFixedHeight(self._line_height) + device_box.setMinimumWidth(self.dimensions[1]) + device_box.setMaximumWidth(16777215) + self.setFixedHeight(self._line_height) + self.setMinimumWidth(self.dimensions[1]) + + self.ui.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.ui.verticalLayout.setSpacing(0) + self.ui.readback.setMaximumWidth(16777215) + self.ui.setpoint.setMaximumWidth(16777215) + self.ui.step_size.setMaximumWidth(16777215) + + indicator_hint = indicator.minimumSizeHint() + step_hint = self.ui.step_size.sizeHint() + self._indicator_thickness = max(indicator_hint.height(), 10) + self._vertical_indicator_width = max(indicator.minimumWidth(), 15) + self._horizontal_indicator_width = max(90, step_hint.width()) + base_width = max(device_box.minimumSizeHint().width(), self.dimensions[1]) + self._indicator_switch_width = ( + base_width - self._vertical_indicator_width + self._horizontal_indicator_width + ) + + def resizeEvent(self, event): + super().resizeEvent(event) + self._update_indicator_orientation() + + def _update_indicator_orientation(self): + if not hasattr(self, "ui"): + return + + indicator = self.ui.position_indicator + available_width = self.ui.device_box.width() or self.width() or self.dimensions[1] + should_use_horizontal = available_width >= self._indicator_switch_width + if should_use_horizontal == self._indicator_is_horizontal: + return + + self._indicator_is_horizontal = should_use_horizontal + indicator.vertical = not should_use_horizontal + + if should_use_horizontal: + indicator.setMinimumSize(self._horizontal_indicator_width, self._indicator_thickness) + indicator.setMaximumHeight(self._indicator_thickness) + indicator.setMaximumWidth(16777215) + indicator.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Fixed) + else: + indicator.setMinimumSize(self._vertical_indicator_width, self._indicator_thickness) + indicator.setMaximumSize(self._vertical_indicator_width, 16777215) + indicator.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred) + + indicator.updateGeometry() if __name__ == "__main__": # pragma: no cover diff --git a/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui b/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui index bf2ffb62..217da1bc 100644 --- a/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +++ b/bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui @@ -2,12 +2,18 @@ Form + + + 0 + 0 + + 0 0 - 612 - 91 + 592 + 76 @@ -26,8 +32,29 @@ Form + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + 0 + 0 + + Device Name @@ -227,12 +254,12 @@ PositionIndicator - QWidget +
position_indicator
SpinnerWidget - QWidget +
spinner_widget
diff --git a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py index e16c8371..3c3eeb9a 100644 --- a/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +++ b/bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py @@ -27,30 +27,13 @@ class PositionerGroupBox(QGroupBox): self.layout().setContentsMargins(0, 0, 0, 0) self.layout().setSpacing(0) self.widget = PositionerBox(self, dev_name) - self.widget.compact_view = True - self.widget.expand_popup = False self.layout().addWidget(self.widget) self.widget.position_update.connect(self._on_position_update) - self.widget.expand.connect(self._on_expand) self.setTitle(self.device_name) self.widget.force_update_readback() - def _on_expand(self, expand): - if expand: - self.setTitle("") - self.setFlat(True) - else: - self.setTitle(self.device_name) - self.setFlat(False) - def _on_position_update(self, pos: float): self.position_update.emit(pos) - precision = getattr(self.widget.dev[self.widget.device], "precision", 8) - try: - precision = int(precision) - except (TypeError, ValueError): - precision = int(8) - self.widget.label = f"{pos:.{precision}f}" def close(self): self.widget.close() diff --git a/tests/unit_tests/test_positioner_box.py b/tests/unit_tests/test_positioner_box.py index 34607bde..f74b1bb1 100644 --- a/tests/unit_tests/test_positioner_box.py +++ b/tests/unit_tests/test_positioner_box.py @@ -36,11 +36,11 @@ class PositionerWithoutPrecision(Positioner): def positioner_box(qtbot, mocked_client): """Fixture for PositionerBox widget""" with mock.patch( - "bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4" + "bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4" ) as mock_uuid: mock_uuid.return_value = "fake_uuid" with mock.patch( - "bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.PositionerBoxBase._check_device_is_valid", + "bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.PositionerBoxBase._check_device_is_valid", return_value=True, ): db = create_widget(qtbot, PositionerBox, device="samx", client=mocked_client) @@ -141,7 +141,7 @@ def test_positioner_control_line(qtbot, mocked_client): Inherits from PositionerBox, but the layout is changed. Check dimensions only """ with mock.patch( - "bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4" + "bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4" ) as mock_uuid: mock_uuid.return_value = "fake_uuid" with mock.patch( @@ -151,7 +151,8 @@ def test_positioner_control_line(qtbot, mocked_client): db = PositionerControlLine(device="samx", client=mocked_client) qtbot.addWidget(db) - assert db.ui.device_box.height() == 60 + assert db.ui.device_box.height() == db.height() + assert db.ui.device_box.height() >= db.dimensions[0] assert db.ui.device_box.width() == 600 diff --git a/tests/unit_tests/test_positioner_box_2d.py b/tests/unit_tests/test_positioner_box_2d.py index 40535ab0..39aff535 100644 --- a/tests/unit_tests/test_positioner_box_2d.py +++ b/tests/unit_tests/test_positioner_box_2d.py @@ -12,11 +12,11 @@ from .conftest import create_widget def positioner_box_2d(qtbot, mocked_client): """Fixture for PositionerBox widget""" with mock.patch( - "bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.uuid.uuid4" + "bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.uuid.uuid4" ) as mock_uuid: mock_uuid.return_value = "fake_uuid" with mock.patch( - "bec_widgets.widgets.control.device_control.positioner_box._base.positioner_box_base.PositionerBoxBase._check_device_is_valid", + "bec_widgets.widgets.control.device_control.positioner_box.positioner_box_base.PositionerBoxBase._check_device_is_valid", return_value=True, ): db = create_widget(