diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ee7b9a67..876edeb4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.10.0] - Unreleased + +### Changed + +- `MouseScrollUp` and `MouseScrollDown` now inherit from `MouseEvent` and have attached modifier keys. https://github.com/Textualize/textual/pull/1458 + ## [0.9.1] - 2022-12-30 ### Added diff --git a/docs/tutorial.md b/docs/tutorial.md index e618be668..7d37eea58 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -365,7 +365,7 @@ The first argument to `reactive` may be a default value or a callable that retur The `time` attribute has a simple float as the default value, so `self.time` will be `0` on start. -The `on_mount` method is an event handler called then the widget is first added to the application (or _mounted_). In this method we call [set_interval()][textual.message_pump.MessagePump.set_interval] to create a timer which calls `self.update_time` sixty times a second. This `update_time` method calculates the time elapsed since the widget started and assigns it to `self.time`. Which brings us to one of Reactive's super-powers. +The `on_mount` method is an event handler called when the widget is first added to the application (or _mounted_). In this method we call [set_interval()][textual.message_pump.MessagePump.set_interval] to create a timer which calls `self.update_time` sixty times a second. This `update_time` method calculates the time elapsed since the widget started and assigns it to `self.time`. Which brings us to one of Reactive's super-powers. If you implement a method that begins with `watch_` followed by the name of a reactive attribute (making it a _watch method_), that method will be called when the attribute is modified. diff --git a/src/textual/_xterm_parser.py b/src/textual/_xterm_parser.py index 40af686a4..ab760e242 100644 --- a/src/textual/_xterm_parser.py +++ b/src/textual/_xterm_parser.py @@ -54,36 +54,40 @@ class XTermParser(Parser[events.Event]): if sgr_match: _buttons, _x, _y, state = sgr_match.groups() buttons = int(_buttons) - button = (buttons + 1) & 3 x = int(_x) - 1 y = int(_y) - 1 delta_x = x - self.last_x delta_y = y - self.last_y self.last_x = x self.last_y = y - event: events.Event + event_class: type[events.MouseEvent] + if buttons & 64: - event = ( - events.MouseScrollUp if button == 1 else events.MouseScrollDown - )(sender, x, y) - else: - event = ( - events.MouseMove - if buttons & 32 - else (events.MouseDown if state == "M" else events.MouseUp) - )( - sender, - x, - y, - delta_x, - delta_y, - button, - bool(buttons & 4), - bool(buttons & 8), - bool(buttons & 16), - screen_x=x, - screen_y=y, + event_class = ( + events.MouseScrollDown if buttons & 1 else events.MouseScrollUp ) + button = 0 + else: + if buttons & 32: + event_class = events.MouseMove + else: + event_class = events.MouseDown if state == "M" else events.MouseUp + + button = (buttons + 1) & 3 + + event = event_class( + sender, + x, + y, + delta_x, + delta_y, + button, + bool(buttons & 4), + bool(buttons & 8), + bool(buttons & 16), + screen_x=x, + screen_y=y, + ) return event return None diff --git a/src/textual/events.py b/src/textual/events.py index 1f509a072..e99007e21 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -419,22 +419,14 @@ class MouseUp(MouseEvent, bubble=True, verbose=True): pass -class MouseScrollDown(InputEvent, bubble=True, verbose=True): - __slots__ = ["x", "y"] - - def __init__(self, sender: MessageTarget, x: int, y: int) -> None: - super().__init__(sender) - self.x = x - self.y = y +@rich.repr.auto +class MouseScrollDown(MouseEvent, bubble=True): + pass -class MouseScrollUp(InputEvent, bubble=True, verbose=True): - __slots__ = ["x", "y"] - - def __init__(self, sender: MessageTarget, x: int, y: int) -> None: - super().__init__(sender) - self.x = x - self.y = y +@rich.repr.auto +class MouseScrollUp(MouseEvent, bubble=True): + pass class Click(MouseEvent, bubble=True): diff --git a/tests/test_xterm_parser.py b/tests/test_xterm_parser.py index 6e352d87d..1b426267b 100644 --- a/tests/test_xterm_parser.py +++ b/tests/test_xterm_parser.py @@ -236,14 +236,14 @@ def test_mouse_move(parser, sequence, shift, meta, button): @pytest.mark.parametrize( - "sequence", + "sequence, shift, meta", [ - "\x1b[<64;18;25M", - "\x1b[<68;18;25M", - "\x1b[<72;18;25M", + ("\x1b[<64;18;25M", False, False), + ("\x1b[<68;18;25M", True, False), + ("\x1b[<72;18;25M", False, True), ], ) -def test_mouse_scroll_up(parser, sequence): +def test_mouse_scroll_up(parser, sequence, shift, meta): """Scrolling the mouse with and without modifiers held down. We don't currently capture modifier keys in scroll events. """ @@ -256,17 +256,19 @@ def test_mouse_scroll_up(parser, sequence): assert isinstance(event, MouseScrollUp) assert event.x == 17 assert event.y == 24 + assert event.shift is shift + assert event.meta is meta @pytest.mark.parametrize( - "sequence", + "sequence, shift, meta", [ - "\x1b[<65;18;25M", - "\x1b[<69;18;25M", - "\x1b[<73;18;25M", + ("\x1b[<65;18;25M", False, False), + ("\x1b[<69;18;25M", True, False), + ("\x1b[<73;18;25M", False, True), ], ) -def test_mouse_scroll_down(parser, sequence): +def test_mouse_scroll_down(parser, sequence, shift, meta): events = list(parser.feed(sequence)) assert len(events) == 1 @@ -276,6 +278,8 @@ def test_mouse_scroll_down(parser, sequence): assert isinstance(event, MouseScrollDown) assert event.x == 17 assert event.y == 24 + assert event.shift is shift + assert event.meta is meta def test_mouse_event_detected_but_info_not_parsed(parser):