Skip to content
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

fix threading issue #3779

Merged
merged 7 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Loading