Textual Rule 控件完全指南:用<hr>式的分隔线组织终端界面布局
【免费下载链接】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
本文围绕 Textual 框架内置的Rule控件展开,讲解如何用它像 HTML 的<hr>标签一样在终端界面中分隔内容区块,覆盖水平/垂直两种方向的全部线型、Reactive 属性、构造器与类方法、CSS 布局定制以及参数校验与源码实现细节。读完本文,你将能够在自己的 Textual 应用中熟练插入、定制和动态切换各种风格的分隔线。
Rule 是什么
Rule是 Textual 提供的一个分隔类(separator)控件,功能与 HTML 中的<hr>(水平线)标签类似,用来在视觉上把界面中的不同内容区块分隔开,增强布局的层次感与可读性。
在 Textual 官方 API 文档(docs/widgets/rule.md)中,Rule 的定位被明确描述为 "A rule widget to separate content, similar to a<hr>HTML tag"。
Rule 的两个关键特性:
- 不可聚焦(Focusable:否):Rule 不参与键盘焦点管理,用户无法通过 Tab 键聚焦到它;
- 非容器(Container:否):Rule 不能挂载子控件,它只是一个纯渲染的装饰性控件。
这两个特性使其非常轻量:它不发送任何消息(Messages)、没有绑定按键(Bindings)、也没有组件类(Component Classes),职责单一纯粹。
快速上手:在应用里放置一条分隔线
最简单的用法是在compose()中直接yield Rule(),然后app.run()启动:
from textual.app import App, ComposeResult from textual.widgets import Label, Rule class MyApp(App): def compose(self) -> ComposeResult: yield Label("上半部分内容") yield Rule() yield Label("下半部分内容") if __name__ == "__main__": MyApp().run()默认情况下,Rule()渲染为一条水平实线(line_style="solid"),并使用主题的$secondary颜色(见下文 DEFAULT_CSS 源码),这通常能很好地融入 Textual 自带的主题体系。
水平 Rule:默认方向与全部线型
Rule 的默认方向(orientation)是"horizontal"(水平)。水平方向下,Rule 会沿容器宽度方向延伸成一条横线。
仓库中的官方示例 docs/examples/widgets/horizontal_rules.py 一次性展示了所有可用的水平线型,配合标签标注每种线型的名称:
from textual.app import App, ComposeResult from textual.containers import Vertical from textual.widgets import Label, Rule class HorizontalRulesApp(App): CSS_PATH = "horizontal_rules.tcss" def compose(self) -> ComposeResult: with Vertical(): yield Label("solid (default)") yield Rule() yield Label("heavy") yield Rule(line_style="heavy") yield Label("thick") yield Rule(line_style="thick") yield Label("dashed") yield Rule(line_style="dashed") yield Label("double") yield Rule(line_style="double") yield Label("ascii") yield Rule(line_style="ascii") if __name__ == "__main__": app = HorizontalRulesApp() app.run()配套的样式文件 docs/examples/widgets/horizontal_rules.tcss 负责让示例居中并约束布局:
Screen { align: center middle; } Vertical { height: auto; width: 80%; } Label { width: 100%; text-align: center; }从示例可以看到,构造时只需通过line_style参数即可切换线型。line_style一共支持 9 种取值:ascii、blank、dashed、double、heavy、hidden、none、solid、thick。其中solid是默认值;blank、hidden、none三种在视觉上等效于空白,常用于"隐藏"分隔线但保留布局占位;ascii使用纯 ASCII 字符(-),适合对字符集有严格限制的环境。
垂直 Rule:侧边栏与列布局的分隔
将orientation设为"vertical",Rule 就会变成一条竖线,用于在左右分栏布局(如侧边栏、双列内容)中分隔列。
仓库示例 docs/examples/widgets/vertical_rules.py 展示了所有垂直线型:
from textual.app import App, ComposeResult from textual.containers import Horizontal from textual.widgets import Label, Rule class VerticalRulesApp(App): CSS_PATH = "vertical_rules.tcss" def compose(self) -> ComposeResult: with Horizontal(): yield Label("solid") yield Rule(orientation="vertical") yield Label("heavy") yield Rule(orientation="vertical", line_style="heavy") yield Label("thick") yield Rule(orientation="vertical", line_style="thick") yield Label("dashed") yield Rule(orientation="vertical", line_style="dashed") yield Label("double") yield Rule(orientation="vertical", line_style="double") yield Label("ascii") yield Rule(orientation="vertical", line_style="ascii") if __name__ == "__main__": app = VerticalRulesApp() app.run()配套样式 docs/examples/widgets/vertical_rules.tcss 中,Horizontal容器设定为固定高度比例、标签限定宽度并垂直居中文本,从而让竖线与标签在垂直方向完整伸展:
Screen { align: center middle; } Horizontal { width: auto; height: 80%; } Label { width: 6; height: 100%; text-align: center; }要点:垂直 Rule 需要所在的容器有确定的可用高度,它才会伸展填满。若容器高度是auto,竖线可能没有足够的长度可渲染。
Reactive 属性:orientation 与 line_style
Rule 只有两个 Reactive 属性,官方文档的属性表如下(docs/widgets/rule.md):
| 名称 | 类型 | 默认值 | 描述 |
|---|---|---|---|
orientation | RuleOrientation | "horizontal" | 规则的方向(横/竖)。 |
line_style | LineStyle | "solid" | 规则的线型。 |
在源码 src/textual/widgets/_rule.py 中,两者的类型别名与 reactive 定义如下:
RuleOrientation = Literal["horizontal", "vertical"] LineStyle = Literal[ "ascii", "blank", "dashed", "double", "heavy", "hidden", "none", "solid", "thick", ] class Rule(Widget, can_focus=False): orientation: Reactive[RuleOrientation] = reactiveRuleOrientation line_style: Reactive[LineStyle] = reactiveLineStyle因为二者是 Reactive 属性,你可以在运行时直接赋值,控件会自动重绘:
rule = Rule() # ... 挂载到界面后,运行时动态切换 rule.orientation = "vertical" rule.line_style = "double"watch_orientation回调会在方向变化时同步切换控件的 CSS 类-horizontal与-vertical(见 src/textual/widgets/_rule.py),这两个类正是 DEFAULT_CSS 中不同布局规则的选择器。
源码解读:Rule 是如何渲染的
阅读 src/textual/widgets/_rule.py 可以清楚看到 Rule 的实现细节。
1. 线型到字符的映射表
每种线型在水平与垂直方向对应不同的 Unicode 制表符:
_HORIZONTAL_LINE_CHARS = { "ascii": "-", "blank": " ", "dashed": "╍", "double": "═", "heavy": "━", "hidden": " ", "none": " ", "solid": "─", "thick": "█", } _VERTICAL_LINE_CHARS = { "ascii": "|", "blank": " ", "dashed": "╏", "double": "║", "heavy": "┃", "hidden": " ", "none": " ", "solid": "│", "thick": "█", }可以推断,视觉风格差异正源于这些字符的选择:heavy用粗线字符━/┃,thick直接用实心块█,double用双线字符═/║,dashed用间断字符╍/╏,ascii则退化为-/|。
2. render() 的分发逻辑
Rule.render()根据orientation选择字符表,并构造对应的可渲染对象:
def render(self) -> RenderResult: if self.orientation == "vertical": return VerticalRuleRenderable(_VERTICAL_LINE_CHARS[self.line_style], style, self.content_size.height) elif self.orientation == "horizontal": return HorizontalRuleRenderable(_HORIZONTAL_LINE_CHARS[self.line_style], style, self.content_size.width) else: raise InvalidRuleOrientation(...)HorizontalRuleRenderable将单个字符重复width次拼成一行(Segment(self.width * self.character, self.style));VerticalRuleRenderable将单字符段与换行段交替重复height次,形成纵向延伸的竖线。
线条颜色来自self.rich_style,而 rich_style 由 CSS 决定,默认取主题色$secondary(见 DEFAULT_CSS)。
3. 内容尺寸的确定
Rule 重写了get_content_width与get_content_height:
def get_content_width(self, container, viewport): return container.width if self.orientation == "horizontal" else 1 def get_content_height(self, container, viewport, width): return 1 if self.orientation == "horizontal" else container.height即:水平方向占满容器宽度(高度固定 1 行),垂直方向占满容器高度(宽度固定 1 列)。这与 DEFAULT_CSS 中的布局规则相互印证:
Rule { color: $secondary; } Rule.-horizontal { height: 1; margin: 1 0; width: 1fr; } Rule.-vertical { width: 1; margin: 0 2; height: 1fr; }可以看到水平 Rule 上下各留 1 行外边距、宽度为1fr;垂直 Rule 左右各留 2 列外边距、高度为1fr。expand = True(在__init__中设置)确保它在分配布局空间时尽量伸展。
构造器参数与便捷类方法
Rule.__init__的完整签名(见 src/textual/widgets/_rule.py):
Rule( orientation: RuleOrientation = "horizontal", line_style: LineStyle = "solid", *, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, )除两个核心参数外,其余参数与所有 Textual 控件一致(DOM id、CSS 类、禁用状态等)。
同时 Rule 提供两个语义化的类方法构造器:
Rule.horizontal(line_style="solid", ...):等价于Rule(orientation="horizontal", line_style=...);Rule.vertical(line_style="solid", ...):等价于Rule(orientation="vertical", line_style=...)。
例如:
yield Rule.vertical(line_style="heavy")在快照测试应用 tests/snapshot_tests/snapshot_apps/rules.py 中,就同时使用了位置参数形式Rule("vertical", line_style=...)与关键字形式来创建竖线:
with Vertical(): for rule_style in RULE_STYLES: yield Rule(line_style=rule_style) with Horizontal(): for rule_style in RULE_STYLES: yield Rule("vertical", line_style=rule_style)参数校验:无效值会抛出异常
Textual 对 Rule 的两个核心参数做了严格校验,源码中定义了两种异常:
InvalidRuleOrientation:方向非法时抛出;InvalidLineStyle:线型非法时抛出。
校验逻辑由 reactive 的validate_orientation与validate_line_style钩子实现,非法值会被直接拒绝:
def validate_orientation(self, orientation): if orientation not in _VALID_RULE_ORIENTATIONS: raise InvalidRuleOrientation(f"Valid rule orientations are {friendly_list(_VALID_RULE_ORIENTATIONS)}") return orientation def validate_line_style(self, style): if style not in _VALID_LINE_STYLES: raise InvalidLineStyle(f"Valid rule line styles are {friendly_list(_VALID_LINE_STYLES)}") return style注意:校验是在赋值时触发的,因此无论在构造时还是运行时给orientation/line_style赋非法值都会立刻抛异常。测试文件 tests/test_rule.py 完整覆盖了这四种失败场景:
async def test_invalid_rule_orientation(): with pytest.raises(InvalidRuleOrientation): Rule(orientation="invalid orientation!") async def test_invalid_rule_line_style(): with pytest.raises(InvalidLineStyle): Rule(line_style="invalid line style!") async def test_invalid_reactive_rule_orientation_change(): rule = Rule() with pytest.raises(InvalidRuleOrientation): rule.orientation = "invalid orientation!" async def test_invalid_reactive_rule_line_style_change(): rule = Rule() with pytest.raises(InvalidLineStyle): rule.line_style = "invalid line style!"这两个异常类与类型别名LineStyle、RuleOrientation均从 src/textual/widgets/rule.py 导出,可以直接导入使用:
from textual.widgets.rule import InvalidLineStyle, InvalidRuleOrientation, LineStyle, RuleOrientation用 CSS 定制 Rule 的外观
除了线型,Rule 作为普通 Widget 同样支持 Textual 的 CSS 体系。常用的定制手段包括:
- 颜色:
color属性决定线条颜色,例如Rule { color: $accent; }; - 外边距与尺寸:水平 Rule 可通过
margin、width调整横线的位置与长短;垂直 Rule 可通过height控制伸展范围; - 组合选择器:利用方向类
Rule.-horizontal/Rule.-vertical分别定制横竖两种形态。
例如将某条 Rule 变为较宽幅的强调色横线:
Rule.emphasis { color: $warning; margin: 1 0; }消息、绑定与组件类
官方文档明确说明 Rule 的这三项均为空:
- Messages(消息):不发送任何消息;
- Bindings(按键绑定):无绑定;
- Component Classes(组件类):无组件类。
因此在实现自定义行为时,你不需要为 Rule 处理任何消息或按键事件,它的角色是纯装饰性的。
测试与验证
仓库通过快照测试保证 Rule 渲染的视觉回归,相关用例位于 tests/snapshot_tests/test_snapshots.py:
test_rule_horizontal_rules:基于docs/examples/widgets/horizontal_rules.py生成快照(test_rule_horizontal_rules.svg);test_rule_vertical_rules:基于docs/examples/widgets/vertical_rules.py生成快照(test_rule_vertical_rules.svg);test_rules:基于 tests/snapshot_tests/snapshot_apps/rules.py 一次性渲染 9 种线型 × 横竖两方向(test_rules.svg)。
此外 tests/test_rule.py 覆盖了非法方向与非法线型在构造与运行时赋值两种场景下的异常行为。若要在自己的应用中验证 Rule 行为,也可以在测试中使用 Textual 的 Pilot 驱动应用后断言控件属性与渲染结果。
小结
Rule 是 Textual 中一个轻量、专注的分隔控件:默认水平实线,通过orientation与line_style两个 Reactive 属性即可在横/竖两个方向、9 种线型间自由切换,并支持运行时动态修改;作为纯装饰控件,它无消息、无绑定、无组件类,配合 CSS 的color、margin、width/height等规则可以灵活融入各种布局。无论是表单分区、侧边栏分隔还是日志区块划分,Rule都能以最少代码提供清晰的结构化视觉反馈。
【免费下载链接】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),仅供参考