Textual 内联模式(Inline Mode)样式定制实战指南:用:inline伪类打造提示符下方的常驻应用
【免费下载链接】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 自 0.55.0 起支持在终端提示符下方"内联"运行应用,让你无需切换全屏即可把时钟、监控面板等轻量 UI 常驻在 shell 输出流中。本文围绕官方 How-To 文档 docs/how-to/style-inline-apps.md,结合仓库源码与可运行示例,讲解如何用inline=True启动内联应用、理解其默认布局,并通过:inline伪类与 CSS 精准定制内联模式下的高度、边框与配色。读完你将掌握一套无需修改业务代码即可为内联场景定制样式的完整方案。
什么是内联模式:应用直接渲染在提示符下方
普通 Textual 应用会接管整个终端屏幕,退出后恢复 shell 界面;而内联模式(inline mode)会把应用"画"在当前提示符下方的若干行里,光标、命令历史与后续输出都围绕它共存,特别适合实时状态展示、进度面板、快捷工具等场景。
启用方式极为简单,只需在调用run()时传入inline=True:
app.run(inline=True)从源码可以看到,run_async还提供了配套参数inline_no_clear(退出内联应用时不清除其输出),以及mouse、size、auto_pilot等通用选项(见 src/textual/app.py)。
驱动层面,Textual 会在inline=True且非 Windows 环境下加载专用的LinuxInlineDriver(见 src/textual/app.py),该驱动的is_inline属性恒为True(见 src/textual/drivers/linux_inline_driver.py),从而驱动整套内联渲染流程。也就是说,内联模式在 Linux/macOS 终端上开箱即用,Windows 平台暂不支持内联运行。
最小内联应用:一个实时时钟
官方 How-To 用一个实时时钟作为示例(完整代码见 docs/examples/how-to/inline01.py):
from datetime import datetime from textual.app import App, ComposeResult from textual.widgets import Digits class ClockApp(App): CSS = """ Screen { align: center middle; } #clock { width: auto; } """ def compose(self) -> ComposeResult: yield Digits("", id="clock") def on_ready(self) -> None: self.update_clock() self.set_interval(1, self.update_clock) def update_clock(self) -> None: clock = datetime.now().time() self.query_one(Digits).update(f"{clock:%T}") if __name__ == "__main__": app = ClockApp() app.run(inline=True) # (1)!代码本身与普通 Textual 应用没有任何区别,唯一的关键点是第 31 行的app.run(inline=True):它以# (1)!标注,即"以inline=True运行应用即进入内联模式"。compose中挂载Digits控件显示时间,on_ready中先立即刷新一次,再用set_interval(1, ...)每秒更新时间。
运行后,在 Textual 的默认设置下,这个时钟只占5 行:3 行用于显示数字字形,2 行用于顶部与底部的默认边框。绝大多数已有应用无需任何改动即可直接以内联方式运行——这正是内联模式的设计目标。
默认内联布局与 INLINE_PADDING 顶部留白
除上述 5 行本体外,Textual 还会在内联应用上方额外输出一个空行作为与提示符之间的留白。这一留白由App上的类变量INLINE_PADDING控制,源码中定义如下(见 src/textual/app.py):
INLINE_PADDING: ClassVar[int] = 1 """Number of blank lines above an inline app."""- 默认值为
1,即内联应用上方空 1 行; - 若希望应用紧贴提示符、消除这段留白,可在你的 App 类中覆写为
0:
class ClockApp(App): INLINE_PADDING = 0该值在启动时由内联驱动实际写入终端:LinuxInlineDriver.start_application_mode中执行self.write("\n" * self._app.INLINE_PADDING)(见 src/textual/drivers/linux_inline_driver.py);应用退出时则通过Control.move(0, -self.INLINE_PADDING)回退光标位置(见 src/textual/app.py),保证退出后提示符位置正确。
用:inline伪类精准定制内联样式
内联应用通常在宽度不受限但高度敏感的场景下运行,你可能希望调整其高度、隐藏边框、换一套配色。Textual 为此提供了专门的 CSS 伪类:inline——它只在应用处于内联模式时匹配规则,普通(全屏)模式下这些规则完全不生效。
该伪类的底层实现可以在App._PSEUDO_CLASSES注册表中找到(见 src/textual/app.py):
_PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = { "focus": lambda app: app.app_focus, "blur": lambda app: not app.app_focus, "dark": lambda app: app.current_theme.dark, "light": lambda app: not app.current_theme.dark, "inline": lambda app: app.is_inline, "ansi": lambda app: app.native_ansi_color, "nocolor": lambda app: app.no_color, }:inline对应的判定函数返回app.is_inline(见 src/textual/app.py),而该属性在驱动层面即内联驱动的is_inline == True,因此 CSS 解析时能准确获知当前运行模式。
下面是对时钟应用的内联定制版(完整代码见 docs/examples/how-to/inline02.py):
from datetime import datetime from textual.app import App, ComposeResult from textual.widgets import Digits class ClockApp(App): CSS = """ Screen { align: center middle; &:inline { border: none; height: 50vh; Digits { color: $success; } } } #clock { width: auto; } """ def compose(self) -> ComposeResult: yield Digits("", id="clock") def on_ready(self) -> None: self.update_clock() self.set_interval(1, self.update_clock) def update_clock(self) -> None: clock = datetime.now().time() self.query_one(Digits).update(f"{clock:%T}") if __name__ == "__main__": app = ClockApp() app.run(inline=True)高亮部分(第 11–17 行)就是针对内联模式的定制规则,逐条解读:
Screen { &:inline { ... } }:使用&嵌套语法,等价于Screen:inline,只在内联模式下匹配 Screen 节点;border: none;:移除内联模式下的默认边框(默认 2 行边框正是前面 5 行布局的一部分),让应用更紧凑;height: 50vh;:把 Screen 高度设为终端视口高度的 50%(Textual 支持vh这类以视口为基准的 CSS 尺寸单位)。在内联模式下,Screen 的高度直接决定应用占用的终端行数;Digits { color: $success; }:嵌套规则,将内联模式下的时钟数字染成主题的成功色($success为 Textual 设计系统内置的颜色变量),实现"同一应用、两套外观"。
这些规则只在inline=True运行时生效,以普通全屏方式运行同一 App 时样式保持原样,互不干扰。
内联高度是如何计算的(源码原理)
为什么设置height就能控制应用占用的行数?从源码看,Screen 提供了_get_inline_height方法专门计算内联模式下的显示行数(见 src/textual/screen.py),其逻辑大致为:
- 若设置了明确的高度样式,则解析该高度值(如
50vh换算为具体行数); - 否则以内容高度(
get_content_height)作为内联高度——这就是默认时钟占 5 行的原因; - 加上
gutter(边框 + 内边距)的高度; - 再受
min-height/max-height约束夹取; - 最后以整个应用(终端)的高度为上限封顶。
应用层则遍历屏幕栈,取所有屏幕内联高度的最大值(见 src/textual/app.py):
def _get_inline_height(self) -> int: size = self.size return max(screen._get_inline_height(size) for screen in self._screen_stack)刷新时,Screen 的_compositor_refresh会调用render_inline,将内联区域按计算出的高度渲染,并在新高度小于旧高度时先行清屏(clear标志),避免留下残影(见 src/textual/screen.py)。由此可见,Screen的height(配合min-height/max-height)是控制内联应用占据行数的核心手段。
实用技巧与注意事项
- 退出时保留输出:内联应用退出后默认会清理界面,若想保留最后一次渲染结果(例如让时钟的最后时刻留在终端上),可传入
run(inline=True, inline_no_clear=True)。仓库测试 tests/test_app.py 中的test_early_exit_inline即验证了内联模式下提前退出不会破坏终端状态。 - 留白与边框的取舍:默认的 1 行顶部留白(
INLINE_PADDING)与 2 行边框共同构成了内联应用的"呼吸感";空间紧张时可用INLINE_PADDING = 0加border: none的组合最大化可用行数。 - 只影响内联,不影响全屏:
:inline伪类是内联样式定制的关键边界——所有针对内联模式的调整都应写在&:inline { ... }块内,普通规则仍按常规方式作用于两种模式。 - 平台限制:内联驱动仅在非 Windows 平台加载,Windows 下
inline=True会回退到常规模式。 - 依赖版本:内联模式能力自 Textual 0.55.0 起提供,请确保使用不低于该版本的 Textual。
小结
内联模式让 Textual 应用能够常驻在提示符下方,而无需任何业务逻辑改动。若想进一步定制内联外观,只需掌握两点:用INLINE_PADDING = 0控制顶部留白,用:inline伪类编写仅在内联模式下生效的 CSS 规则(调整Screen的height、border及子控件配色)。这种"一套代码、两种形态"的能力,使 Textual 内联应用非常适合嵌入日常 shell 工作流,作为轻量常驻仪表盘使用。更多关于 CSS 语法与伪类的基础知识,可参阅 docs/guide/CSS.md;两个完整的可运行示例分别位于 docs/examples/how-to/inline01.py 与 docs/examples/how-to/inline02.py。
【免费下载链接】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),仅供参考