mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Screen processes mouse move events after forwarding them to the child widget.
Signed-off-by: Michael Seifert <m.seifert@digitalernachschub.de>
This commit is contained in:
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Fixed a crash when a `SelectionList` had a prompt wider than itself https://github.com/Textualize/textual/issues/2900
|
- Fixed a crash when a `SelectionList` had a prompt wider than itself https://github.com/Textualize/textual/issues/2900
|
||||||
- Fixed a bug where `Click` events were bubbling up from `Switch` widgets https://github.com/Textualize/textual/issues/2366
|
- Fixed a bug where `Click` events were bubbling up from `Switch` widgets https://github.com/Textualize/textual/issues/2366
|
||||||
- Fixed a crash when using empty CSS variables https://github.com/Textualize/textual/issues/1849
|
- Fixed a crash when using empty CSS variables https://github.com/Textualize/textual/issues/1849
|
||||||
|
- `MouseMove` events bubble up from widgets. `App` and `Screen` receive `MouseMove` events even if there's no Widget under the cursor. https://github.com/Textualize/textual/issues/2905
|
||||||
|
|
||||||
## [0.30.0] - 2023-07-17
|
## [0.30.0] - 2023-07-17
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The `MouseMove` event is sent to a widget when the mouse pointer is moved over a widget.
|
The `MouseMove` event is sent to a widget when the mouse pointer is moved over a widget.
|
||||||
|
|
||||||
- [ ] Bubbles
|
- [x] Bubbles
|
||||||
- [x] Verbose
|
- [x] Verbose
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|||||||
@@ -435,10 +435,10 @@ class MouseEvent(InputEvent, bubble=True):
|
|||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
class MouseMove(MouseEvent, bubble=False, verbose=True):
|
class MouseMove(MouseEvent, bubble=True, verbose=True):
|
||||||
"""Sent when the mouse cursor moves.
|
"""Sent when the mouse cursor moves.
|
||||||
|
|
||||||
- [ ] Bubbles
|
- [X] Bubbles
|
||||||
- [X] Verbose
|
- [X] Verbose
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@@ -818,22 +818,13 @@ class Screen(Generic[ScreenResultType], Widget):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
self.app._set_mouse_over(widget)
|
self.app._set_mouse_over(widget)
|
||||||
mouse_event = events.MouseMove(
|
|
||||||
event.x - region.x,
|
|
||||||
event.y - region.y,
|
|
||||||
event.delta_x,
|
|
||||||
event.delta_y,
|
|
||||||
event.button,
|
|
||||||
event.shift,
|
|
||||||
event.meta,
|
|
||||||
event.ctrl,
|
|
||||||
screen_x=event.screen_x,
|
|
||||||
screen_y=event.screen_y,
|
|
||||||
style=event.style,
|
|
||||||
)
|
|
||||||
widget.hover_style = event.style
|
widget.hover_style = event.style
|
||||||
mouse_event._set_forwarded()
|
if widget is self:
|
||||||
widget._forward_event(mouse_event)
|
self.post_message(event)
|
||||||
|
else:
|
||||||
|
mouse_event = self._translate_mouse_move_event(event, region)
|
||||||
|
mouse_event._set_forwarded()
|
||||||
|
widget._forward_event(mouse_event)
|
||||||
|
|
||||||
if not self.app._disable_tooltips:
|
if not self.app._disable_tooltips:
|
||||||
try:
|
try:
|
||||||
@@ -856,6 +847,28 @@ class Screen(Generic[ScreenResultType], Widget):
|
|||||||
else:
|
else:
|
||||||
tooltip.display = False
|
tooltip.display = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _translate_mouse_move_event(
|
||||||
|
event: events.MouseMove, region: Region
|
||||||
|
) -> events.MouseMove:
|
||||||
|
"""
|
||||||
|
Returns a mouse move event whose relative coordinates are translated to
|
||||||
|
the origin of the specified region.
|
||||||
|
"""
|
||||||
|
return events.MouseMove(
|
||||||
|
event.x - region.x,
|
||||||
|
event.y - region.y,
|
||||||
|
event.delta_x,
|
||||||
|
event.delta_y,
|
||||||
|
event.button,
|
||||||
|
event.shift,
|
||||||
|
event.meta,
|
||||||
|
event.ctrl,
|
||||||
|
screen_x=event.screen_x,
|
||||||
|
screen_y=event.screen_y,
|
||||||
|
style=event.style,
|
||||||
|
)
|
||||||
|
|
||||||
def _forward_event(self, event: events.Event) -> None:
|
def _forward_event(self, event: events.Event) -> None:
|
||||||
if event.is_forwarded:
|
if event.is_forwarded:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import threading
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from textual.app import App, ScreenStackError
|
from textual.app import App, ScreenStackError, ComposeResult
|
||||||
|
from textual.events import MouseMove
|
||||||
|
from textual.geometry import Offset
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Button, Input, Label
|
from textual.widgets import Button, Input, Label
|
||||||
|
|
||||||
@@ -350,3 +352,59 @@ async def test_switch_screen_updates_results_callback_stack():
|
|||||||
app.switch_screen("b")
|
app.switch_screen("b")
|
||||||
assert len(app.screen._result_callbacks) == 1
|
assert len(app.screen._result_callbacks) == 1
|
||||||
assert app.screen._result_callbacks[-1].callback is None
|
assert app.screen._result_callbacks[-1].callback is None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_screen_receives_mouse_move_events():
|
||||||
|
class MouseMoveRecordingScreen(Screen):
|
||||||
|
mouse_events = []
|
||||||
|
|
||||||
|
def on_mouse_move(self, event: MouseMove) -> None:
|
||||||
|
MouseMoveRecordingScreen.mouse_events.append(event)
|
||||||
|
|
||||||
|
class SimpleApp(App[None]):
|
||||||
|
SCREENS = {"a": MouseMoveRecordingScreen()}
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.push_screen("a")
|
||||||
|
|
||||||
|
mouse_offset = Offset(1, 1)
|
||||||
|
|
||||||
|
async with SimpleApp().run_test() as pilot:
|
||||||
|
await pilot.hover(None, mouse_offset)
|
||||||
|
|
||||||
|
assert len(MouseMoveRecordingScreen.mouse_events) == 1
|
||||||
|
mouse_event = MouseMoveRecordingScreen.mouse_events[0]
|
||||||
|
assert mouse_event.x, mouse_event.y == mouse_offset
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mouse_move_event_bubbles_to_screen_from_widget():
|
||||||
|
class MouseMoveRecordingScreen(Screen):
|
||||||
|
mouse_events = []
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
Label {
|
||||||
|
offset: 10 10;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Label("Any label")
|
||||||
|
|
||||||
|
def on_mouse_move(self, event: MouseMove) -> None:
|
||||||
|
MouseMoveRecordingScreen.mouse_events.append(event)
|
||||||
|
|
||||||
|
class SimpleApp(App[None]):
|
||||||
|
SCREENS = {"a": MouseMoveRecordingScreen()}
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.push_screen("a")
|
||||||
|
|
||||||
|
label_offset = Offset(10, 10)
|
||||||
|
mouse_offset = Offset(1, 1)
|
||||||
|
|
||||||
|
async with SimpleApp().run_test() as pilot:
|
||||||
|
await pilot.hover(Label, mouse_offset)
|
||||||
|
|
||||||
|
assert len(MouseMoveRecordingScreen.mouse_events) == 1
|
||||||
|
mouse_event = MouseMoveRecordingScreen.mouse_events[0]
|
||||||
|
assert mouse_event.x, mouse_event.y == (label_offset.x + mouse_offset.x, label_offset.y + mouse_offset.y)
|
||||||
|
|||||||
Reference in New Issue
Block a user