fix rubber band state getting stuck across interrupted drags
Two fixes: 1. Stale state cleanup. If a rubber band drag is interrupted without a matching release event (Wayland focus steal, drag outside window, tab switch, alt-tab), _rb_origin and the rubber band widget stay stuck. The next click then reuses the stale origin and rubber band stops working until the app is restarted. New _clear_stale_rubber_band helper is called at the top of every mouse press entry point (Grid.mousePressEvent, on_padding_click, ThumbnailWidget pixmap press) so the next interaction starts from a clean slate. 2. Scroll offset sign error in _rb_drag. The intersection test translated thumb geometry by +vp_offset, but thumb.geometry() is in widget coords and rb_rect is in viewport coords — the translation needs to convert between them. Switched to translating rb_rect into widget coords (rb_widget = rb_rect.translated(vp_offset)) before the intersection test, which is the mathematically correct direction. Rubber band selection now tracks the visible band when scrolled. behavior change: rubber band stays responsive after interrupted drags
This commit is contained in:
parent
e31ca07973
commit
7249d57852
@ -335,6 +335,11 @@ class ThumbnailWidget(QWidget):
|
|||||||
grid.on_padding_click(self, pos)
|
grid.on_padding_click(self, pos)
|
||||||
event.accept()
|
event.accept()
|
||||||
return
|
return
|
||||||
|
# Pixmap click — clear any stale rubber band state from a
|
||||||
|
# previous interrupted drag before starting a new interaction.
|
||||||
|
grid = self._grid()
|
||||||
|
if grid:
|
||||||
|
grid._clear_stale_rubber_band()
|
||||||
self._drag_start = pos
|
self._drag_start = pos
|
||||||
self.clicked.emit(self.index, event)
|
self.clicked.emit(self.index, event)
|
||||||
elif event.button() == Qt.MouseButton.RightButton:
|
elif event.button() == Qt.MouseButton.RightButton:
|
||||||
@ -544,6 +549,21 @@ class ThumbnailGrid(QScrollArea):
|
|||||||
self._thumbs[self._selected_index].set_selected(False)
|
self._thumbs[self._selected_index].set_selected(False)
|
||||||
self._selected_index = -1
|
self._selected_index = -1
|
||||||
|
|
||||||
|
def _clear_stale_rubber_band(self) -> None:
|
||||||
|
"""Reset any leftover rubber band state before starting a new interaction.
|
||||||
|
|
||||||
|
Rubber band state can get stuck if a drag is interrupted without
|
||||||
|
a matching release event — Wayland focus steal, drag outside the
|
||||||
|
window, tab switch mid-drag, etc. Every new mouse press calls this
|
||||||
|
so the next interaction starts from a clean slate instead of
|
||||||
|
reusing a stale origin (which would make the rubber band "not
|
||||||
|
work" until the app is restarted).
|
||||||
|
"""
|
||||||
|
if self._rubber_band is not None:
|
||||||
|
self._rubber_band.hide()
|
||||||
|
self._rb_origin = None
|
||||||
|
self._rb_pending_origin = None
|
||||||
|
|
||||||
def _select(self, index: int) -> None:
|
def _select(self, index: int) -> None:
|
||||||
if index < 0 or index >= len(self._thumbs):
|
if index < 0 or index >= len(self._thumbs):
|
||||||
return
|
return
|
||||||
@ -617,12 +637,14 @@ class ThumbnailGrid(QScrollArea):
|
|||||||
|
|
||||||
def on_padding_click(self, thumb, local_pos) -> None:
|
def on_padding_click(self, thumb, local_pos) -> None:
|
||||||
"""Called directly by ThumbnailWidget when a click misses the pixmap."""
|
"""Called directly by ThumbnailWidget when a click misses the pixmap."""
|
||||||
|
self._clear_stale_rubber_band()
|
||||||
vp_pos = thumb.mapTo(self.viewport(), local_pos)
|
vp_pos = thumb.mapTo(self.viewport(), local_pos)
|
||||||
self._rb_pending_origin = vp_pos
|
self._rb_pending_origin = vp_pos
|
||||||
|
|
||||||
def mousePressEvent(self, event: QMouseEvent) -> None:
|
def mousePressEvent(self, event: QMouseEvent) -> None:
|
||||||
# Clicks on viewport/flow (gaps, space below thumbs) start rubber band
|
# Clicks on viewport/flow (gaps, space below thumbs) start rubber band
|
||||||
if event.button() == Qt.MouseButton.LeftButton:
|
if event.button() == Qt.MouseButton.LeftButton:
|
||||||
|
self._clear_stale_rubber_band()
|
||||||
child = self.childAt(event.position().toPoint())
|
child = self.childAt(event.position().toPoint())
|
||||||
if child is self.widget() or child is self.viewport():
|
if child is self.widget() or child is self.viewport():
|
||||||
self._rb_pending_origin = event.position().toPoint()
|
self._rb_pending_origin = event.position().toPoint()
|
||||||
@ -635,11 +657,15 @@ class ThumbnailGrid(QScrollArea):
|
|||||||
return
|
return
|
||||||
rb_rect = QRect(self._rb_origin, vp_pos).normalized()
|
rb_rect = QRect(self._rb_origin, vp_pos).normalized()
|
||||||
self._rubber_band.setGeometry(rb_rect)
|
self._rubber_band.setGeometry(rb_rect)
|
||||||
|
# rb_rect is in viewport coords; thumb.geometry() is in widget (content)
|
||||||
|
# coords. Convert rb_rect to widget coords for the intersection test —
|
||||||
|
# widget.mapFrom(viewport, (0,0)) gives the widget-coord of viewport's
|
||||||
|
# origin, which is exactly the translation needed when scrolled.
|
||||||
vp_offset = self.widget().mapFrom(self.viewport(), QPoint(0, 0))
|
vp_offset = self.widget().mapFrom(self.viewport(), QPoint(0, 0))
|
||||||
|
rb_widget = rb_rect.translated(vp_offset)
|
||||||
self._clear_multi()
|
self._clear_multi()
|
||||||
for i, thumb in enumerate(self._thumbs):
|
for i, thumb in enumerate(self._thumbs):
|
||||||
thumb_rect = thumb.geometry().translated(vp_offset)
|
if rb_widget.intersects(thumb.geometry()):
|
||||||
if rb_rect.intersects(thumb_rect):
|
|
||||||
self._multi_selected.add(i)
|
self._multi_selected.add(i)
|
||||||
thumb.set_multi_selected(True)
|
thumb.set_multi_selected(True)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user