news 2026/9/19 17:34:10

Textual Rule 控件完全指南:用 `<hr>` 式的分隔线组织终端界面布局

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Textual Rule 控件完全指南:用 `<hr>` 式的分隔线组织终端界面布局

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 种取值:asciiblankdasheddoubleheavyhiddennonesolidthick。其中solid是默认值;blankhiddennone三种在视觉上等效于空白,常用于"隐藏"分隔线但保留布局占位;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):

名称类型默认值描述
orientationRuleOrientation"horizontal"规则的方向(横/竖)。
line_styleLineStyle"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_widthget_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 列外边距、高度为1frexpand = 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_orientationvalidate_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!"

这两个异常类与类型别名LineStyleRuleOrientation均从 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 可通过marginwidth调整横线的位置与长短;垂直 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 中一个轻量、专注的分隔控件:默认水平实线,通过orientationline_style两个 Reactive 属性即可在横/竖两个方向、9 种线型间自由切换,并支持运行时动态修改;作为纯装饰控件,它无消息、无绑定、无组件类,配合 CSS 的colormarginwidth/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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:33:21

YooAsset资源管理设计哲学:从AssetBundle构建到热更新落地实践

1. 为什么资源管理是Unity项目的隐形地基做Unity项目超过三年的朋友&#xff0c;大概率都经历过这样的场景&#xff1a;游戏在编辑器里跑得飞快&#xff0c;打包出来一进战斗场景就卡成幻灯片&#xff1b;或者热更之后玩家反馈资源错乱&#xff0c;模型贴图张冠李戴&#xff1b…

作者头像 李华
网站建设 2026/9/19 17:33:18

Greasy Fork 用户脚本实战指南:从安装到编写发布

我第一次打开 Greasy Fork 的脚本列表&#xff0c;说实话没什么特别的感觉。满屏的英文、奇怪的注释、参数列表&#xff0c;第一眼看上去离普通人太远了。但真正把一个用户脚本装进浏览器&#xff0c;跑起来之后&#xff0c;我才意识到过去自己花在网页重复操作上的时间有多冤枉…

作者头像 李华
网站建设 2026/9/19 17:32:31

FigmaCN使用指南:6个实用技巧,让设计师彻底告别英文界面

FigmaCN使用指南&#xff1a;6个实用技巧&#xff0c;让设计师彻底告别英文界面 【免费下载链接】figmaCN 中文 Figma 插件&#xff0c;设计师人工翻译校验 项目地址: https://gitcode.com/gh_mirrors/fi/figmaCN FigmaCN 是一款免费的中文 Figma 插件&#xff0c;能把 …

作者头像 李华
网站建设 2026/9/19 17:30:19

济南樱花燃气灶故障维修电话|燃气灶漏气检测|欧米到家咨询电话

燃气灶是济南家庭日常烹饪中使用频率很高的设备&#xff0c;涉及点火、燃烧、熄火保护、阀体和燃气连接等多个安全环节。遇到燃气灶打不着火、有火花却点不燃、一松手就熄火、火焰发黄发红、火力变小、锅底熏黑、旋钮拧不动、关火后持续打火&#xff0c;或闻到燃气异味等情况时…

作者头像 李华