diff --git a/src/aare/gui/widgets/camera_image.py b/src/aare/gui/widgets/camera_image.py index 03454282..04705f8b 100644 --- a/src/aare/gui/widgets/camera_image.py +++ b/src/aare/gui/widgets/camera_image.py @@ -59,6 +59,7 @@ from aare.gui.styles import ( THEME_SUNSET, TOOLTIP_TEXT, WHITE, + font_scale, qcolor, ) from aare.gui.widgets.busy_overlay import ( @@ -130,6 +131,7 @@ class SampleCameraImageLabel(QGraphicsView): self._tell_state = None self._auto_centering = False self._busy_overlay_style: BusyOverlayStyle | None = None + self._mounted_sample_name: str | None = None self._geom = geom self._bookmarks: SmargonBookmarkList = SmargonBookmarkList() @@ -459,6 +461,7 @@ class SampleCameraImageLabel(QGraphicsView): self._draw_detections(painter, rect) self._draw_target_point(painter) self._draw_overlay_legend(painter) + self._draw_mounted_sample(painter) self._draw_hover_hud(painter) self._draw_help_overlay(painter) @@ -803,6 +806,11 @@ class SampleCameraImageLabel(QGraphicsView): self._bounding_box = s.box self._tell_state = s.tell_state + new_name = s.sample.sample_name if s.sample is not None else None + if new_name != self._mounted_sample_name: + self._mounted_sample_name = new_name + self.viewport().update() + new_session_state = s.session.session if hasattr(s, "session") else None if new_session_state != self._session_state: self._session_state = new_session_state @@ -1269,6 +1277,32 @@ class SampleCameraImageLabel(QGraphicsView): label = f"{bar_um / 1000.0:g} mm" if bar_um >= 1000.0 else f"{bar_um:g} µm" return bar_um, label + def _draw_mounted_sample(self, painter: QPainter): + # Top-left HUD line (legend sits bottom-left, scale bar bottom-right): + # which sample is on the gonio, readable without leaving the camera. + if not self._mounted_sample_name: + return + + painter.save() + painter.resetTransform() + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + + font = QFont() + # Follows the Ctrl+plus/minus text zoom — painted HUDs bypass QSS. + font.setPointSize(round(10 * font_scale())) + font.setBold(True) + painter.setFont(font) + fm = QFontMetrics(font) + + margin = 18 + text = f"Currently mounted: {self._mounted_sample_name}" + baseline = margin + fm.ascent() + painter.setPen(QPen(qcolor(SHADOW, 200))) + painter.drawText(QPointF(margin + 1, baseline + 1), text) + painter.setPen(QPen(qcolor(WHITE))) + painter.drawText(QPointF(margin, baseline), text) + painter.restore() + def _draw_hover_hud(self, painter: QPainter): # Bottom-right HUD: grey scale bar over the hovered pixel coordinates. if self._hover_pos is None or self.pixmap_item is None: diff --git a/tests/unit/gui/test_camera_image.py b/tests/unit/gui/test_camera_image.py index be91b41a..bf28c75e 100644 --- a/tests/unit/gui/test_camera_image.py +++ b/tests/unit/gui/test_camera_image.py @@ -297,3 +297,30 @@ def test_unknown_session_paints_the_viewing_mode_badge(camera): assert camera._busy_overlay_style is None camera.grab() assert camera._session_badge_rect is not None + + +def test_mounted_sample_hud_follows_status(camera): + from aarecommon.models.models import DewarAddress, SampleShortInfo + + # No sample in the status -> no HUD line, draw path skips cleanly. + assert camera._mounted_sample_name is None + camera.grab() + + sample = SampleShortInfo( + db_id=7, + puck_name="puck1", + dewar_name="dewar", + sample_name="lyso_007", + run_number=1, + user="p123", + pin=1, + location=DewarAddress(segment="A", pos=1), + ) + status = _status(busy=False, session=SessionsStateEnum.OwnedByYou) + camera.update_daq_status(status.model_copy(update={"sample": sample})) + assert camera._mounted_sample_name == "lyso_007" + camera.grab() # paints the top-left "Currently mounted:" line + + # Unmount (sample gone from the status) clears the line again. + camera.update_daq_status(status) + assert camera._mounted_sample_name is None