style: visual and interaction polish
CI / test (3.11) (pull_request) Successful in 1m23s
CI / test (3.12) (pull_request) Successful in 1m22s
CI / test (3.13) (pull_request) Successful in 1m21s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m26s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m42s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m40s
CI / test-with-coverage (pull_request) Successful in 2m2s
CI / coverage-analysis (pull_request) Successful in 4s
CI / lint (pull_request) Successful in 2m37s
CI / lint (push) Successful in 29s
Docs build and publish / docker (push) Successful in 12s
CI / test (3.11) (push) Canceled after 34s
CI / test (3.12) (push) Canceled after 30s
CI / test (3.13) (push) Canceled after 29s
CI / test-with-beamline-plugins (pxi_bec) (push) Canceled after 25s
CI / test-with-coverage (push) Canceled after 19s
CI / test-with-beamline-plugins (pxii_bec) (push) Canceled after 24s
CI / test-with-beamline-plugins (pxiii_bec) (push) Canceled after 20s
CI / coverage-analysis (push) Canceled after 0s
Build and Publish / release (push) Failing after 5s
CI / test (3.11) (pull_request) Successful in 1m23s
CI / test (3.12) (pull_request) Successful in 1m22s
CI / test (3.13) (pull_request) Successful in 1m21s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Successful in 1m26s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Successful in 1m42s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Successful in 1m40s
CI / test-with-coverage (pull_request) Successful in 2m2s
CI / coverage-analysis (pull_request) Successful in 4s
CI / lint (pull_request) Successful in 2m37s
CI / lint (push) Successful in 29s
Docs build and publish / docker (push) Successful in 12s
CI / test (3.11) (push) Canceled after 34s
CI / test (3.12) (push) Canceled after 30s
CI / test (3.13) (push) Canceled after 29s
CI / test-with-beamline-plugins (pxi_bec) (push) Canceled after 25s
CI / test-with-coverage (push) Canceled after 19s
CI / test-with-beamline-plugins (pxii_bec) (push) Canceled after 24s
CI / test-with-beamline-plugins (pxiii_bec) (push) Canceled after 20s
CI / coverage-analysis (push) Canceled after 0s
Build and Publish / release (push) Failing after 5s
Windowed startup at 50%x70% instead of maximized; overlay legend now defaults off (it covers the camera image); square slider handles with grip-line PNG assets (the round handle clipped flat); warmer sunrise gradient bottom; abr/beam-mark layout tweaks; hover cues on the beamline-state entries; DPI/font-aware pop-out titlebar icons; the out-of-date Tutorial menu entries are hidden until content is redone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit was merged in pull request #129.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 103 B |
Binary file not shown.
|
After Width: | Height: | Size: 103 B |
+5
-8
@@ -191,18 +191,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.
|
||||
# Start windowed (not maximized) at a fraction of the primary screen,
|
||||
# centered — sized explicitly because the size hint 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()
|
||||
win.show()
|
||||
sys.exit(app.exec())
|
||||
|
||||
except Exception as e:
|
||||
|
||||
@@ -1420,7 +1420,9 @@ class MainWindow(QMainWindow):
|
||||
show_target_coordinates = bool(
|
||||
settings.value("samcam/show_target_coordinates", True, type=bool)
|
||||
)
|
||||
show_overlay_legend = bool(settings.value("samcam/show_overlay_legend", True, type=bool))
|
||||
# Legend defaults off — it covers the camera image; users opt in and
|
||||
# the choice persists via QSettings.
|
||||
show_overlay_legend = bool(settings.value("samcam/show_overlay_legend", False, type=bool))
|
||||
compact_overlay_legend = bool(
|
||||
settings.value("samcam/compact_overlay_legend", False, type=bool)
|
||||
)
|
||||
@@ -1693,7 +1695,7 @@ class MainWindow(QMainWindow):
|
||||
try:
|
||||
settings = self.portrait_sample_camera.target_overlay_settings()
|
||||
self.portrait_sample_camera.set_show_overlay_legend(
|
||||
settings.get("show_overlay_legend", True)
|
||||
settings.get("show_overlay_legend", False)
|
||||
)
|
||||
except Exception:
|
||||
logger.debug("Could not restore the camera overlay legend", exc_info=True)
|
||||
@@ -2058,15 +2060,9 @@ class MainWindow(QMainWindow):
|
||||
local_contact_action.triggered.connect(self.show_local_contact)
|
||||
help_menu.addAction(local_contact_action)
|
||||
|
||||
help_menu.addSeparator()
|
||||
|
||||
start_text_tutorial_action = QAction("Start Tutorial (Text)", self)
|
||||
start_text_tutorial_action.triggered.connect(self.start_text_tutorial)
|
||||
help_menu.addAction(start_text_tutorial_action)
|
||||
|
||||
start_interactive_tutorial_action = QAction("Start Tutorial (Interactive)", self)
|
||||
start_interactive_tutorial_action.triggered.connect(self.start_interactive_tutorial)
|
||||
help_menu.addAction(start_interactive_tutorial_action)
|
||||
# Tutorial entries hidden 2026-08-10: content is out of date. The
|
||||
# tutorial machinery stays wired — restore the two QActions here
|
||||
# (Start Tutorial (Text)/(Interactive)) once the content is refreshed.
|
||||
|
||||
def _capture_default_window_state(self) -> None:
|
||||
self._default_window_state = self.saveState()
|
||||
@@ -2874,7 +2870,8 @@ class MainWindow(QMainWindow):
|
||||
# the merged Information dock defaults visible, so old values would
|
||||
# wrongly hide it.
|
||||
if settings.contains("information"):
|
||||
self.information_dock.setVisible(settings.value("information", True, type=bool))
|
||||
# bool() wrap: settings.value is typed object even with type=bool.
|
||||
self.information_dock.setVisible(bool(settings.value("information", True, type=bool)))
|
||||
|
||||
settings.endGroup()
|
||||
|
||||
|
||||
@@ -20,10 +20,15 @@ class AbrTweakButtons(QWidget):
|
||||
|
||||
self._step_mm = step_mm
|
||||
grid_layout = QGridLayout(self)
|
||||
grid_layout.setColumnStretch(0, 1)
|
||||
# Caption, arrows, and value pack tight to the left; the empty
|
||||
# trailing column soaks up all dead width. The values keep AlignRight
|
||||
# in their content-width column so the decimal points line up without
|
||||
# drifting over next to the Step column.
|
||||
grid_layout.setColumnStretch(0, 0)
|
||||
grid_layout.setColumnStretch(1, 0)
|
||||
grid_layout.setColumnStretch(2, 0)
|
||||
grid_layout.setColumnStretch(3, 3)
|
||||
grid_layout.setColumnStretch(3, 0)
|
||||
grid_layout.setColumnStretch(4, 1)
|
||||
|
||||
grid_layout.addWidget(QLabel("GMX"), 0, 0)
|
||||
button_gmx_minus = ButtonWithPayload("←", payload={"x": -1, "y": 0, "z": 0})
|
||||
|
||||
@@ -27,7 +27,7 @@ class BeamMarkWidget(QWidget):
|
||||
|
||||
# Shares the readings row instead of a full-width row below.
|
||||
clear_button = QPushButton("Clear marks")
|
||||
clear_button.setFixedWidth(90)
|
||||
clear_button.setFixedWidth(100) # 100 is needed to see everything
|
||||
grid_layout.addWidget(clear_button, 1, 5)
|
||||
clear_button.pressed.connect(self.clear_button_pressed)
|
||||
|
||||
|
||||
@@ -356,17 +356,28 @@ class BeamlineStatePanel(QFrame):
|
||||
# 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.
|
||||
# Hover underline (the app-wide tab/chip affordance) only on
|
||||
# entries that mean something: the current state and reachable
|
||||
# targets — not the grey dead ends.
|
||||
hover_underline = (
|
||||
" text-decoration: underline;" if (is_current or is_pending or is_available) else ""
|
||||
)
|
||||
qss = (
|
||||
f"QPushButton {{ border: none; background: transparent; color: {color};"
|
||||
f" padding: 1px 8px; }}"
|
||||
f" QPushButton:hover {{ color: {color}; }}"
|
||||
f" QPushButton:hover {{ color: {color};{hover_underline} }}"
|
||||
)
|
||||
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:
|
||||
# The current state is not a click target, but it is not
|
||||
# forbidden either — plain cursor + a "you are here" tip.
|
||||
if is_current or is_pending:
|
||||
cursor = Qt.CursorShape.ArrowCursor
|
||||
tooltip = f"{state.display_name()}: this is the current state"
|
||||
elif is_available:
|
||||
cursor = Qt.CursorShape.PointingHandCursor
|
||||
tooltip = self._TOOLTIPS.get(state, state.display_name())
|
||||
else:
|
||||
|
||||
@@ -106,7 +106,7 @@ class SamcamPanel(QWidget):
|
||||
)
|
||||
|
||||
self.show_overlay_legend_checkbox = QCheckBox("Show overlay legend")
|
||||
self.show_overlay_legend_checkbox.setChecked(True)
|
||||
self.show_overlay_legend_checkbox.setChecked(False)
|
||||
self.show_overlay_legend_checkbox.toggled.connect(self.show_overlay_legend_changed.emit)
|
||||
|
||||
self.compact_overlay_legend_checkbox = QCheckBox("Compact legend")
|
||||
|
||||
+15
-10
@@ -29,9 +29,9 @@ BACKGROUND = "#e2e7ee"
|
||||
# continuous sky instead of every widget restarting the gradient.
|
||||
# Tune the transition point here:
|
||||
BACKGROUND_GRADIENT_TOP = "#84abd9" # RHEL9 window-frame blue (sampled from screenshot)
|
||||
BACKGROUND_GRADIENT_MID = "#bbd6f6"
|
||||
BACKGROUND_GRADIENT_MID_POS = "0.65" # 0..1 — where the mid stop sits
|
||||
BACKGROUND_GRADIENT_BOTTOM = "#dbe9f9"
|
||||
BACKGROUND_GRADIENT_MID = "#adccf1"
|
||||
BACKGROUND_GRADIENT_MID_POS = "0.55" # 0..1 — where the mid stop sits
|
||||
BACKGROUND_GRADIENT_BOTTOM = "#f8e9c5"
|
||||
APP_BACKGROUND = (
|
||||
"qlineargradient(x1:0, y1:0, x2:0, y2:1,"
|
||||
f" stop:0 {BACKGROUND_GRADIENT_TOP},"
|
||||
@@ -75,6 +75,10 @@ SPIN_ARROW_UP = (_GRAPHICS_DIR / "spin_arrow_up_light.png").as_posix()
|
||||
SPIN_ARROW_DOWN = (_GRAPHICS_DIR / "spin_arrow_down_light.png").as_posix()
|
||||
DARK_SPIN_ARROW_UP = (_GRAPHICS_DIR / "spin_arrow_up_dark.png").as_posix()
|
||||
DARK_SPIN_ARROW_DOWN = (_GRAPHICS_DIR / "spin_arrow_down_dark.png").as_posix()
|
||||
# Slider-handle grip lines (3 vertical ticks) — QSS can't draw interior
|
||||
# lines, so they are tiny PNG assets like the spin arrows above.
|
||||
SLIDER_GRIP = (_GRAPHICS_DIR / "slider_grip_light.png").as_posix()
|
||||
DARK_SLIDER_GRIP = (_GRAPHICS_DIR / "slider_grip_dark.png").as_posix()
|
||||
# Check marks (PRIMARY blue / dusk gold at generation time):
|
||||
CHECK_MARK = (_GRAPHICS_DIR / "check_mark_light.png").as_posix()
|
||||
DARK_CHECK_MARK = (_GRAPHICS_DIR / "check_mark_dark.png").as_posix()
|
||||
@@ -1164,9 +1168,10 @@ def _sunrise_stylesheet(overrides: dict[str, str] | None = None) -> str:
|
||||
selection-color: $selection_text;
|
||||
}
|
||||
|
||||
/* Sliders: rounded groove ends (like the scrollbar handles) + round
|
||||
handle. Wheel adjustment needs the right mouse button held — see
|
||||
WheelValueGuard. */
|
||||
/* Sliders: rounded groove ends (like the scrollbar handles) + square
|
||||
handle with grip lines (SLIDER_GRIP asset) — the old 16px round handle
|
||||
was clipped flat by the slider's widget height. Wheel adjustment needs
|
||||
the right mouse button held — see WheelValueGuard. */
|
||||
QSlider::groove:horizontal {
|
||||
height: 6px;
|
||||
background: $slider_track;
|
||||
@@ -1182,8 +1187,8 @@ def _sunrise_stylesheet(overrides: dict[str, str] | None = None) -> str:
|
||||
background: $white;
|
||||
border: 1px solid $slider_muted;
|
||||
width: 14px;
|
||||
margin: -5px 0;
|
||||
border-radius: 7px;
|
||||
margin: -3px 0;
|
||||
image: url($slider_grip);
|
||||
}
|
||||
|
||||
/* Soft scrollbars: square track band (runs flush to the widget edges —
|
||||
@@ -1772,8 +1777,8 @@ def _sunset_stylesheet() -> str:
|
||||
background: $dark_elevated;
|
||||
border: 1px solid $dark_muted;
|
||||
width: 14px;
|
||||
margin: -5px 0;
|
||||
border-radius: 7px;
|
||||
margin: -3px 0;
|
||||
image: url($dark_slider_grip);
|
||||
}
|
||||
|
||||
/* Soft scrollbars: square track band + rounded handle, no end arrows
|
||||
|
||||
@@ -141,7 +141,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
|
||||
self._show_target_point = True
|
||||
self._show_target_coordinates = True
|
||||
self._show_overlay_legend = True
|
||||
self._show_overlay_legend = False
|
||||
self._compact_overlay_legend = False
|
||||
# "?" badge is a mouse-controls cheatsheet, decoupled from the legend —
|
||||
# legend visibility is already handled by the panel checkboxes.
|
||||
|
||||
@@ -4,7 +4,10 @@ from PySide6.QtWidgets import QDockWidget, QHBoxLayout, QLabel, QToolButton, QVB
|
||||
|
||||
from aare.gui.styles import FRAME_L1_COLOR, FRAME_L1_WIDTH, qcolor
|
||||
|
||||
# Title-bar buttons: icon fills the button, both the same size.
|
||||
# Title-bar buttons: icon fills the button, both the same size. These are
|
||||
# minimums — the real size follows the font metrics (see _titlebar_button),
|
||||
# so the glyphs keep up with the DPI/font settings of the machine (fixed px
|
||||
# rendered tiny on the RHEL9 consoles).
|
||||
TITLEBAR_BUTTON_PX = 22
|
||||
TITLEBAR_ICON_PX = 18
|
||||
|
||||
@@ -14,7 +17,11 @@ def _titlebar_icon(kind: str, color: QColor, size: int = TITLEBAR_ICON_PX) -> QI
|
||||
pixmaps draw boxed icons, and text glyphs are missing from the
|
||||
container's fonts. Color comes from the caller's palette so the glyphs
|
||||
follow the theme (they are pixmaps, QSS color cannot reach them)."""
|
||||
pixmap = QPixmap(size, size)
|
||||
# Paint at the physical resolution so the glyph stays crisp on HiDPI.
|
||||
screen = QGuiApplication.primaryScreen()
|
||||
dpr = screen.devicePixelRatio() if screen is not None else 1.0
|
||||
pixmap = QPixmap(round(size * dpr), round(size * dpr))
|
||||
pixmap.setDevicePixelRatio(dpr)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
@@ -37,11 +44,21 @@ def _titlebar_icon(kind: str, color: QColor, size: int = TITLEBAR_ICON_PX) -> QI
|
||||
def _titlebar_button(parent: QWidget, tooltip: str) -> QToolButton:
|
||||
# Icon is set by DockTitleBar._tint_icons (initially and on theme change).
|
||||
button = QToolButton(parent)
|
||||
button.setIconSize(QSize(TITLEBAR_ICON_PX, TITLEBAR_ICON_PX))
|
||||
button.setFixedSize(TITLEBAR_BUTTON_PX, TITLEBAR_BUTTON_PX)
|
||||
# Size from the font, not fixed px: a setup with larger fonts/DPI gets
|
||||
# proportionally larger glyphs. The constants act as the floor.
|
||||
icon_px = max(TITLEBAR_ICON_PX, parent.fontMetrics().height())
|
||||
button_px = icon_px + (TITLEBAR_BUTTON_PX - TITLEBAR_ICON_PX)
|
||||
button.setIconSize(QSize(icon_px, icon_px))
|
||||
button.setFixedSize(button_px, button_px)
|
||||
button.setAutoRaise(True)
|
||||
# No button chrome — the glyph IS the button.
|
||||
button.setStyleSheet("QToolButton { border: none; background: transparent; }")
|
||||
# No button chrome — the glyph IS the button. The padding/height zeroing
|
||||
# is the opt-out from the app sheets' global QToolButton cap (16px +
|
||||
# padding): without it the light theme's horizontal padding shrinks the
|
||||
# content box and Qt scales the glyph down to a speck.
|
||||
button.setStyleSheet(
|
||||
"QToolButton { border: none; background: transparent;"
|
||||
f" padding: 0px; min-height: 0px; max-height: {button_px}px; }}"
|
||||
)
|
||||
button.setToolTip(tooltip)
|
||||
button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
return button
|
||||
@@ -82,7 +99,7 @@ class DockTitleBar(QWidget):
|
||||
def _tint_icons(self) -> None:
|
||||
color = self.palette().color(QPalette.ColorRole.WindowText)
|
||||
for kind, button in self._icon_buttons.items():
|
||||
button.setIcon(_titlebar_icon(kind, color))
|
||||
button.setIcon(_titlebar_icon(kind, color, button.iconSize().width()))
|
||||
|
||||
def changeEvent(self, event):
|
||||
# A theme switch lands here as a palette/style change; the glyphs are
|
||||
|
||||
Reference in New Issue
Block a user