c/tty/demo_tty.c · 14048 字节 · 下载原文
/* demo_tty.c —— **第 2 层:彩色命令行**(`tty_*` / `Lib/c/tty.h`)
* demo_tty.c -- **Layer 2: the color command line** (`tty_*` / `Lib/c/tty.h`)
*
* ## 这一层是干嘛的
* ## What this layer is for
*
* 「**在一个字符界面上做版面**」—— 清屏、把光标挪到第几行第几列、设前景/背景色、
* "Lay out a character screen" -- clear it, move the cursor to a row/column, set fg/bg colors,
* 画个框。老 DOS 程序(菜单、表格、状态栏)全靠这几个原语拼出来。
* draw a box. Old DOS programs (menus, tables, status bars) are built from these primitives.
*
* ⚠ **新程序用 `tty_*`,老程序才用 `conio` / `Crt`**(用户定的分层标准:
* ⚠ **New programs use `tty_*`; only old programs use `conio` / `Crt`** (the agreed layering rule:
* 「新版程序,文字模式用 tty 库,图像模式用 ui 库;旧版老程序才用 conio、graphics 等库」)。
* "new programs use the tty lib for text mode and the ui lib for graphics; old programs use conio, graphics, etc.").
* `tty_*` 是这套能力的**正式名字**,22 门语言写出来的是**同一串调用**;
* `tty_*` is the **official name** for this capability; all 22 languages emit the **same call sequence**;
* `Lib/c/conio.h` 那一套是**兼容层**,留给老源码。
* the `Lib/c/conio.h` set is a **compatibility layer**, kept for old sources.
*
* ## 它是字符库,不是图形库
* ## It is a character library, not a graphics library
*
* **只有字符,没有任何图形功能**。要画图请用别的层:
* **Characters only, no graphics features whatsoever**. To draw, use another layer:
* · 传统固定分辨率图形 → BGI(`Lib/c/graphics.h`,见 `demo_bgi.c`)
* · classic fixed-resolution graphics -> BGI (`Lib/c/graphics.h`, see `demo_bgi.c`)
* · 现代图元 → `ui_*`(`Lib/c/vml_ui.h`,见 `demo_ui.c`)
* · modern primitives -> `ui_*` (`Lib/c/vml_ui.h`, see `demo_ui.c`)
*
* ## 坐标与颜色
* ## Coordinates and colors
*
* · 坐标 **1 起算**(`(1,1)` 左上角)—— 与 `gotoxy` / `GotoXY` / `LOCATE` 一致。
* · Coordinates are **1-based** (`(1,1)` is the top-left corner) -- same as `gotoxy` / `GotoXY` / `LOCATE`.
* · 颜色 **0–15 索引色**:`0 黑 1 蓝 2 绿 3 青 4 红 5 品红 6 棕 7 浅灰
* · Colors are **0-15 indexed**: 0 black 1 blue 2 green 3 cyan 4 red 5 magenta 6 brown 7 lightgray
* 8 深灰 9 亮蓝 10 亮绿 11 亮青 12 亮红 13 亮洋红 14 黄 15 白`
* 8 darkgray 9 lightblue 10 lightgreen 11 lightcyan 12 lightred 13 lightmagenta 14 yellow 15 white
*
* ## 它画到哪儿
* ## Where it draws
*
* **主屏**(默认)= 命令行页那个 80×25 的字符网格。`tty_*` 只产生字节
* The **main screen** (default) = the 80x25 character grid of the command-line page. `tty_*` only emits bytes
* (光标定位 `ESC[{行};{列}H`、颜色 SGR、字符、清屏 `ESC[2J`),走既有的渲染链,
* (cursor positioning `ESC[{row};{col}H`, color SGR, characters, clear `ESC[2J`), through the existing render chain,
* **不弹窗**。副屏(`tty_alt_open`)才会弹一扇窗口当终端 —— 本 demo 不演示它,
* and **does not pop up a window**. Only the alternate screen (`tty_alt_open`) pops up a window as a terminal -- this demo skips it,
* 免得"画完就退出"的例程顺手弹一个窗出来。
* so a "draw and exit" sample does not leave a window behind.
*
* ## ⚠ 版面是被一条**已量到的库缺陷**逼成这样的(不是审美选择)
* ## ⚠ This layout is forced by a **measured library defect** (it is not an aesthetic choice)
* ### ⚠ **2026-10-04 复验:这条缺陷仍然存在**(用最小复现重跑,字节跨度跨过第 80 列就当场乱)
* ### ⚠ **Re-checked 2026-10-04: this defect is still present** (the minimal repro re-run garbles the moment the byte span crosses column 80)
* 实测(桌面 `vmlcli`):`tty_goto(70,2)` 后连打 10 个 `┌` ⇒ 第 4 个起,输出里被插进一条 `ESC[3;1H`,
* Measured (desktop `vmlcli`): after `tty_goto(70,2)` printing ten `┌` ⇒ from the 4th on, an `ESC[3;1H` is spliced into the output
* 之后整份输出变成 CP437 乱码(UTF-8 校验**粘性**失效)。所以本 demo 的"不越过第 80 列"规矩**仍然必须遵守**。
* and the whole output then turns into CP437 mojibake (the UTF-8 validation fails **stickily**). So this demo's "never cross column 80" rule **still applies**.
*
* `Lib/shared/src/conio.c` 的 `putch()` 把**字节**当**列**计数:每个字节 `conX++`、
* `putch()` in `Lib/shared/src/conio.c` counts **bytes** as **columns**: every byte does `conX++`,
* 到 80 就折行并重发一条光标定位。而中文 / UTF-8 框线(`┌ ─ │`)一个字符是 **3 个字节**
* wraps at 80 and re-emits a cursor positioning. But a CJK char / UTF-8 box char (`┌ ─ │`) is **3 bytes**
* ⇒ 只要**某一段连续输出跨过第 80 列**,折行那一下就会落在**一个字符的中间**,
* => as soon as **one continuous output span crosses column 80**, the wrap lands **in the middle of a character**,
* 把一条 `ESC[…H` 插进这个字符的字节序列里。宿主的输出管线是**按字节重组 UTF-8** 的
* splicing an `ESC[...H` into that character's byte sequence. The host output pipeline **reassembles UTF-8 byte by byte**
* (`VMLRuntime.Syscall.cs` 的 `TryFlushOneOutputUnit`),校验一旦失败就**粘性**地
* (`TryFlushOneOutputUnit` in `VMLRuntime.Syscall.cs`); once validation fails it **stickily**
* 认定"这个程序写的是单字节老编码"(CP437)—— **此后整份输出全乱,不可恢复**。
* concludes "this program writes an old single-byte encoding" (CP437) -- **from then on the whole output is garbled, unrecoverably**.
*
* 最小复现(2026-09-24 实测,`Examples/c/demo_tty.c` 交付报告里也记了):
* Minimal reproduction (measured 2026-09-24; also recorded in the `Examples/c/demo_tty.c` delivery report):
*
* tty_goto(10, 2); for (i=0;i<20;i++) tty_puts("┌"); // 字节跨度 10..70 → 干净 ✓
// byte span 10..70 → clean ✓
* tty_goto(70, 2); for (i=0;i<10;i++) tty_puts("┌"); // 字节跨度 70..100 → 第 4 个起全乱 ✗
// byte span 70..100 → from the 4th one on everything is garbled ✗
*
* 不是 `tty_*` 的问题 —— 根在 `conio.c`(`tty_puts` 在主屏上就是转发给 `cputs`)。
* Not a `tty_*` problem -- the root is in `conio.c` (`tty_puts` just forwards to `cputs` on the main screen).
* 判据:把上面第二行的 `┌` 换成 `A`(1 字节 = 1 列)就完全正常。
* Test: replace `┌` in the second line above with `A` (1 byte = 1 column) and it is perfectly fine.
*
* 所以本 demo 的规矩是:**每一段"定位之后连续输出"的字节跨度不越过第 80 列**,
* So the rule for this demo is: **no "position then output continuously" span may cross column 80**,
* 也就是 `起始列 + 3×字符数 ≤ 80`:
* that is, `start column + 3 x char count <= 80`:
* · ASCII 行照常铺满(1 字节 = 1 列,正好是它的真实宽度)
* · ASCII lines fill the row as usual (1 byte = 1 column, exactly its real width)
* · 中文行都用 `tty_print_at` 重新定位(`tty_goto` 会把列计数清零)
* · CJK lines all re-position with `tty_print_at` (`tty_goto` resets the column counter)
* · UTF-8 框线只画**窄**的(19 格,从第 4 列起 ⇒ 61 字节)
* · UTF-8 box lines are drawn **narrow** only (19 cells, starting at column 4 => 61 bytes)
*
* 跑法:命令行页输入 vml run examples/c/demo_tty.c
* How to run: type vml run examples/c/demo_tty.c on the command-line page
*/
#include <tty.h>
#include <vml_ui.h> /* 只为 ui_scr_get_language()(界面语言,见下面 g_lang)*/
/* only for ui_scr_get_language() (the UI language, see g_lang below) */
#define COLS 80
#define ROWS 25
/* 界面语言:开局查一次(ui_scr_get_language 是 syscall,别每行都调)*/
/* UI language: queried once at start (ui_scr_get_language is a syscall, do not call it on every line) */
static int g_lang;
/* 在第 (x,y) 处用指定前景/背景打一串(打完颜色恢复成 7/0) */
/* Print a string at (x,y) with the given fg/bg (the color is restored to 7/0 afterwards) */
static void say(int x, int y, int fg, int bg, char *s)
{
tty_print_at(x, y, s, fg, bg);
}
int main(void)
{
int i;
/* ── 1. 初始化 + 清屏 ──
* ── 1. Init + clear the screen ──
* `tty_init(0, 0, 0)` = 尺寸自适应、不清屏(最常用的那一句);
* `tty_init(0, 0, 0)` = auto-size and no clear (the most common form);
* 这里要一块干净的屏,所以随后单独调 `tty_cls()`。
* Here we want a clean screen, so `tty_cls()` is called separately right after. */
tty_init(0, 0, 0);
tty_cls();
g_lang = ui_scr_get_language();
/* ── 2. 标题栏:亮黄字 + 蓝底,铺满第 1 行 ── */
/* ── 2. Title bar: bright yellow on blue, filling the whole first row ── */
tty_color(14, 1);
tty_goto(1, 1);
tty_puts(" ");
tty_goto(3, 1);
tty_puts("Dolaima tty_* demo (C) -- color / cursor / box / int");
tty_color(8, 0);
tty_goto(3, 2);
tty_puts("tty.h: tty_init / tty_cls / tty_color / tty_goto / tty_box / tty_put_int");
/* ── 3. ASCII 面板(61 列,安全)── */
/* ── 3. ASCII panel (61 columns, safe) ── */
tty_color(7, 0);
tty_box(2, 3, 62, 12, 0); /* style 0 = ASCII(`+ - |`) */
/* 面板里的内容:每行一种前景色 —— 这就是"能设前景色"最直观的样子 */
/* The panel contents: one foreground color per line -- the most direct look at "fg color works" */
say(4, 4, 7, 0, g_lang == 0 ? "color 7 lightgray 普通正文" : "color 7 lightgray body text");
say(4, 5, 11, 0, g_lang == 0 ? "color 11 lightcyan 次要信息" : "color 11 lightcyan secondary");
say(4, 6, 10, 0, g_lang == 0 ? "color 10 lightgreen ok / 成功" : "color 10 lightgreen ok / success");
say(4, 7, 14, 0, g_lang == 0 ? "color 14 yellow 状态栏高亮" : "color 14 yellow status bar");
say(4, 8, 12, 0, g_lang == 0 ? "color 12 lightred 错误 / 警告" : "color 12 lightred error / warn");
say(4, 9, 13, 0, g_lang == 0 ? "color 13 lightmagenta 强调" : "color 13 lightmagenta emphasis");
/* 反白一行("选中项"的长相):黑字白底 */
/* One reversed line (how a "selected item" looks): black text on white */
say(4, 11, 0, 7, " > Open (reversed: fg=0 bg=7) ");
/* ── 4. UTF-8 单线框(19 格宽 × 3 字节 = 57,从第 4 列起 ⇒ 61 字节,安全)── */
/* ── 4. UTF-8 single-line box (19 cells x 3 bytes = 57, from column 4 => 61 bytes, safe) ── */
tty_color(11, 0);
tty_box(4, 14, 22, 19, 1); /* style 1 = ┌ ─ │ ┐ └ ┘ 单线框 */
/* style 1 = ┌ ─ │ ┐ └ ┘ single-line box (UTF-8) */
/* 框里的中文:每行重新定位,列计数从 0 起 */
/* CJK text inside the box: each line re-positions, so the column counter restarts at 0 */
say(6, 16, 14, 0, g_lang == 0 ? "中文也" : "CJK too");
say(6, 17, 10, 0, g_lang == 0 ? "没问题" : "works");
/* ── 5. 右侧说明(ASCII,列 26 起)── */
/* ── 5. Notes on the right (ASCII, starting at column 26) ── */
say(26, 14, 11, 0, "tty_box(..., style=1)");
say(26, 15, 7, 0, " = UTF-8 single-line box");
say(26, 17, 11, 0, "tty_box(..., style=0)");
say(26, 18, 7, 0, " = ASCII box (used above)");
/* ── 6. 16 色色带 —— 一条就能看出"亮色档"对不对 ── */
/* ── 6. 16-color band -- one row is enough to tell whether the "bright" tier is right ── */
for (i = 0; i < 16; i++) {
tty_color(0, i); /* 黑字 + 第 i 号底色 */
/* black text on background color number i */
tty_goto(3 + i * 4, 21);
tty_puts(" ");
}
tty_color(7, 0);
tty_goto(3, 22);
tty_puts("index 0..15: black blue green cyan red magenta brown gray / +8 = bright");
/* ── 7. 数字与光标:`tty_put_int` + `tty_wherex/wherey` ── */
/* ── 7. Numbers and cursor: `tty_put_int` + `tty_wherex/wherey` ── */
tty_color(11, 0);
tty_goto(3, 24);
tty_puts("tty_put_int: ");
tty_color(14, 0);
tty_put_int(2026);
tty_color(11, 0);
tty_puts(" / ");
tty_color(14, 0);
tty_put_int(-42);
tty_color(11, 0);
tty_puts(" cursor=");
tty_put_int(tty_wherex());
tty_puts(",");
tty_put_int(tty_wherey());
tty_puts(" screen=");
tty_put_int(tty_width());
tty_puts("x");
tty_put_int(tty_height());
/* ── 8. 状态栏 + 收尾 ── */
/* ── 8. Status bar + wrap-up ── */
tty_color(0, 3); /* 黑字青底 */
/* black text on cyan background */
tty_goto(1, ROWS - 1);
tty_puts(" tty_* demo done -- no key wait, exits by itself ");
tty_color(8, 0);
tty_goto(3, ROWS);
tty_puts(g_lang == 0 ? "demo_tty (C) 结束 —— 画完即退出" : "demo_tty (C) done -- draws and exits");
/* 复位颜色,别把终端留在某种底色上 */
/* Reset the colors; do not leave the terminal on some background color */
tty_color(7, 0);
return 0;
}