From f61a50b79007a56d8f156a44d1d1eab29a2732e6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 9 Mar 2023 14:13:12 +0000 Subject: [PATCH] prevent stuck scrollbar (#2003) * prevent stuck scrollbar * update changelog * remove debug * remove debug --- CHANGELOG.md | 2 +- src/textual/driver.py | 54 +++++++++++++++++++++++----------------- src/textual/scrollbar.py | 8 ++++-- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd56a3f0..6ee4642d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,7 +34,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed bug that prevented pilot from pressing some keys https://github.com/Textualize/textual/issues/1815 - DataTable race condition that caused crash https://github.com/Textualize/textual/pull/1962 -- Fixed scrollbar getting "stuck" to cursor when cursor leaves window during drag https://github.com/Textualize/textual/pull/1968 +- Fixed scrollbar getting "stuck" to cursor when cursor leaves window during drag https://github.com/Textualize/textual/pull/1968 https://github.com/Textualize/textual/pull/2003 - DataTable crash when enter pressed when table is empty https://github.com/Textualize/textual/pull/1973 ## [0.13.0] - 2023-03-02 diff --git a/src/textual/driver.py b/src/textual/driver.py index afe852bfd..0e8ac6412 100644 --- a/src/textual/driver.py +++ b/src/textual/driver.py @@ -27,8 +27,8 @@ class Driver(ABC): self._size = size self._loop = asyncio.get_running_loop() self._mouse_down_time = _clock.get_time_no_wait() - self._dragging = False - self._dragging_button = None + self._down_buttons: list[int] = [] + self._last_move_event: events.MouseMove | None = None @property def is_headless(self) -> bool: @@ -44,29 +44,37 @@ class Driver(ABC): """Performs some additional processing of events.""" if isinstance(event, events.MouseDown): self._mouse_down_time = event.time + if event.button: + self._down_buttons.append(event.button) + elif isinstance(event, events.MouseUp): + if event.button: + self._down_buttons.remove(event.button) elif isinstance(event, events.MouseMove): - if event.button and not self._dragging: - self._dragging = True - self._dragging_button = event.button - elif self._dragging and self._dragging_button != event.button: - # Artificially generate a MouseUp event when we stop "dragging" - self.send_event( - MouseUp( - x=event.x, - y=event.y, - delta_x=event.delta_x, - delta_y=event.delta_y, - button=self._dragging_button, - shift=event.shift, - meta=event.meta, - ctrl=event.ctrl, - screen_x=event.screen_x, - screen_y=event.screen_y, - style=event.style, + if ( + self._down_buttons + and not event.button + and self._last_move_event is not None + ): + buttons = list(dict.fromkeys(self._down_buttons).keys()) + self._down_buttons.clear() + move_event = self._last_move_event + for button in buttons: + self.send_event( + MouseUp( + x=move_event.x, + y=move_event.y, + delta_x=0, + delta_y=0, + button=button, + shift=event.shift, + meta=event.meta, + ctrl=event.ctrl, + screen_x=move_event.screen_x, + screen_y=move_event.screen_y, + style=event.style, + ) ) - ) - self._dragging = False - self._dragging_button = None + self._last_move_event = event self.send_event(event) diff --git a/src/textual/scrollbar.py b/src/textual/scrollbar.py index bc2ed6dd7..df5516ce9 100644 --- a/src/textual/scrollbar.py +++ b/src/textual/scrollbar.py @@ -291,6 +291,7 @@ class ScrollBar(Widget): def _on_hide(self, event: events.Hide) -> None: if self.grabbed: self.release_mouse() + self.grabbed = None def _on_enter(self, event: events.Enter) -> None: self.mouse_over = True @@ -299,10 +300,12 @@ class ScrollBar(Widget): self.mouse_over = False def action_scroll_down(self) -> None: - self.post_message(ScrollDown() if self.vertical else ScrollRight()) + if not self.grabbed: + self.post_message(ScrollDown() if self.vertical else ScrollRight()) def action_scroll_up(self) -> None: - self.post_message(ScrollUp() if self.vertical else ScrollLeft()) + if not self.grabbed: + self.post_message(ScrollUp() if self.vertical else ScrollLeft()) def action_grab(self) -> None: self.capture_mouse() @@ -313,6 +316,7 @@ class ScrollBar(Widget): async def _on_mouse_up(self, event: events.MouseUp) -> None: if self.grabbed: self.release_mouse() + self.grabbed = None event.stop() def _on_mouse_capture(self, event: events.MouseCapture) -> None: