Textual Toast 组件指南:用 App.notify 实现终端应用的通知消息系统
【免费下载链接】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
Toast 是 Textual 框架内置的通知消息组件,用于在应用界面右下角短暂弹出信息、警告或错误提示。本文以 docs/widgets/toast.md 为骨架,结合 src/textual/widgets/_toast.py、src/textual/app.py 与 tests/notifications 等源码与测试,讲解 Toast 的设计定位、CSS 定制方法、源码实现原理与验证方式,读完即可在自己的 Textual 应用中定制风格统一、位置合适的通知消息。
Toast 是什么
Toast 是一个用于展示短时通知消息的 widget,随 Textual 0.30.0 版本加入(对应 src/textual/widgets/_toast.py 顶部的模块注释)。在组件能力上,它具备以下特征:
- 不可聚焦(非 Focusable):Toast 不会参与键盘焦点循环,通知消息属于被动展示内容,不需要用户通过键盘操作。
- 不是容器(非 Container):Toast 自身不包含其他子 widget,它只是渲染一段通知内容。
文档给出了一个重要的使用警告:
Toast并不是设计来让你直接在应用中使用的,它实际上由notify方法调用,用于在 Textual 内置通知系统中展示消息。
也就是说,正常情况下你不需要mount(Toast(...))手动创建 Toast 实例,而是通过应用级的App.notify()API 触发,Textual 会自动创建、展示并销毁 Toast。相关 API 文档见 docs/api/app.md 中的notify说明。
如何触发一条通知:App.notify
尽管文档主题是 Toast 组件,但正确使用它的入口是App.notify。该方法定义在 src/textual/app.py,签名如下:
def notify( self, message: str, *, title: str = "", severity: SeverityLevel = "information", timeout: float | None = None, markup: bool = True, ) -> None:各参数含义与默认值:
| 参数 | 默认值 | 说明 | | :- | :- | :- | |message| 必填 | 通知正文内容 | |title|""| 通知标题,留空则不显示标题行 | |severity|"information"| 严重级别,可选information、warning、error| |timeout|None| 显示时长(秒);None时使用App.NOTIFICATION_TIMEOUT| |markup|True| 是否将message按 Textual 内容标记语法(Content markup,兼容 Rich 控制台标记)渲染 |
关于超时默认值,src/textual/app.py 定义了类变量NOTIFICATION_TIMEOUT: ClassVar[float] = 5,即默认每条通知展示 5 秒;在notify内部,当timeout is None时会回落到该值(见 src/textual/app.py)。
notify是一个线程安全方法,可以在工作线程(Worker)中安全调用。调用后它会把参数封装成一个Notification数据对象,并通过post_message(Notify(notification))派发消息,再由应用的消息处理器_on_notify把它登记进通知集合并刷新界面(见 src/textual/app.py)。
完整示例
仓库中的 docs/examples/widgets/toast.py 演示了四种典型的通知用法:
from textual.app import App class ToastApp(App[None]): def on_mount(self) -> None: # Show an information notification. self.notify("It's an older code, sir, but it checks out.") # Show a warning. Note that Textual's notification system allows # for the use of Rich console markup. self.notify( "Now witness the firepower of this fully " "[b]ARMED[/b] and [i][b]OPERATIONAL[/b][/i] battle station!", title="Possible trap detected", severity="warning", ) # Show an error. Set a longer timeout so it's noticed. self.notify("It's a trap!", severity="error", timeout=10) # Show an information notification, but without any sort of title. self.notify("It's against my programming to impersonate a deity.", title="") if __name__ == "__main__": ToastApp().run()这个例子覆盖了三种严重级别,并演示了两个细节:
- 消息支持标记语法:
[b]...[/b]、[i]...[/i]这类 Rich/Textual 标记会被渲染成加粗、斜体样式,前提是markup=True(默认开启)。 title=""可以隐藏标题:代码最后一行的通知不显示标题,只渲染正文。
从源码看,Toast.render()(src/textual/widgets/_toast.py)正是根据notification.markup决定是走Content.from_markup(...)还是普通Content(...),并在存在标题时用toast--title的视觉样式把标题拼到正文上方。
其他通知管理 API
除了notify,应用层还提供:
App.action_notify(message, title="", severity="information"):将通知作为 Action 暴露,可在 bindings 或 CSS 动画动作 中直接调用(src/textual/app.py)。App.clear_notifications():清空当前所有未过期的通知(src/textual/app.py)。
样式定制:定位、配色与标题
Toast 的样式定制主要通过 CSS 完成,文档给出了三个层面的定制手段。
1. 定制 Toast 本身:类型选择器
通过Toast这个 CSS 类型选择器 定制通知卡片的外观,例如:
Toast { padding: 3; }类型选择器的规则详见 CSS 类型选择器。注意Toast的默认样式来自DEFAULT_CSS(src/textual/widgets/_toast.py),你可以在应用 CSS 中覆盖这些属性,包括:
width: 60; max-width: 50%; height: auto;:宽度默认 60 个单元格、最大不超过界面宽度一半;margin-top: 1; padding: 1 1;:卡片间距与内边距;background: $panel-lighten-1;:背景使用设计系统变量;- 一系列
link-*属性:控制通知内链接(链接标记)的颜色与下划线样式。
2. 定制 Toast 的位置:ToastRack 类型选择器
Toast 实际被放置在一个名为ToastRack的容器中,通过 CSS 类型选择器可以改变通知出现的位置。默认样式为右下角对齐(见 src/textual/widgets/_toast.py 中align: right bottom; dock: bottom;)。文档给出的示例是把通知移到右上角:
ToastRack { align: right top; }ToastRack还承担了垂直排列(layout: vertical)、滚动(overflow-y: scroll)与停靠(dock: bottom)等职责,其完整默认样式如下(来自源码DEFAULT_CSS):
ToastRack { display: none; layer: _toastrack; width: 1fr; height: auto; dock: bottom; align: right bottom; visibility: hidden; layout: vertical; overflow-y: scroll; margin-bottom: 1; }3. 按严重级别定制:三种状态类
三种严重级别分别对应三个 CSS 类选择器:
-information-warning-error
文档推荐按级别分别定制,例如:
Toast.-information { /* Styling here. */ } Toast.-warning { /* Styling here. */ } Toast.-error { /* Styling here. */ }这些类名由源码保证:Toast.__init__会把f"-{notification.severity}"作为 CSS 类挂在 Toast 上(src/textual/widgets/_toast.py),而严重级别的合法取值由类型别名SeverityLevel = Literal["information", "warning", "error"]约束(src/textual/notifications.py)。
框架自带的默认样式就大量使用了这三个类(src/textual/widgets/_toast.py):
Toast.-information { border-left: outer $success; } Toast.-information .toast--title { color: $text-success; } Toast.-warning { border-left: outer $warning; } Toast.-warning .toast--title { color: $text-warning; } Toast.-error { border-left: outer $error; } Toast.-error .toast--title { color: $text-error; }可以看到默认实现用左侧边框颜色($success/$warning/$error)区分严重级别,并同步调整标题颜色。
4. 单独定制标题:toast--title 组件类
标题行由组件类toast--title标记。文档示例让信息级通知的标题变为斜体:
Toast.-information .toast--title { text-style: italic; }组件类的官方定义见 docs/api/dom_node.md 中的COMPONENT_CLASSES概念,Toast只注册了这一个组件类:
| Class | Description | | :- | :- | |toast--title| Targets the title of the toast. |
其默认样式为text-style: bold; color: $foreground;(src/textual/widgets/_toast.py),即标题默认加粗并继承前景色。
源码实现:从 notify 到 Toast 的生命周期
了解实现细节有助于解释为什么文档要求“不要直接使用 Toast”。整个通知链路由三个类协作完成,全部位于 src/textual/widgets/_toast.py:
Toast:渲染与自动消失
class Toast(Static, inherit_css=False): DEFAULT_CLASSES = "-textual-system"- Toast 继承
Static(不是Container,与文档“非容器”的描述一致),用于渲染只读文本内容。 __init__接收一个Notification,把严重级别转成 CSS 类(f"-{notification.severity}")。- 挂载后(
_on_mount)会启动一个定时器:self.set_timer(self._timeout, self._expire),其中_timeout来自notification.time_left(即raised_at + timeout - 当前时间)。 - 定时器到点或用户点击 Toast(
@on(Click))都会触发_expire:先调用app._unnotify(...)让应用忘记这条通知,再把自己(连同包裹它的ToastHolder)从 DOM 中移除。移除自身这类“短时存活”组件的惯用法可参考 docs/api/await_remove.md。
ToastHolder:单条对齐容器
class ToastHolder(Container, inherit_css=False): DEFAULT_CSS = """ ToastHolder { align-horizontal: right; width: 1fr; height: auto; visibility: hidden; } """每条 Toast 外面都包了一层ToastHolder,用于单独控制这条 toast 在纵向布局中的对齐方式。ToastRack.show中挂载的是ToastHolder(Toast(toast), id=...)的嵌套结构。
ToastRack:通知容器与增量刷新
class ToastRack(Container, inherit_css=False): DEFAULT_CLASSES = "-textual-system"ToastRack在屏幕组合阶段被自动插入 DOM:Screen._extend_compose会在每个 Screen 中放入ToastRack(id="textual-toastrack")(src/textual/screen.py),因此你无需手动创建它。它把每条通知包装成ToastHolder,并用--textual-toast-{notification.identity}作为 DOM ID。
ToastRack.show(notifications)是刷新入口,做三件事:
- 根据是否有通知决定
self.display(无通知时隐藏整个栈); - 移除“已经不在通知集合中”的过期 Toast;
- 为新增且未过期的通知批量挂载新的 Toast,并
scroll_end滚动到最新一条。
Notification 数据模型
Notification是一个 dataclass(src/textual/notifications.py),字段包括message、title、severity、timeout、markup、raised_at(Unix 时间戳)和identity(UUID)。其中:
time_left属性 =(raised_at + timeout) - time(),用于计算剩余显示时间;has_expired属性判断是否time_left <= 0;Notifications集合类每次增删、迭代前都会_reap()回收已过期条目,保证过期通知不会残留。
这条“过期即回收”的机制与测试 tests/notifications/test_notifications.py 中的test_timeout、test_remove_notification等用例一一对应。
能力边界:响应式属性、消息与绑定
根据文档,Toast 组件本身保持极简:
- 响应式属性(Reactive Attributes):无;
- 消息(Messages):不主动发送任何消息;
- 绑定(Bindings):无键盘绑定。
也就是说,Toast 是一个“只展示、不交互”的被动组件,用户与它的唯一互动是点击提前关闭(点击通过内部@on(Click)处理器处理,不对外暴露消息)。如果需要编程式控制通知,应使用应用层的notify/clear_notificationsAPI 而不是直接操作 Toast。
测试验证与再深入
仓库为通知系统提供了完整的测试覆盖,可作为理解行为边界的参考:
- tests/notifications/test_all_levels_notifications.py:验证三种严重级别(
information/warning/error)的通知都能正常展示; - tests/notifications/test_app_notifications.py:验证应用级
notify的添加、移除、过期自动清除与clear_notifications行为; - tests/notifications/test_notification.py:验证
Notification的默认标题、默认严重级别、默认超时、UUID 唯一性与过期计算; - tests/notifications/test_notifications.py:验证
Notifications集合的增删、过期回收与去重。
若想继续了解通知系统背后的消息机制,可阅读 docs/api/message.md(Notify消息)与 src/textual/notifications.py 中SeverityLevel、Notify、Notification、Notifications的完整定义;Toast 的完整 API 签名与组件类文档则以 docs/widgets/toast.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),仅供参考