Support MouseScrollLeft and MouseScrollRight control sequences.

This commit is contained in:
fancidev
2025-07-27 18:51:48 +08:00
parent e3bae00713
commit 7fdc1e51d9
4 changed files with 94 additions and 4 deletions

View File

@@ -100,9 +100,12 @@ class XTermParser(Parser[Message]):
event_class: type[events.MouseEvent] event_class: type[events.MouseEvent]
if buttons & 64: if buttons & 64:
event_class = ( event_class = [
events.MouseScrollDown if buttons & 1 else events.MouseScrollUp events.MouseScrollUp,
) events.MouseScrollDown,
events.MouseScrollLeft,
events.MouseScrollRight,
][buttons & 3]
button = 0 button = 0
else: else:
button = (buttons + 1) & 3 button = (buttons + 1) & 3

View File

@@ -606,6 +606,24 @@ class MouseScrollUp(MouseEvent, bubble=True, verbose=True):
""" """
@rich.repr.auto
class MouseScrollRight(MouseEvent, bubble=True, verbose=True):
"""Sent when the mouse wheel is scrolled *right*.
- [X] Bubbles
- [X] Verbose
"""
@rich.repr.auto
class MouseScrollLeft(MouseEvent, bubble=True, verbose=True):
"""Sent when the mouse wheel is scrolled *left*.
- [X] Bubbles
- [X] Verbose
"""
class Click(MouseEvent, bubble=True): class Click(MouseEvent, bubble=True):
"""Sent when a widget is clicked. """Sent when a widget is clicked.

View File

@@ -113,7 +113,12 @@ _JUSTIFY_MAP: dict[str, JustifyMethod] = {
_MOUSE_EVENTS_DISALLOW_IF_DISABLED = (events.MouseEvent, events.Enter, events.Leave) _MOUSE_EVENTS_DISALLOW_IF_DISABLED = (events.MouseEvent, events.Enter, events.Leave)
_MOUSE_EVENTS_ALLOW_IF_DISABLED = (events.MouseScrollDown, events.MouseScrollUp) _MOUSE_EVENTS_ALLOW_IF_DISABLED = (
events.MouseScrollDown,
events.MouseScrollUp,
events.MouseScrollRight,
events.MouseScrollLeft,
)
@rich.repr.auto @rich.repr.auto
@@ -4569,6 +4574,18 @@ class Widget(DOMNode):
if self._scroll_up_for_pointer(animate=False): if self._scroll_up_for_pointer(animate=False):
event.stop() event.stop()
def _on_mouse_scroll_right(self, event: events.MouseScrollRight) -> None:
if self.allow_horizontal_scroll:
self.release_anchor()
if self._scroll_right_for_pointer(animate=False):
event.stop()
def _on_mouse_scroll_left(self, event: events.MouseScrollLeft) -> None:
if self.allow_horizontal_scroll:
self.release_anchor()
if self._scroll_left_for_pointer(animate=False):
event.stop()
def _on_scroll_to(self, message: ScrollTo) -> None: def _on_scroll_to(self, message: ScrollTo) -> None:
if self._allow_scroll: if self._allow_scroll:
self.release_anchor() self.release_anchor()

View File

@@ -8,6 +8,8 @@ from textual.events import (
MouseDown, MouseDown,
MouseMove, MouseMove,
MouseScrollDown, MouseScrollDown,
MouseScrollLeft,
MouseScrollRight,
MouseScrollUp, MouseScrollUp,
MouseUp, MouseUp,
Paste, Paste,
@@ -283,6 +285,56 @@ def test_mouse_scroll_down(parser, sequence, shift, meta):
assert event.meta is meta assert event.meta is meta
@pytest.mark.parametrize(
"sequence, shift, meta",
[
("\x1b[<66;18;25M", False, False),
("\x1b[<70;18;25M", True, False),
("\x1b[<74;18;25M", False, True),
],
)
def test_mouse_scroll_left(parser, sequence, shift, meta):
"""Scrolling the mouse with and without modifiers held down.
We don't currently capture modifier keys in scroll events.
"""
events = list(parser.feed(sequence))
assert len(events) == 1
event = events[0]
assert isinstance(event, MouseScrollLeft)
assert event.x == 17
assert event.y == 24
assert event.shift is shift
assert event.meta is meta
@pytest.mark.parametrize(
"sequence, shift, meta",
[
("\x1b[<67;18;25M", False, False),
("\x1b[<71;18;25M", True, False),
("\x1b[<75;18;25M", False, True),
],
)
def test_mouse_scroll_right(parser, sequence, shift, meta):
"""Scrolling the mouse with and without modifiers held down.
We don't currently capture modifier keys in scroll events.
"""
events = list(parser.feed(sequence))
assert len(events) == 1
event = events[0]
assert isinstance(event, MouseScrollRight)
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): def test_mouse_event_detected_but_info_not_parsed(parser):
# I don't know if this can actually happen in reality, but # I don't know if this can actually happen in reality, but
# there's a branch in the code that allows for the possibility. # there's a branch in the code that allows for the possibility.