-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ring_progress_bar): added hover mouse effect #1031
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: pre_release_v3
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -82,6 +82,16 @@ def __init__(self, parent: RingProgressContainerWidget | None = None, client=Non | |||||||||||||||||||||||||||||
| self.registered_slot: tuple[Callable, str | EndpointInfo] | None = None | ||||||||||||||||||||||||||||||
| self.RID = None | ||||||||||||||||||||||||||||||
| self._gap = 5 | ||||||||||||||||||||||||||||||
| self._hovered = False | ||||||||||||||||||||||||||||||
| self._hover_progress = 0.0 | ||||||||||||||||||||||||||||||
| self._hover_animation = QtCore.QPropertyAnimation(self, b"hover_progress") | ||||||||||||||||||||||||||||||
| self._hover_animation.setDuration(180) | ||||||||||||||||||||||||||||||
| easing_curve = ( | ||||||||||||||||||||||||||||||
| QtCore.QEasingCurve.Type.OutCubic | ||||||||||||||||||||||||||||||
| if hasattr(QtCore.QEasingCurve, "Type") | ||||||||||||||||||||||||||||||
| else QtCore.QEasingCurve.OutCubic | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
| self._hover_animation.setEasingCurve(easing_curve) | ||||||||||||||||||||||||||||||
| self.set_start_angle(self.config.start_position) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
| def cleanup(self) -> None: | |
| """ | |
| Clean up resources associated with this progress bar. | |
| Stops the hover animation and delegates to any base-class cleanup if available. | |
| """ | |
| parent_cleanup = getattr(super(), "cleanup", None) | |
| if callable(parent_cleanup): | |
| parent_cleanup() | |
| # Ensure the hover animation is stopped to prevent it from running on a destroyed object. | |
| self._hover_animation.stop() |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The hover effect increases both the line width and the radius of the ring. The radius expansion (hover_radius_delta = 4.0) could cause the hovered ring to overlap with adjacent rings, especially when rings are closely spaced. This might create visual artifacts or confusion about which ring is actually hovered. Consider either:
- Reducing the radius expansion to prevent overlap
- Accounting for the gap between rings when calculating the expansion
- Only expanding the line width without changing the radius
| if self._hover_progress > 0.0: | |
| hover_radius_delta = 4.0 | |
| base_radius = adjusted_rect.width() / 2 | |
| if base_radius > 0: | |
| target_radius = base_radius + (hover_radius_delta * self._hover_progress) | |
| scale = target_radius / base_radius | |
| center = adjusted_rect.center() | |
| new_width = adjusted_rect.width() * scale | |
| new_height = adjusted_rect.height() * scale | |
| adjusted_rect = QtCore.QRectF( | |
| center.x() - new_width / 2, center.y() - new_height / 2, new_width, new_height | |
| ) | |
| # Keep hover feedback limited to line width changes to avoid overlapping adjacent rings. | |
| # The adjusted_rect (and thus radius) is not scaled based on hover state. |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the value changes, the tooltip is updated only if this ring is currently hovered. However, there's a potential race condition: if the hover state changes between checking is_ring_hovered and calling _update_hover_tooltip, the tooltip might not be updated correctly. Additionally, calling _update_hover_tooltip without the event parameter means it falls back to setToolTip, which may not display correctly when the mouse is already over the ring. Consider passing the last known mouse position or handling this case differently.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,8 +3,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pyqtgraph as pg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from bec_lib.logger import bec_logger | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from qtpy.QtCore import QSize, Qt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from qtpy.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from qtpy.QtCore import QPointF, QSize, Qt | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from qtpy.QtWidgets import QHBoxLayout, QLabel, QToolTip, QVBoxLayout, QWidget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
8
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from bec_widgets.utils import Colors | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from bec_widgets.utils.bec_widget import BECWidget | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,7 +29,9 @@ def __init__(self, parent: QWidget | None = None, **kwargs): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.rings: list[Ring] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.gap = 20 # Gap between rings | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.color_map: str = "turbo" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._hovered_ring: Ring | None = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.setLayout(QHBoxLayout()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.setMouseTracking(True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.initialize_bars() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.initialize_center_label() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -59,6 +61,7 @@ def add_ring(self, config: dict | None = None) -> Ring: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring = Ring(parent=self) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring.setGeometry(self.rect()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring.gap = self.gap * len(self.rings) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring.set_value(0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.rings.append(ring) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -106,6 +109,7 @@ def initialize_center_label(self): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.center_label = QLabel("", parent=self) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.center_label.setAlignment(Qt.AlignmentFlag.AlignCenter) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.center_label.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout.addWidget(self.center_label) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _calculate_minimum_size(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -150,6 +154,100 @@ def resizeEvent(self, event): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for ring in self.rings: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring.setGeometry(self.rect()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def mouseMoveEvent(self, event): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pos = event.position() if hasattr(event, "position") else QPointF(event.pos()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ring = self._ring_at_pos(pos) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
154
to
+159
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for ring in self.rings: | |
| ring.setGeometry(self.rect()) | |
| def mouseMoveEvent(self, event): | |
| pos = event.position() if hasattr(event, "position") else QPointF(event.pos()) | |
| ring = self._ring_at_pos(pos) | |
| rect = self.rect() | |
| for ring in self.rings: | |
| ring.setGeometry(rect) | |
| # Cache approximate ring center and maximum radius for fast hover checks | |
| center = rect.center() | |
| self._ring_center = QPointF(center) | |
| # Use half of the smaller widget dimension as an outer radius approximation | |
| self._max_ring_radius = min(rect.width(), rect.height()) / 2.0 | |
| def mouseMoveEvent(self, event): | |
| pos = event.position() if hasattr(event, "position") else QPointF(event.pos()) | |
| ring = None | |
| center = getattr(self, "_ring_center", None) | |
| max_radius = getattr(self, "_max_ring_radius", None) | |
| if center is not None and max_radius is not None: | |
| dx = pos.x() - center.x() | |
| dy = pos.y() - center.y() | |
| # Add a small margin to the radius to avoid missing near-edge hovers | |
| outer_radius = max_radius + 5.0 | |
| if (dx * dx + dy * dy) <= (outer_radius * outer_radius): | |
| ring = self._ring_at_pos(pos) | |
| else: | |
| ring = self._ring_at_pos(pos) |
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new hover functionality (mouse tracking, hover state management, and tooltip display) lacks test coverage. Given that the repository has comprehensive test coverage for ring_progress_bar functionality (as seen in tests/unit_tests/test_ring_progress_bar.py) and includes hover tests for other widgets (e.g., tests/unit_tests/test_macro_tree_widget.py::test_hover_events), consider adding tests that verify:
- Hover state transitions when mouse enters/leaves rings
- Tooltip content and display for different ring configurations
- Ring detection at various positions (_ring_at_pos logic)
- Animation behavior during hover state changes
- Value updates correctly updating tooltips while hovered
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ring detection logic uses ring.config.line_width to calculate the inner and outer boundaries. However, when hover is active, the actual line width is current_line_width = base_line_width + (hover_line_delta * self._hover_progress) (see ring.py line 439). This discrepancy means that when a ring is hovered and grows, the detection area doesn't grow with it, potentially causing the hover state to flicker or become unstable when the mouse is at the edge of the ring. Consider using the current actual line width in the detection calculation or accounting for the maximum possible hover expansion.
Copilot
AI
Jan 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tooltip formatting uses f"{ring.config.value:.{precision}f}" where precision comes from ring.config.precision. If precision is negative or excessively large, this could cause a ValueError or produce unexpected output. While this is likely validated elsewhere in the config, consider adding defensive bounds checking (e.g., precision = max(0, min(10, int(ring.config.precision)))) to ensure robust tooltip generation.
| precision = int(ring.config.precision) | |
| # Ensure precision is a safe, bounded integer to avoid formatting errors | |
| raw_precision = getattr(ring.config, "precision", 0) | |
| try: | |
| precision_int = int(raw_precision) | |
| except (TypeError, ValueError): | |
| precision_int = 0 | |
| precision = max(0, min(10, precision_int)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The easing curve fallback logic handles both old and new versions of Qt/qtpy. However, the property animation is targeting b"hover_progress" which is defined later as a QProperty. If this property definition is missing or incorrect, the animation will silently fail. Consider adding a check or assertion to verify that the property animation target is valid, or document this dependency clearly.