TUI——用户流程从文本循环到屏幕交互

有交互CLI的用户流程是一个文本循环:输入命令→输出文字→再输入。但当程序需要让用户「浏览列表」「选择项目」「查看详情」时,纯文本的命令循环就力不从心了。TUI在终端屏幕上划分区域,用户在区域之间移动焦点、触发操作——用户流程从「输入命令」变成了「在屏幕上导航」。


一、从命令到屏幕

有交互CLI:

1
2
3
4
5
6
7
8
9
> list
1. process A (PID 100)
2. process B (PID 200)
3. process C (PID 300)
> select 2
selected: process B
> details
PID: 200, Memory: 45MB, Threads: 12
> exit

用户需要记住自己看到了什么、选择了什么、下一步要做什么什么。信息是线性的,上下文靠记忆维持。

TUI:

1
2
3
4
5
6
7
8
9
10
11
┌──────────────────────────────┐
│ Processes [v1.0]│
├──────────────┬───────────────┤
│ PID Name │ Details │
│ 100 abc.exe │ PID: 100 │
│>200 xxx.exe │ Name: xxx.exe │ ← 焦点在这里
│ 300 yyy.exe │ Memory: 45MB │
│ │ Threads: 12 │
├──────────────┴───────────────┤
│ ↑↓ Move Enter Detail q Quit│
└──────────────────────────────┘

用户不需要记住——屏幕同时展示了列表和详情。焦点用 > 标记,操作提示在底部。信息是空间的,上下文靠屏幕维持。

TUI的核心变化:信息从线性文本变成了空间布局。


二、屏幕区域:TUI的物理结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
flowchart LR
subgraph "TUI 屏幕"
direction TB
Title["标题栏"]
subgraph "中部区域"
direction LR
List["列表区域<br/>↑↓ 移动<br/>Enter 选择"]
Detail["详情区域<br/>显示选中项信息"]
end
Status["状态栏 / 操作提示"]
Title -.-> List
Title -.-> Detail
List -.-> Status
Detail -.-> Status
end

style Title fill:#4a9eff,color:#fff
style List fill:#ff9f4a,color:#fff
style Detail fill:#4caf50,color:#fff
style Status fill:#888,color:#fff

TUI的屏幕被划分成多个区域:

1
2
3
4
5
6
7
8
┌──────────────────────────────┐
│ 标题栏 │ ← 区域 1:标题
├──────────────┬───────────────┤
│ 列表区域 │ 详情区域 │ ← 区域 2 + 区域 3
│ │ │
├──────────────┴───────────────┤
│ 状态栏 / 操作提示 │ ← 区域 4:状态栏
└──────────────────────────────┘

每个区域有自己的内容和行为:

1
2
3
4
标题栏:显示程序名称、版本
列表区域:显示可选择的项目,支持上下滚动
详情区域:显示当前选中项目的详细信息
状态栏:显示当前操作提示或状态信息

区域之间的关系:

1
2
3
4
5
6
7
列表区域 和 详情区域 是联动的:
用户在列表区域移动焦点
→ 详情区域自动更新为选中项目的详情

这是一种「主从关系」:
列表 = 主区域(用户操作的目标)
详情 = 从区域(根据主区域的状态更新)

三、焦点:用户在屏幕上的「位置」

有交互CLI中,用户的「位置」是隐式的——光标在哪,用户就在哪。TUI中,焦点是显式的——有一个明确的标记告诉用户「你当前在操作哪个区域」。

1
2
3
4
5
6
7
8
焦点在列表区域:
↑↓ → 在列表中移动
Enter → 查看详情
q → 退出

焦点在详情区域:
↑↓ → 滚动详情内容
Tab → 回到列表区域

焦点决定了用户输入被谁接收。 同一个按键(如 ↑)在不同焦点下有不同含义。

1
2
3
焦点在列表:↑ = 上移一个项目
焦点在详情:↑ = 上滚一行
焦点在搜索框:↑ = 可能无效

焦点是TUI控制流的核心——它决定了用户输入流向哪个区域。


四、焦点转移:导航的核心机制

1
2
3
4
5
6
7
8
9
10
11
graph TD
A["搜索框"] -->|"Esc"| B["列表区域"]
B -->|"Tab"| C["详情区域"]
C -->|"Shift+Tab"| B
A2["/ 键"] -->|"触发"| A
B -->|"q"| D["退出"]

style A fill:#ff9f4a,color:#fff
style B fill:#4a9eff,color:#fff
style C fill:#4caf50,color:#fff
style D fill:#f44,color:#fff

用户在TUI中移动,本质上是焦点在区域之间转移

1
2
3
4
5
6
7
8
9
10
11
12
13
初始状态:焦点在列表区域

用户按 Tab
→ 焦点转移到详情区域

用户按 Shift+Tab
→ 焦点回到列表区域

用户按 /(搜索)
→ 焦点转移到搜索框

用户按 Esc
→ 焦点回到列表区域

焦点转移图:

1
2
3
4
5
6
7
8
        ┌──────────┐
│ 搜索框 │
└────┬─────┘
│ Esc
Tab │ Shift+Tab
┌────┴─────┐ │ ┌──────┴───┐
│ 列表区域 │←┼→│ 详情区域 │
└──────────┘ └──────────┘

焦点转移 = TUI的导航。 用户通过焦点转移在屏幕的不同区域之间移动。


五、用户流程的完整路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
用户流程(TUI 完整路径):

