diff --git a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py index 140efad..63af101 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/digital_twin.py @@ -22,10 +22,13 @@ from qtpy.QtWidgets import ( QApplication, QDialog, QDialogButtonBox, + QFrame, QHBoxLayout, QLabel, QPlainTextEdit, QPushButton, + QScrollArea, + QSizePolicy, QStyle, QVBoxLayout, QWidget, @@ -74,32 +77,47 @@ class DigitalTwin(BECWidget, QWidget): self.check_config() self.bec_dispatcher.connect_slot(self.check_config, MessageEndpoints.device_config_update()) - central = QWidget() - self.root_layout = QHBoxLayout(central) + self.content_widget = QWidget(self) + self.root_layout = QHBoxLayout(self.content_widget) + self.root_layout.setContentsMargins(6, 6, 6, 6) + self.root_layout.setSpacing(6) self.input_widget = QWidget() self.input_layout = QVBoxLayout(self.input_widget) + self.input_layout.setContentsMargins(4, 4, 4, 4) + self.input_layout.setSpacing(6) self.input = InputPanel() self.settings = SettingsPanel() self.input_layout.addWidget(self.input) self.input_layout.addWidget(self.settings) + self.input_layout.addStretch() self.plot_widget = QWidget() self.plot_layout = QVBoxLayout(self.plot_widget) + self.plot_layout.setContentsMargins(4, 4, 4, 4) + self.plot_layout.setSpacing(6) self.sideview_plot = SideviewPlot() self.surface_plots = SurfacePlots() - self.plot_layout.addWidget(self.sideview_plot) - self.plot_layout.addWidget(self.surface_plots) + self.plot_layout.addWidget(self.sideview_plot, stretch=1) + self.plot_layout.addWidget(self.surface_plots, stretch=1) + self.plot_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) self.mover = MoverPanel(self.dev) - self.root_layout.addWidget(self.input_widget, alignment=Qt.AlignmentFlag.AlignTop) - self.root_layout.addWidget(self.plot_widget, alignment=Qt.AlignmentFlag.AlignTop) - self.root_layout.addWidget(self.mover, alignment=Qt.AlignmentFlag.AlignTop) + self.input_scroll = self._scroll_area(self.input_widget, min_width=320, max_width=360) + self.mover_scroll = self._scroll_area(self.mover, min_width=380, max_width=460) - self.setLayout(self.root_layout) + self.root_layout.addWidget(self.input_scroll) + self.root_layout.addWidget(self.plot_widget, stretch=1) + self.root_layout.addWidget(self.mover_scroll) + widget_layout = self.layout() + if widget_layout is None: + widget_layout = QVBoxLayout(self) + widget_layout.setContentsMargins(0, 0, 0, 0) + widget_layout.setSpacing(0) + widget_layout.addWidget(self.content_widget) self.setWindowTitle("Digital Twin") - self.resize(1800, 800) + self.resize(1450, 760) self.input.energy.value_changed_connect(self.calc_assistant) self.input.sldi_hacc.value_changed_connect(self.calc_assistant) @@ -127,12 +145,26 @@ class DigitalTwin(BECWidget, QWidget): self.load_offsets(recalculate=False) self.calc_assistant(identifier="init") - # Timer: update plots every 1 second + # Timer: update reality plots every 1 second self._timer = QTimer(self) - self._timer.setInterval(100) + self._timer.setInterval(1000) self._timer.timeout.connect(self.calc_reality) self._timer.start() + @staticmethod + def _scroll_area(widget: QWidget, min_width: int, max_width: int) -> QScrollArea: + """Wrap a side panel in a compact vertical scroll area.""" + scroll = QScrollArea() + scroll.setWidgetResizable(True) + widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.MinimumExpanding) + scroll.setWidget(widget) + scroll.setFrameShape(QFrame.Shape.NoFrame) + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + scroll.setMinimumWidth(min_width) + scroll.setMaximumWidth(max_width) + scroll.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding) + return scroll + def apply_theme(self, theme: Literal["dark", "light"]): """ Apply the theme @@ -152,8 +184,8 @@ class DigitalTwin(BECWidget, QWidget): BEC dispatcher whenever there is a config update, stop the timer that updates the plot in the background. """ - reload = (args[0] if args else {}).get("action") == "reload" - if reload: + reload_config = (args[0] if args else {}).get("action") == "reload" + if reload_config: self._timer.stop() devices = [ "abs", @@ -234,7 +266,7 @@ class DigitalTwin(BECWidget, QWidget): running_app = QApplication.instance() if running_app is not None: running_app.exit(0) - if reload: + if reload_config: self._timer.start() @SafeSlot() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py index 093dae9..be65480 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/input_panel.py @@ -3,7 +3,7 @@ Panel for user inputs of the digital twin widget """ # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ( Button, @@ -20,7 +20,8 @@ class InputPanel(QWidget): def __init__(self, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) # Adapt to reality self.adapt_reality = Button(label_button="Adapt to reality", enabled=True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py index 3f26f06..fcf3703 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/mover_panel.py @@ -5,7 +5,7 @@ Panel to move an axis to a certain position from typing import Literal # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.move_widget import ( AbsorberWidget, @@ -20,7 +20,8 @@ class MoverPanel(QWidget): def __init__(self, dev, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) self.mover_widgets = [] @@ -189,7 +190,7 @@ class MoverPanel(QWidget): ) self.mover_widgets.append(self.es0wi_try) - self.es0_mov_group = Group("Expperimental Station 0", [self.es0wi_try]) + self.es0_mov_group = Group("Experimental Station 0", [self.es0wi_try]) # Experimental Station 1 self.ot_es1_trz = MoveWidget( @@ -197,7 +198,7 @@ class MoverPanel(QWidget): ) self.mover_widgets.append(self.ot_es1_trz) - self.es1_mov_group = Group("Expperimental Station 1", [self.ot_es1_trz]) + self.es1_mov_group = Group("Experimental Station 1", [self.ot_es1_trz]) # Assemble complete mover group self.mover_group = Group( diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py index 2eb0cf9..8cbafce 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/plots.py @@ -33,6 +33,8 @@ class SurfacePlots(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self._layout = QHBoxLayout(self) + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(6) self.surfaces: dict[str, SurfaceDict] = { "assistant": { @@ -202,7 +204,8 @@ class SideviewPlot(QWidget): def __init__(self, parent=None): super().__init__(parent=parent) self._layout = QVBoxLayout(self) - # self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(0) self.plot_widget = pg.PlotWidget() self.plot_widget.getAxis("bottom").enableAutoSIPrefix(False) @@ -243,7 +246,6 @@ class SideviewPlot(QWidget): self.plot_widget.hideButtons() self._layout.addWidget(self.plot_group) - self._layout.addStretch() self.plot_vacuum_pipes() self.plot_walls() diff --git a/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py b/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py index 8947ea3..88ce3e1 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/panels/settings_panel.py @@ -3,7 +3,7 @@ Settings panel for the digital twin widget """ # pylint: disable=E0611 -from qtpy.QtWidgets import QLayout, QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout, QWidget from debye_bec.bec_widgets.widgets.digital_twin.widgets.qt_widgets import ( Button, @@ -18,7 +18,8 @@ class SettingsPanel(QWidget): def __init__(self, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) - self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore + self._layout.setContentsMargins(4, 4, 4, 4) + self._layout.setSpacing(4) # Reload offsets self.load_offsets = Button(label="Load Offsets", label_button="Load", enabled=True) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py index 08e87d5..65efd74 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/widgets/move_widget.py @@ -128,8 +128,8 @@ class MotionWorker(QObject): """ position_changed = Signal(float) - error = Signal(bool) # True = error - finished = Signal(bool) # True = reached target, False = stopped + error = Signal() + finished = Signal() def __init__(self, dev, motor, target_pos: float): super().__init__() @@ -284,7 +284,7 @@ class MotionWorker(QObject): fb = surv_ax["device"].read(cached=True)[surv_ax["name"]]["value"] if abs(fb - surv_ax["old_value"]) > surv_ax["abs_tol"]: self.dev[self.motor].stop() - self.error.emit(1) + self.error.emit() break self.finished.emit() @@ -315,35 +315,33 @@ class MoveWidget(QWidget): self.decimals = decimals layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(4, 0, 4, 0) + layout.setSpacing(4) # Name self.label = QLabel(label) - self.label.setFixedWidth(100) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(76) self.label.setWordWrap(True) layout.addWidget(self.label) # Target self.target_label = QLabel("-") - self.target_label.setFixedWidth(100) + self.target_label.setFixedWidth(84) layout.addWidget(self.target_label) # Feedback self.fb_label = QLabel("-") - self.fb_label.setFixedWidth(100) + self.fb_label.setFixedWidth(84) layout.addWidget(self.fb_label) # Status icon self.status_icon = StatusIcon() - self.status_icon.setFixedWidth(30) - self.status_icon.setContentsMargins(0, 0, 10, 0) + self.status_icon.setFixedWidth(24) layout.addWidget(self.status_icon) # Start / Stop button self.btn_action = QPushButton("Move") - self.btn_action.setFixedWidth(90) + self.btn_action.setFixedWidth(64) self.btn_action.setFixedHeight(20) self.btn_action.clicked.connect(self._on_button_clicked) layout.addWidget(self.btn_action) @@ -522,35 +520,33 @@ class AbsorberWidget(QWidget): self.text_color = (0, 0, 0) layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(4, 0, 4, 0) + layout.setSpacing(4) # Name self.label = QLabel(label) - self.label.setFixedWidth(100) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(76) self.label.setWordWrap(True) layout.addWidget(self.label) # Blank self.blank_label = QLabel("") - self.blank_label.setFixedWidth(100) + self.blank_label.setFixedWidth(84) layout.addWidget(self.blank_label) # Feedback self.fb_label = QLabel("-") - self.fb_label.setFixedWidth(100) + self.fb_label.setFixedWidth(84) layout.addWidget(self.fb_label) # Blank icon self.blank_icon = QLabel("") - self.blank_icon.setFixedWidth(30) - self.blank_icon.setContentsMargins(0, 0, 10, 0) + self.blank_icon.setFixedWidth(24) layout.addWidget(self.blank_icon) # Open self.btn_action = QPushButton("Open") - self.btn_action.setFixedWidth(90) + self.btn_action.setFixedWidth(64) self.btn_action.setFixedHeight(20) self.btn_action.clicked.connect(self._on_button_clicked) layout.addWidget(self.btn_action) diff --git a/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py b/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py index 5a0f59b..311c63b 100644 --- a/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py +++ b/debye_bec/bec_widgets/widgets/digital_twin/widgets/qt_widgets.py @@ -21,11 +21,17 @@ from qtpy.QtWidgets import ( QWidget, ) +LABEL_WIDTH = 118 +ROW_MARGINS = (4, 0, 4, 0) +ROW_SPACING = 6 + class Group(QGroupBox): def __init__(self, label, widgets): super().__init__(label) self.layout = QVBoxLayout(self) # type: ignore + self.layout.setContentsMargins(6, 6, 6, 6) + self.layout.setSpacing(4) for widget in widgets: self.layout.addWidget(widget) # type: ignore @@ -34,16 +40,14 @@ class NumberIndicator(QWidget): def __init__(self, label="", unit=None, highlight=False, decimals=3): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.val = QLabel("-") self.val.setAlignment(Qt.AlignTop) # type: ignore - # self.val.setFixedWidth(140) layout.addWidget(self.val) self.unit = unit self.highlight = highlight @@ -85,12 +89,11 @@ class InputNumberField(QWidget): ): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.identifier = identifier self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.val = QDoubleSpinBox() @@ -102,7 +105,6 @@ class InputNumberField(QWidget): self.val.setSuffix(" " + unit) if prefix is not None: self.val.setPrefix(prefix + " ") - # self.val.setFixedWidth(140) layout.addWidget(self.val) def set_number(self, number): @@ -124,19 +126,18 @@ class InputNumberField(QWidget): class ComboBox(QWidget): - def __init__(self, identifier="", label="", enums=[]): + def __init__(self, identifier="", label="", enums=None): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.identifier = identifier self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.value = QComboBox() - for entry in enums: + for entry in enums or []: self.value.addItem(entry) layout.addWidget(self.value) @@ -168,15 +169,15 @@ class Button(QWidget): def __init__(self, label=None, label_button: str = "", enabled=False): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) if label is not None: self.label = QLabel(label) - self.label.setFixedWidth(140) + self.label.setFixedWidth(LABEL_WIDTH) layout.addWidget(self.label) self.button = QPushButton(label_button) if label is not None: - self.button.setFixedWidth(160) + self.button.setFixedWidth(130) self.enable_button(enabled) layout.addWidget(self.button) @@ -204,11 +205,10 @@ class TextIndicator(QWidget): def __init__(self, label): super().__init__() layout = QHBoxLayout(self) - layout.setContentsMargins(10, 0, 0, 0) - layout.setSpacing(0) + layout.setContentsMargins(*ROW_MARGINS) + layout.setSpacing(ROW_SPACING) self.label = QLabel(label) - self.label.setFixedWidth(140) - self.label.setContentsMargins(0, 0, 10, 0) + self.label.setFixedWidth(LABEL_WIDTH) self.label.setWordWrap(True) layout.addWidget(self.label) self.text = QLabel("-") @@ -223,84 +223,3 @@ class TextIndicator(QWidget): def setColor(self, color: str): self.text.setStyleSheet(f"QLabel {{color:{color}}}") - - -# class Button(QWidget): -# def __init__(self, label, label_button): -# super().__init__() -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(150) -# layout.addWidget(self.label) -# self.button = QPushButton(label_button) -# self.button.setStyleSheet("color: black; background-color: dodgerblue;") -# self.button.setFixedWidth(160) -# layout.addWidget(self.button) - -# def set_on_press(self, func): -# """Connect a function to the button press.""" -# self.button.clicked.connect(func) - -# def enable_button(self): -# self.button.setEnabled(True) -# self.button.setStyleSheet("color: black; background-color: dodgerblue;") - -# def disable_button(self): -# self.button.setEnabled(False) -# self.button.setStyleSheet("color: black; background-color: grey;") - -# def set_button_text(self, text): -# self.button.setText(text) - -# class LED(QWidget): -# def __init__(self, states, colors, label): -# super().__init__() -# self.states = states -# self.colors = colors -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(150) -# layout.addWidget(self.label) -# self.led = QLabel() -# self.led.setFixedWidth(160) -# layout.addWidget(self.led) - -# def apply_color(self, val): -# color = self.colors[self.states.index(val)] -# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;") - -# class InputTextField(QWidget): -# def __init__(self, topic, label): -# super().__init__() -# self.topic = topic -# layout = QHBoxLayout(self) -# layout.setContentsMargins(10, 0, 0, 0) -# layout.setSpacing(0) -# self.label = QLabel(label) -# self.label.setFixedWidth(140) -# self.label.setContentsMargins(0, 0, 10, 0) -# self.label.setWordWrap(True) -# layout.addWidget(self.label) -# self.val = QLineEdit() -# self.val.setPlaceholderText('0') -# # self.val.setFixedWidth(140) -# layout.addWidget(self.val) - -# def set_text(self, text): -# self.val.setText(text) - -# def has_focus(self) -> bool: -# return self.val.hasFocus() - -# def text(self) -> str: -# return self.val.text() - -# def set_on_return(self, func): -# """Connect a function to the Enter/Return key press.""" -# self.val.returnPressed.connect( -# partial(func, self.val, self.topic, lambda: self.val.text()) -# )