Skip to content

Commit

Permalink
fix threading issue (#3779)
Browse files Browse the repository at this point in the history
* fix threading issue

* remote debug

* changelog

* version bump

* changelog

* docstring

* fix snapshot test
  • Loading branch information
willmcgugan authored Nov 29, 2023
1 parent eed7a94 commit 1a76b62
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 80 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.42.2] - 2023-11-29

### Fixed

- Fixed NoWidget error https://github.com/Textualize/textual/pull/3779

## [0.43.1] - 2023-11-29

### Fixed
Expand Down Expand Up @@ -1465,6 +1471,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
- New handler system for messages that doesn't require inheritance
- Improved traceback handling

[0.43.2]: https://github.com/Textualize/textual/compare/v0.43.1...v0.43.2
[0.43.1]: https://github.com/Textualize/textual/compare/v0.43.0...v0.43.1
[0.43.0]: https://github.com/Textualize/textual/compare/v0.42.0...v0.43.0
[0.42.0]: https://github.com/Textualize/textual/compare/v0.41.0...v0.42.0
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "textual"
version = "0.43.1"
version = "0.43.2"
homepage = "https://github.com/Textualize/textual"
repository = "https://github.com/Textualize/textual"
documentation = "https://textual.textualize.io/"
Expand Down
25 changes: 24 additions & 1 deletion src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
from .dom import DOMNode
from .driver import Driver
from .drivers.headless_driver import HeadlessDriver
from .errors import NoWidget
from .features import FeatureFlag, parse_features
from .file_monitor import FileMonitor
from .geometry import Offset, Region, Size
Expand Down Expand Up @@ -444,6 +445,9 @@ def __init__(
self._animate = self._animator.bind(self)
self.mouse_position = Offset(0, 0)

self._mouse_down_widget: Widget | None = None
"""The widget that was most recently mouse downed (used to create click events)."""

self.cursor_position = Offset(0, 0)
"""The position of the terminal cursor in screen-space.
Expand Down Expand Up @@ -2671,7 +2675,7 @@ async def on_event(self, event: events.Event) -> None:
# Handle input events that haven't been forwarded
# If the event has been forwarded it may have bubbled up back to the App
if isinstance(event, events.Compose):
screen = Screen(id=f"_default")
screen: Screen[Any] = Screen(id=f"_default")
self._register(self, screen)
self._screen_stack.append(screen)
screen.post_message(events.ScreenResume())
Expand All @@ -2683,7 +2687,26 @@ async def on_event(self, event: events.Event) -> None:
if isinstance(event, events.MouseEvent):
# Record current mouse position on App
self.mouse_position = Offset(event.x, event.y)

if isinstance(event, events.MouseDown):
try:
self._mouse_down_widget, _ = self.get_widget_at(
event.x, event.y
)
except NoWidget:
# Shouldn't occur, since at the very least this will find the Screen
self._mouse_down_widget = None

self.screen._forward_event(event)

if isinstance(event, events.MouseUp):
if self._mouse_down_widget is not None and (
self.get_widget_at(event.x, event.y)[0]
is self._mouse_down_widget
):
click_event = events.Click.from_event(event)
self.screen._forward_event(click_event)

elif isinstance(event, events.Key):
if not await self.check_bindings(event.key, priority=True):
forward_target = self.focused or self.screen
Expand Down
19 changes: 4 additions & 15 deletions src/textual/driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

if TYPE_CHECKING:
from .app import App
from .widget import Widget


class Driver(ABC):
Expand All @@ -33,7 +32,6 @@ def __init__(
self._debug = debug
self._size = size
self._loop = asyncio.get_running_loop()
self._mouse_down_widget: Widget | None = None
self._down_buttons: list[int] = []
self._last_move_event: events.MouseMove | None = None

Expand All @@ -58,17 +56,15 @@ def process_event(self, event: events.Event) -> None:
Args:
event: An event to send.
"""
# NOTE: This runs in a thread.
# Avoid calling methods on the app.
event._set_sender(self._app)
if isinstance(event, events.MouseDown):
self._mouse_down_widget = self._app.get_widget_at(event.x, event.y)[0]
if event.button:
self._down_buttons.append(event.button)
elif isinstance(event, events.MouseUp):
if event.button:
try:
self._down_buttons.remove(event.button)
except ValueError:
pass
if event.button and event.button in self._down_buttons:
self._down_buttons.remove(event.button)
elif isinstance(event, events.MouseMove):
if (
self._down_buttons
Expand Down Expand Up @@ -99,13 +95,6 @@ def process_event(self, event: events.Event) -> None:

self.send_event(event)

if (
isinstance(event, events.MouseUp)
and self._app.get_widget_at(event.x, event.y)[0] is self._mouse_down_widget
):
click_event = events.Click.from_event(event)
self.send_event(click_event)

@abstractmethod
def write(self, data: str) -> None:
"""Write data to the output device.
Expand Down
6 changes: 3 additions & 3 deletions src/textual/pilot.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,12 @@ async def _post_mouse_events(
# Get the widget under the mouse before the event because the app might
# react to the event and move things around. We override on each iteration
# because we assume the final event in `events` is the actual event we care
# about and that all the preceeding events are just setup.
# E.g., the click event is preceeded by MouseDown/MouseUp to emulate how
# about and that all the preceding events are just setup.
# E.g., the click event is preceded by MouseDown/MouseUp to emulate how
# the driver works and emits a click event.
widget_at, _ = app.get_widget_at(*offset)
event = mouse_event_cls(**message_arguments)
app.post_message(event)
app.screen._forward_event(event)
await self.pause()

return selector is None or widget_at is target_widget
Expand Down
120 changes: 60 additions & 60 deletions tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Large diffs are not rendered by default.

0 comments on commit 1a76b62

Please sign in to comment.