Textual MouseScrollRight 事件详解:捕获终端横向滚轮滚动
【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual
导读
MouseScrollRight是 Textual 事件体系中的一员,专门用于响应鼠标滚轮(或触控板手势)在终端中向右滚动的事件。本文以 docs/events/mouse_scroll_right.md 为骨架,深入 Textual 源码与测试,完整讲解该事件的定义、触发时机、属性与方法、在 Widget 内的默认行为(横向滚动),以及如何编写自定义处理器实现专属交互逻辑。
事件定义与继承关系
MouseScrollRight定义于 src/textual/events.py:
@rich.repr.auto class MouseScrollRight(MouseEvent, bubble=True, verbose=True): """Sent when the mouse wheel is scrolled *right*. - [X] Bubbles - [X] Verbose """从源码可以看出该事件的两个关键特征:
- Bubbles(冒泡):事件会从产生它的控件向父控件逐级冒泡,父级可以统一监听或拦截(通过调用
event.stop()); - Verbose(冗长日志):与
Click等非 Verbose 事件不同,滚动事件属于高频事件,Textual 会将其记录在冗长日志(verbose log)中,方便调试滚动相关的交互问题,但生产环境日志中不会默认输出这些高频噪声。
它与同族的 MouseScrollUp、MouseScrollDown、MouseScrollLeft 一样,都直接继承自MouseEvent(见 src/textual/events.py),本身不新增任何字段,全部属性和方法均来自父类。
注意:原文档明确指出——完整的属性与方法列表见
MouseEvent。因此理解MouseScrollRight的关键,在于吃透MouseEvent提供的坐标系、修饰键与滚动增量等信息。
MouseEvent 提供的属性与坐标系
MouseScrollRight继承自MouseEvent(定义于 src/textual/events.py),事件携带以下核心信息:
坐标类属性
| 属性 | 类型 | 含义 |
|---|---|---|
x/y | int | 鼠标所在的相对单元格坐标(相对接收事件的控件) |
pointer_x/pointer_y | float | 鼠标所在的相对像素级(浮点)坐标 |
screen_x/screen_y | int | 鼠标所在的屏幕绝对坐标(相对屏幕左上角) |
pointer_screen_x/pointer_screen_y | float | 屏幕绝对坐标的浮点版本 |
offset | Offset | (x, y)组合成的相对偏移量 |
screen_offset | Offset | (screen_x, screen_y)组合成的屏幕偏移量 |
滚动增量类属性
| 属性 | 类型 | 含义 |
|---|---|---|
delta_x | int | 自上次鼠标消息以来 x 方向的变化量 |
delta_y | int | 自上次鼠标消息以来 y 方向的变化量 |
delta | Offset | (delta_x, delta_y)组合成的增量偏移量 |
修饰键与按钮
| 属性 | 类型 | 含义 |
|---|---|---|
shift | bool | Shift 键是否按下 |
meta | bool | Meta 键是否按下 |
ctrl | bool | Ctrl 键是否按下 |
button | int | 被按下的按键索引 |
style | Style | 鼠标下方光标位置的 Rich Style |
实用方法
get_content_offset(widget):返回鼠标在指定控件内容区内的偏移量(若落在 padding 或 border 区域则返回None);get_content_offset_capture(widget):即使鼠标不在内容区内,也返回相对内容区左上角的偏移量;_apply_offset(x, y):在事件坐标基础上平移生成新事件,用于消息沿 DOM 树向下转发时校正坐标系。
这些坐标均为单元格(cell)整数坐标,因为终端渲染以字符单元为最小单位;delta_x/delta_y由XTermParser依据上一次鼠标位置实时计算(见下文触发链路)。
事件的产生链路:从 ANSI 序列到 MouseScrollRight
MouseScrollRight并非凭空产生,而是由终端的 SGR 鼠标协议(\x1b[<...M/m序列)解析而来。核心解析逻辑位于 src/textual/_xterm_parser.py:
if buttons & 64: event_class = [ events.MouseScrollUp, events.MouseScrollDown, events.MouseScrollLeft, events.MouseScrollRight, ][buttons & 3] button = 0这段代码揭示了事件类型的分派规则:SGR 序列中的按钮码与64做按位与,若结果非零,则说明这是滚动事件;随后用buttons & 3取低 2 位作为索引,映射到四个滚动方向,其中索引3即对应MouseScrollRight。
解析器还会维护上一次的鼠标坐标(last_x/last_y),据此计算:
delta_x = int(x) - int(self.last_x) delta_y = int(y) - int(self.last_y)并提取修饰键状态:
bool(buttons & 4), # shift bool(buttons & 8), # meta bool(buttons & 16), # ctrl测试用例印证
仓库测试 tests/test_xterm_parser.py 对右侧滚动事件的解析做了参数化验证:
@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): 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可以看到,序列\x1b[<67;18;25M(按钮码 67 = 64 + 3)被正确解析为一个MouseScrollRight事件,坐标为(17, 24)(序列中坐标从 1 开始计数,源码中减 1 转为 0 基坐标),且不带修饰键;按钮码 71、75 则分别验证了 Shift、Meta 键状态位的解析。
Widget 中的默认行为:驱动横向滚动
Textual 的Widget基类为滚动事件提供了开箱即用的默认处理。在 src/textual/widget.py 中:
def _on_mouse_scroll_right(self, event: events.MouseScrollRight) -> None: if self.allow_horizontal_scroll: if self._scroll_right_for_pointer(): event.stop() def _on_mouse_scroll_left(self, event: events.MouseScrollLeft) -> None: if self.allow_horizontal_scroll: if self._scroll_left_for_pointer(): event.stop()这意味着:
- 当鼠标在支持水平滚动的控件(
allow_horizontal_scroll为真)上向右滚动时,Textual 会自动调用_scroll_right_for_pointer()执行滚动; - 滚动成功(返回
True)后调用event.stop()停止冒泡,避免父级重复处理; - 因此,如果你的控件本身具备水平滚动能力(如设置了
overflow-x: auto),无需编写任何代码即可获得横向滚轮支持。
如何监听与自定义处理
由于事件具有冒泡特性,你可以用两种方式捕获MouseScrollRight。
方式一:命名约定法
按照 Textual 的命名约定,为事件添加on_前缀、将类名转为蛇形命名即可自动关联处理器:
from textual.app import App, ComposeResult from textual.widgets import Static class ScrollWatcher(Static): def on_mouse_scroll_right(self, event: events.MouseScrollRight) -> None: # 事件默认已由基类用于横向滚动,若想自定义,可在此覆盖 self.log(f"Scrolled right at ({event.screen_x}, {event.screen_y})") event.stop() # 停止冒泡方式二:on装饰器法
Textual 的 [on][textual.on] 装饰器(用法见 docs/guide/events.md)允许为事件绑定任意命名的方法,且可配合 CSS 选择器精确指定要监听的控件:
from textual import on from textual.events import MouseScrollRight class MyApp(App): @on(MouseScrollRight, "#content") def handle_scroll(self, event: MouseScrollRight) -> None: self.notify(f"Right scroll at x={event.delta_x}")提示:
on装饰器要求消息类具备control属性,MouseEvent的control属性即返回鼠标下的控件(见 src/textual/events.py),因此可用于选择器匹配。
与 Shift/Ctrl 组合
Textual 的默认行为中,垂直滚轮配合shift/ctrl也会被转换为横向滚动(见 src/textual/widget.py 中_on_mouse_scroll_down/_on_mouse_scroll_up的event.ctrl or event.shift分支)。因此在实际终端中,即使硬件没有横向滚轮,也可以用「Shift + 垂直滚轮」触发等价的横向滚动效果。
调试与日志
由于MouseScrollRight标记为verbose=True,当你在终端按Ctrl+E打开 Textual Devtools 的日志,或将日志级别调至 verbose 时,滚动事件会以类似以下形式输出(来自__rich_repr__,见 src/textual/events.py):
MouseScrollRight(None, x=17, y=24, delta_x=0, delta_y=0, button=0, shift=False, meta=False, ctrl=False)其中各字段只有在非默认值时才会展示,便于快速定位坐标与修饰键状态。
与其他鼠标事件的协同
MouseScrollRight通常与以下事件配合使用,构成完整的鼠标交互体系:
- MouseScrollLeft:向左滚动;
- MouseScrollUp / MouseScrollDown:垂直方向滚动;
- Click、MouseDown、MouseUp:点击类交互;
- MouseMove:指针移动;
- Enter / Leave:鼠标进入/离开控件区域。
设计自定义滚动控件(如横向走马灯、图表横轴缩放、水平菜单切换)时,可在处理器中读取event.delta_x作为步进量,或读取event.x/event.y判断滚动位置,实现精细的分步控制。
小结
MouseScrollRight继承自MouseEvent,具有冒泡与冗长日志特性,本身不新增字段;- 它的产生源于
XTermParser对 SGR 鼠标协议序列的解析,按钮码& 64判定滚动、& 3判定方向(3为右); Widget基类默认将其映射为水平右向滚动,可零代码使用;- 自定义处理可使用命名约定或
on装饰器,注意通过event.stop()控制冒泡。
相关源码与测试索引:
- 事件类定义:src/textual/events.py
- 父类
MouseEvent:src/textual/events.py - ANSI 序列解析:src/textual/_xterm_parser.py
- 默认滚动行为:src/textual/widget.py
- 解析测试:tests/test_xterm_parser.py
- 事件处理指南:docs/guide/events.md
【免费下载链接】textualThe lean application framework for Python. Build sophisticated user interfaces with a simple Python API. Run your apps in the terminal and a web browser.项目地址: https://gitcode.com/gh_mirrors/te/textual
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考