1. 启动程序
2. 看到完整屏幕布局
3. 焦点在默认区域(如列表)
4. 按键操作:
a. ↑↓ → 移动焦点/滚动内容
b. Enter → 进入/确认
c. Tab → 切换区域
d. q/Esc → 返回/退出
5. 看到屏幕更新
6. 重复步骤 4-5
7. 按退出键
8. 屏幕恢复,程序退出

和有交互CLI的区别:

1
2
3
4
5
6
7
有交互CLI:
输入命令 → 看到文字输出 → 输入下一个命令
(线性文本交互)

TUI:
按键 → 屏幕区域更新 → 按键 → 屏幕更新
(空间布局交互)

TUI的用户流程不是「输入命令」,而是「在屏幕上导航」。


六、TUI的程序结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
flowchart TD
A["初始化屏幕布局"] --> B["设置默认焦点"]
B --> C["等待按键"]
C --> D{"按键类型?"}
D -->|"↑↓"| E["移动焦点/滚动"]
D -->|"Enter"| F["确认/进入"]
D -->|"Tab"| G["切换区域"]
D -->|"q"| H["退出"]
E --> I["重绘屏幕"]
F --> I
G --> I
I --> C

style C fill:#4a9eff,color:#fff
style H fill:#f44,color:#fff

TUI的程序结构不再是简单的命令循环,而是事件循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──────────────────────────────┐
│ 初始化屏幕布局 │
│ 设置默认焦点 │
│ │
│ ┌──────────────────────────┐ │
│ │ 等待按键 │ │ ← 事件循环起点
│ │ ↓ │ │
│ │ 根据当前焦点分发事件 │ │
│ │ ↓ │ │
│ │ 更新屏幕 │ │
│ │ ↓ │ │
│ │ 是退出? ──是──→ 恢复终端 │ │
│ │ │ 否 │ │
│ │ └──→ 回到等待按键 │ │ ← 事件循环终点
│ └──────────────────────────┘ │
└──────────────────────────────┘

代码形态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main() {
init_screen(); // 初始化终端为 raw mode
draw_layout(); // 画出初始屏幕

while (1) {
int key = getch(); // 等待按键
if (key == 'q') break;

handle_key(key); // 根据焦点分发
draw_screen(); // 重绘屏幕
}

restore_terminal(); // 恢复终端
return 0;
}

事件循环是有交互CLI的命令循环的升级版。 命令循环处理的是文字行,事件循环处理的是单个按键。


七、屏幕重绘:每次按键后的更新

有交互CLI的输出是追加的——新输出出现在旧输出下面。TUI的输出是覆盖的——每次按键后整个屏幕(或部分屏幕)被重绘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
按键前:
┌──────────────────────────────┐
│>100 abc.exe │ PID: 100 │ ← 焦点在第1行
│ 200 xxx.exe │ Name: xxx.exe │
│ 300 yyy.exe │ Memory: 45MB │
└──────────────────────────────┘

用户按 ↓

按键后:
┌──────────────────────────────┐
│ 100 abc.exe │ PID: 100 │
│>200 xxx.exe │ Name: xxx.exe │ ← 焦点移到第2行
│ 300 yyy.exe │ Memory: 45MB │
└──────────────────────────────┘

屏幕重绘的核心问题:

1
2
3
1. 全量重绘 vs 增量重绘:每次重绘整个屏幕还是只重绘变化的部分?
2. 闪烁问题:重绘太快会导致屏幕闪烁
3. 终端兼容性:不同终端的控制序列不同

TUI的屏幕重绘是它的技术核心——和GUI的渲染循环是同一种东西,只是载体不同。


八、TUI vs 有交互CLI的本质区别

维度 有交互CLI TUI
信息展示 线性文本 空间布局
用户输入 输入完整命令 按单个按键
上下文维持 靠用户记忆 靠屏幕显示
导航方式 输入命令 移动焦点
程序结构 命令循环 事件循环
输出方式 追加文本 覆盖重绘
交互粒度 行级(一次一行) 字符级(一次一个按键)

TUI的核心变化:交互粒度从「行」变成了「字符」。 有交互CLI一次处理一行输入,TUI一次处理一个按键。这个粒度的变化让交互变得即时——用户按下一个键就能看到屏幕变化,不需要按回车。


九、TUI的用户流程总结

1
2
3
4
5
6
7
8
9
10
11
12
13
TUI的用户流程:

启动 → 绘制屏幕 → [按键 → 焦点转移 → 屏幕更新] × N → 退出键 → 恢复终端

特征:
┌─────────────────────────────────────────┐
│ 空间布局 信息同时展示在屏幕上 │
│ 焦点机制 用户输入被当前焦点区域接收 │
│ 事件循环 按键驱动,即时响应 │
│ 屏幕重绘 每次操作后更新显示 │
│ 即时反馈 不需要按回车就有响应 │
│ 模态交互 可以弹出对话框/搜索框 │
└─────────────────────────────────────────┘

TUI相比有交互CLI的核心变化:

维度 有交互CLI TUI
交互模型 命令循环 事件循环
信息展示 线性文本 空间布局
反馈速度 行级(需回车) 字符级(即时)
用户负担 记住上下文 屏幕显示上下文
设计重点 命令接口 屏幕布局 + 焦点管理

TUI是在终端限制下能做到的最高级交互。 但它仍然受限于字符网格——不能显示图片、不能自由拖拽、不能有任意形状的窗口。当程序需要这些能力时,就必须离开终端,进入GUI。下一篇看GUI。