diff --git a/src/aare/gui/widgets/camera_image.py b/src/aare/gui/widgets/camera_image.py index d02a0285..c13ff300 100644 --- a/src/aare/gui/widgets/camera_image.py +++ b/src/aare/gui/widgets/camera_image.py @@ -585,6 +585,11 @@ class SampleCameraImageLabel(QGraphicsView): if self._hover_pos is not None: self._hover_pos = None self.update() + if self._help_expanded: + # Pointer left the widget with the cheatsheet open — fold it, + # same as wandering off the box below. + self._help_expanded = False + self.update() super().leaveEvent(event) def mouseMoveEvent(self, event): @@ -593,6 +598,19 @@ class SampleCameraImageLabel(QGraphicsView): self._hover_pos = self.mapToScene(event.pos()) self.update() + # Expanded help folds when the pointer wanders off the box — click + # is not the only way back to the "?" badge. Before the gate: the + # badge is a pure UI affordance, live even in viewing mode. + if ( + self._help_expanded + and self._help_hit_rect is not None + and not self._help_hit_rect.contains( + QPointF(self.viewport().mapFrom(self, event.pos())) + ) + ): + self._help_expanded = False + self.update() + # Badge hover feedback must run BEFORE the interaction gate: the # badge is visible precisely when interaction is disabled. hovered = self._session_badge_rect is not None and self._session_badge_rect.contains( diff --git a/tests/unit/gui/test_camera_image.py b/tests/unit/gui/test_camera_image.py index 17b6fd23..68045ff5 100644 --- a/tests/unit/gui/test_camera_image.py +++ b/tests/unit/gui/test_camera_image.py @@ -114,6 +114,29 @@ def test_help_badge_click_toggles_cheatsheet(camera, qtbot): assert not camera._help_expanded +def test_help_overlay_folds_when_mouse_leaves_box(camera, qtbot): + camera.grab() # paint records the collapsed "?" badge hit rect + badge = camera._help_hit_rect + qtbot.mouseClick(camera.viewport(), Qt.MouseButton.LeftButton, pos=badge.center().toPoint()) + assert camera._help_expanded + camera.grab() # expanded paint records the full box rect + box = camera._help_hit_rect + + _mouse_move(camera, box.center().toPoint()) # inside the box: stays open + assert camera._help_expanded + + _mouse_move(camera, QPoint(int(box.left()) - 40, int(box.bottom()) + 40)) + assert not camera._help_expanded, "moving off the box must fold it back to the badge" + + # Leaving the widget entirely folds it too. + camera.grab() + badge = camera._help_hit_rect + qtbot.mouseClick(camera.viewport(), Qt.MouseButton.LeftButton, pos=badge.center().toPoint()) + assert camera._help_expanded + camera.leaveEvent(QEvent(QEvent.Type.Leave)) + assert not camera._help_expanded + + def test_camera_error_message_rewords_and_draws(camera): camera.set_camera_available(False) camera.set_camera_error_message("Sample camera feed unavailable: cable unplugged")