fix: window and dock sizing behavior

Three main-window chrome fixes: the un-maximized geometry is pre-set to
half-screen centered so leaving maximized mode no longer restores an
oversized size hint (the beamline state bar also stops imposing its
~2000px minimum width); closing a popped-out dock re-docks it instead
of silently hiding the panel, and pop-outs open enlarged and clamped to
the screen; state-bar buttons only update font/stylesheet/cursor when
values change, since repolishing every DAQ tick dropped the hover
cursor under a resting mouse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 10:35:46 +02:00
co-authored by Claude Fable 5
parent 00e5d116a6
commit 266f8337a9
3 changed files with 80 additions and 8 deletions
+9
View File
@@ -189,6 +189,15 @@ def main():
splash.set_progress(100, "Ready")
splash.finish(win)
# Pre-set the "normal" (un-maximized) geometry as a fraction of the
# primary screen, centered — otherwise leaving maximized mode restores
# the size hint, which is wider than the monitor. Other panels may
# still enforce a somewhat larger minimum; the window then lands on
# that minimum instead.
unmax_w, unmax_h = 0.5, 0.7
available = app.primaryScreen().availableGeometry()
win.resize(int(available.width() * unmax_w), int(available.height() * unmax_h))
win.move(available.center() - win.rect().center())
# Maximized so the window adapts to the monitor instead of its size hint,
# which is taller than a 1920x1200 console.
win.showMaximized()
+38
View File
@@ -884,6 +884,13 @@ class MainWindow(QMainWindow):
register_tutorials(self, self.tutorial_manager)
# Every dock: closing its popped-out window brings it back into the
# layout instead of hiding it (eventFilter), and popping out opens
# an enlarged window (2x width, 3x height, clamped to the screen).
for dock in self.findChildren(QDockWidget):
dock.installEventFilter(self)
dock.topLevelChanged.connect(self._on_dock_top_level_changed)
def _setup_global_shortcuts(self) -> None:
self._shortcut_manual_sample = QAction("Expand Manual Sample", self)
self._shortcut_manual_sample.setShortcut(QKeySequence("Ctrl+M"))
@@ -2410,7 +2417,38 @@ class MainWindow(QMainWindow):
def _mark_user_interaction(self) -> None:
self._refresh_idle_activity(report_backend=True)
@Slot(bool)
def _on_dock_top_level_changed(self, floating: bool) -> None:
dock = self.sender()
if floating and isinstance(dock, QDockWidget):
# Pop-outs open enlarged instead of keeping the cramped docked
# size. Deferred: the window is mid-reparent while the signal
# fires.
QTimer.singleShot(0, lambda d=dock: self._enlarge_floating_dock(d))
def _enlarge_floating_dock(self, dock: QDockWidget) -> None:
if not dock.isFloating():
return
screen = dock.screen().availableGeometry()
dock.resize(
min(dock.width() * 2, int(screen.width() * 0.9)),
min(dock.height() * 3, int(screen.height() * 0.9)),
)
# Keep the enlarged window fully on screen.
geo = dock.frameGeometry()
dx = min(0, screen.right() - geo.right())
dy = min(0, screen.bottom() - geo.bottom())
if dx or dy:
dock.move(geo.x() + dx, geo.y() + dy)
def eventFilter(self, obj, event):
# Closing a floated (popped-out) dock re-docks it instead of hiding —
# otherwise the panel silently disappears and has to be restored via
# the View menu.
if event.type() == QEvent.Type.Close and isinstance(obj, QDockWidget) and obj.isFloating():
obj.setFloating(False)
event.ignore()
return True
try:
if event.type() in {
QEvent.Type.MouseButtonPress,
+33 -8
View File
@@ -5,7 +5,13 @@ from PySide6.QtCore import Qt, QTimer, Signal, Slot
from PySide6.QtGui import QCursor, QFont, QFontMetrics
from PySide6.QtWidgets import QFrame, QHBoxLayout, QLabel, QMenu, QPushButton, QSizePolicy, QToolTip
from aare.gui.styles import STATE_AVAILABLE, STATE_MSG_ERROR, STATE_MSG_INFO, STATE_UNAVAILABLE
from aare.gui.styles import (
FONT_VALUE,
STATE_AVAILABLE,
STATE_MSG_ERROR,
STATE_MSG_INFO,
STATE_UNAVAILABLE,
)
# Shortcut transitions from the "Available transitions" menu in
# widgets/status_bar.py show_state_menu — these come ON TOP of the one-hop
@@ -154,7 +160,7 @@ class BeamlineStatePanel(QFrame):
if index:
separator = QLabel("–", self)
separator.setStyleSheet(
f"color: {STATE_UNAVAILABLE}; background: transparent; border: none; font-size: 18px;"
f"color: {STATE_UNAVAILABLE}; background: transparent; border: none; font-size: {FONT_VALUE};"
)
layout.addWidget(separator)
button = HoverableButton(label, self)
@@ -178,6 +184,15 @@ class BeamlineStatePanel(QFrame):
layout.addStretch(1)
self._apply_highlight()
def minimumSizeHint(self):
# The 13-button strip would otherwise impose a ~2000px minimum on the
# whole main window, so an un-maximized window could never fit the
# screen. Width 0: the strip adapts (two-line labels) and, below that,
# clips — the window stays freely resizable.
hint = super().minimumSizeHint()
hint.setWidth(0)
return hint
def resizeEvent(self, event) -> None:
super().resizeEvent(event)
self._update_label_mode()
@@ -325,21 +340,31 @@ class BeamlineStatePanel(QFrame):
font = QFont(self.font())
font.setPixelSize(18)
font.setWeight(QFont.Weight.Bold if bold else QFont.Weight.Normal)
button.setFont(font)
button.setStyleSheet(
if button.font() != font:
button.setFont(font)
# Guarded updates: this runs on every DAQ tick, and re-applying an
# unchanged stylesheet repolishes the button, which drops the hover
# cursor under a resting mouse until it moves again.
qss = (
f"QPushButton {{ border: none; background: transparent; color: {color};"
f" padding: 1px 8px; }}"
f" QPushButton:hover {{ color: {color}; }}"
)
if button.styleSheet() != qss:
button.setStyleSheet(qss)
# Clickability follows availability; unavailable states get the
# forbidden cursor and only the deferred 3 s explanation tooltip.
if is_available:
button.setCursor(Qt.CursorShape.PointingHandCursor)
button.setToolTip(self._TOOLTIPS.get(state, state.display_name()))
cursor = Qt.CursorShape.PointingHandCursor
tooltip = self._TOOLTIPS.get(state, state.display_name())
else:
button.setCursor(Qt.CursorShape.ForbiddenCursor)
button.setToolTip("")
cursor = Qt.CursorShape.ForbiddenCursor
tooltip = ""
if button.cursor().shape() != cursor:
button.setCursor(cursor)
if button.toolTip() != tooltip:
button.setToolTip(tooltip)
def set_current_state(self, state: BeamlineStateEnum | None) -> None:
self._current_state = state