# 玄铁声明式 UI 库编程指南
> 对标库版本: **v1.5.1**

UI 库是基于渲染库的**声明式界面框架**:界面 = 状态的函数(View = f(State))。你只描述"状态长这样时界面长什么样",框架负责布局、绘制与事件分发。

```xuantie
引 "UI" 予 UI        // 铁铺安装后裸包名引用(推荐)
引 "渲染" 予 R
```

> 本地项目内也可以相对路径引用:`引 "./lib/UI/UI.xt" 予 UI`。UI 库内部会再引渲染库,菱形引入是安全的(见 [05_模块系统.md](./05_模块系统.md) 3.2 节)。

---

## 五分钟上手:计数器

#### 代码
```xuantie
引 "UI"
引 "渲染" 予 R

设 状态 = UI.造状态({"计数": 0})

UI.应用(状态, 函(s) {
    返 UI.列([
        UI.文本("计数器演示", {
            字号: 28
        }),
        UI.文本("当前: " & UI.态取(s, "计数"), {
            字号: 20
        }),
        UI.按钮("点我 +1", {点击: 函() {
            UI.态设(s, "计数", UI.态取(s, "计数") + 1)
        }})
    ], {内边距: 20, 间距: 12})
}, {宽: 640, 高: 420, 标题: "我的应用"})
```
点按钮,数字会真实递增。这就是全部:没有手动事件循环、没有手动重绘、无需手动进行字体配置(内置鸿蒙字体自动加载,见第 4 节)。
#### 效果
<video controls style="max-width:100%;border-radius:10px"><source src="https://xuantie-community.cn-nb1.rains3.com/img/docs/bandicam%202026-08-19%2006-32-43-461.mp4" type="video/mp4"></video>

**工作原理**(了解即可):`应用` 每帧执行一次你的构建函数,用返回的控件树完成 FlexBox 布局、悬浮/点击处理和绘制。状态变了,下一帧界面自然就是新的。

**按需重绘**(默认开):开启后静止界面不空耗帧——无动静(窗口无焦点、无触发UI效果:如动画、输入等)时 `应用` 只泵事件轮询,检测到脏源(状态变更/鼠标移动/左键按住/滚轮/动画进行中/输入栏聚焦)才执行完整的"重建→布局→绘制"帧。空闲 CPU 占用近零;持续动画的界面每帧都脏,行为与全速一致。如需**关闭按需重绘**回到全量渲染循环可使用 `{"按需重绘": 假}` 。

**异步任务不受主循环影响**:`应用`/`刷新` 的每个循环周期都会驱动 fiber 跑一拍(运行时 `xt_scheduler_step` 非阻塞单步泵),fiber 与线程池任务在 UI 主循环期间持续推进,主线程无需 `等待` 泵送;其他线程的 `态设` 会在下一个轮询周期(≤8ms)唤醒重绘。

---

## 窗口选项(`应用` 的第三参)

`UI.应用(状态, 构建函, 选项)` 的`选项`字典控制窗口本体:

| 键 | 默认 | 说明 |
|----|------|------|
| `宽` | 800 | 窗口宽(像素) |
| `高` | 600 | 窗口高(像素) |
| `标题` | `"XuanTie App"` | 标题栏文字(支持中文) |
| `背景` | `色(40, 40, 50, 255)`(深灰) | 整窗清底色,用 `UI.色(r,g,b,a)` 或 `UI.色16("#RRGGBB")` |
| `可调整` | 假 | 真 时窗口允许拖拽调大小;**尺寸变化自动按当前尺寸逐帧重排重绘**(变化即脏源) |
| `全屏` | 假 | 真 时进入全屏,并自动按真实屏幕尺寸布局(否则内容只占左上角 宽x高) |
| `无边框` | 假 | 真 时去掉标题栏与边框——同时失去系统最小化/关闭按钮,需自行提供退出途径 |
| `字体` | 内置鸿蒙字体 | TTF 路径,指定后自动加载/卸载 |
| `字库字号` | 32 | 字体栅格化字号,越大越清晰、纹理越大 |
| `按需重绘` | 真 | 静止界面不空耗帧;假 = 纯 60fps 全量循环 |

**想完全自己管窗口**(自定义清屏/每帧先画自己的东西):
用 `UI.刷新` 嵌入手动渲染循环(见第 6 节),此时窗口行为由你经 `R.*` 全权控制(`R.设窗口可调整`、`R.清背景`、`R.设窗口无边框`、`R.设窗口全屏` 等,详见 [09_渲染库编程指南](09_渲染库编程指南.md))。

### 示例
```xuantie
UI.应用(状态, 函(){
    返 UI.文本("Hello World !",{字号: 32, 字色: UI.色16("#000000")})
}, {
    宽: 960, 高: 600, 标题: "我的面板",
    背景: UI.色16("#F5F5F7"),
    可调整: 真,
    按需重绘: 假
})
```

<video controls style="max-width:100%;border-radius:10px"><source src="https://xuantie-community.cn-nb1.rains3.com/img/docs/bandicam%202026-08-19%2011-37-31-083.mp4" type="video/mp4"></video>

#### **无边框自绘标题栏三件套**(渲染库 v1.2.4):

| API | 说明 |
|-----|------|
| `R.最小化窗口()` | 最小化到任务栏 |
| `R.最大化窗口()` | 工作区最大化(**留出任务栏**——无边框窗是 WS_POPUP,系统 SW_MAXIMIZE 会盖满全屏含任务栏,故走工作区矩形放置) |
| `R.还原窗口()` | 还原到最大化前的位置尺寸 |
| `R.窗口最大化了?()` | 是否最大化(切换钮换图标依据;最大化状态库内跟踪) |
| `R.请求关闭窗口()` | 优雅关闭:投递 WM_CLOSE,主循环下一轮按正常路径退出(不帧中销毁上下文;与程序结束释放资源的 `关闭窗口()` 不同义) |
| [`R.拖拽窗口()`](UI/渲染/拖拽窗口.md) | 标题栏拖拽移动:在自绘标题栏的 `触发:"按下"` 回调里调用,按住期间窗口跟手移动,松手返回 |


### 示例
```xuantie
设 状态 = UI.造状态(空)

UI.应用(状态, 函(s) {
    返 UI.叠([
        // 自绘标题栏(双锚横拉,高 40;按下空白处即 Windows 原生拖拽移动)
        UI.叠([
            UI.行([
                UI.文本("  铸造厂(无边框演示)", {"字号": 14, "字色": UI.色彩["浅灰"]}),
                UI.弹性(1, {}),
                UI.图标按钮("./assets/_ic_min.svg", {"点击": 函() { R.最小化窗口() }}),
                UI.图标按钮("./assets/_ic_max.svg", {"点击": 函() {
                    若 R.窗口最大化了?() { R.还原窗口() } 否 { R.最大化窗口() }
                }}),
                UI.图标按钮("./assets/_ic_close.svg", {
                    "悬底色": UI.色(220, 60, 70, 255), "点击": 函() { R.请求关闭窗口() }
                })
            ], {"叉轴对齐": UI.叉中, "内边距": UI.四边(8, 4, 8, 4), "间距": 4,
                "触发": "按下", "点击": 函() { R.拖拽窗口() }})
        ], {"锚": {"左": 0, "上": 0, "右": 1}, "高": 40, "底色": UI.色(36, 40, 52, 255)})
        // 主区:图标按钮陈列 + 计数
    ], {})
}, {"宽": 640, "高": 360, "标题": "无边框演示", "无边框": 真, "可调整": 真})
```

<video controls style="max-width:100%;border-radius:10px"><source src="https://xuantie-community.cn-nb1.rains3.com/img/docs/bandicam%202026-08-21%2000-54-38-346.mp4" type="video/mp4"></video>

**注意**:标题栏空白区(含标题文字/弹性间隔)经 `触发:"按下"` 调 `拖拽窗口()` 即 Windows 手感拖拽;图标钮层级更高,点钮不触发拖拽。



---

## 状态系统

| API | 说明 |
|-----|------|
| [`UI.造状态(初始字典)`](UI/造状态.md) | 创建响应式状态容器 |
| [`UI.态取(状态, "键")`](UI/态取.md) | 读取状态(渲染期内自动登记依赖) |
| [`UI.态设(状态, "键", 新值)`](UI/态设.md) | 写状态;值变化时通知订阅者并标脏 |
| [`UI.态观(状态, "键", 回调)`](UI/态观.md) | 订阅某键的变化 |
| [`UI.态设批(状态, 变更表)`](UI/态设批.md) | 批量设置多键 |
| [`UI.态快照(状态)`](UI/态快照.md) | 取当前整棵值字典(不登记依赖,引用非深拷贝) |
| [`UI.态脏?(状态)`](UI/态脏.md) | 查询自上次构建以来是否被标脏 |
| [`UI.标脏(状态)`](UI/标脏.md) | 手动标脏(原地改数组/字典后通知重绘) |
| [`UI.态依赖(状态)`](UI/态依赖.md) | 取上次构建期内被读取过的键列表 |
| [`UI.态清除(状态)`](UI/态清除.md) | 清空订阅、依赖记录与脏标志(业务数据保留) |

---

## 控件详解

每个控件/容器都是 `UI.XX(...)` 构造的节点,末参为选项字典(通用键见 3.12)。本节逐控件给出参数、专属选项与示例。

### 列(竖排容器)

`UI.列(子列表, 选项)` —— 子项自上而下排列,是最常用的布局根。

```xuantie
UI.列([
    UI.文本("标题", {字号: 22}),
    UI.按钮("确定", {点击: 函() { ... }}),
], {内边距: UI.全边(16), 间距: 12})
```

要点:`间距` 控制子项间隙;`弹性` 子项会瓜分剩余高度;自身可带 `底色`/`圆角`/`内边距`。

### 行(横排容器)

`UI.行(子列表, 选项)` —— 子项自左而右排列。选项与 列 相同(主轴换成横向)。

```xuantie
UI.行([
    UI.文本("名字:", {}),
    UI.输入栏(s, "名字", {弹性: 1}),
], {间距: 8, 叉轴对齐: UI.叉中})
```

### 叠(层叠容器)

`UI.叠(子列表, 选项)` —— 子项在同一区域层叠(后画的盖先画的),用于背景+前景、角标等。

```xuantie
UI.叠([
    UI.矩形({底色: UI.色16("#1E2430"), 圆角: 10}),
    UI.文本(盖在最上层, {x: 0, y: 0}),
], {宽: 200, 高: 80})
```

### 绝对(绝对布局容器)

`UI.绝对(子列表, 选项)` —— 子项带 `x`/`y` 选项按坐标直摆(相对容器内容区左上角),完全跳过 flex 测量。

```xuantie
UI.绝对([
    UI.文本("左上角", {x: 16, y: 12}),
    UI.按钮("确定", {x: 220, y: 110, 点击: 函() { ... }}),
], {弹性: 1, 底色: UI.色16("#1E2430"), 圆角: 10})
```

**两个注意**:① 绝对容器**不裁剪**——越界子项会画出容器外;② 坐标是死的,不随窗口缩放自适应——要自适应定位请用 [锚点布局](#锚点布局)(顶栏/侧栏/状态栏/居中钮一次写清),弹性拉伸请用 行/列+弹性。

### 文本

`UI.文本("内容", 选项)` —— 静态文字。常用键:`字号`(默认 16)、`字色`。

```xuantie
UI.文本("计数: " & UI.态取(s, "计数"), {字号: 18, 字色: UI.色彩["黄"]})
```

### 按钮

`UI.按钮("标签", 选项)` —— 点击按钮。专属键:

| 键 | 含义 |
|----|------|
| `点击` | 回调:`{"点击": 函() { ... }}` |
| `触发` | `"点击"`(默认,按下并在钮内松开才触发)/`"按下"`(按下即发)/`"按住"`(按住连发) |
| `连发间隔` | 按住模式连发间隔毫秒(默认 50;0=每帧) |
| `首按延迟` | 按住超时开始连发的延迟(默认 0)。点击模式设了它:按住超时进连发、延迟内松开仍单击——单击/长按两用 |
| `禁用` | 真 时不响应交互并显示禁用色 |
| 三态色 | `悬底色`/`按底色`/`禁底色` + `悬字色`/`按字色`/`禁字色` |
| 三态边色 | `悬边色`/`按边色`/`禁边色`(配合通用键 `边框色`/`边框宽` 使用) |
| `悬浮上浮` | 悬浮时上浮像素(默认 2;0=关闭);悬浮同时自动开启并渐增阴影,形成立体感 |
| `按下缩放` | 按住时缩放百分(默认 95;100=关闭);松开以回弹曲线复原(灵动摁感) |

**状态子字典**(CSS 伪类风):`悬浮`/`按下`/`禁用` 三个键各装一个状态的覆写,子键为 `底色`/`字色`/`边色`,与平面键可混用。选项键支持裸标识符写法(见[手册 6.5.1 节](手册/06_标准库与RuntimeAPI.md)),引号写法继续可用:

```xuantie
UI.按钮("点我 +1", {
    宽: 240, 圆角: 8,
    边框色: UI.色彩["灰"], 边框宽: 2,
    悬浮: {边色: UI.色16("#ffd700")},
    按下: {底色: UI.色彩["绿"]},
    点击: 函() { UI.态设(s, "计数", UI.态取(s, "计数") + 1) }
})
UI.按钮("按住连发", {触发: "按住", 首按延迟: 300, 连发间隔: 100, 点击: 函() { ... }})
```

**按下态的登记规则**:按下态跟随按压源——按压沿只登记给**带 `点击` 回调**的节点。无回调的按钮悬浮提亮仍在,但按下无视觉反馈、松开也不触发(适合纯装饰钮;要可点就必须给 `点击`)。

### 矩形

`UI.矩形(选项)` —— 色块。常用键:`宽`/`高`/`底色`/`圆角`。

```xuantie
UI.矩形({"宽": 120, "高": 64, "底色": UI.色16("#3B82F6"), "圆角": 8})
```

### 空白 / 弹性

`UI.空白(选项)` 占位(默认 弹性1);`UI.弹性(弹值, 选项)` 按系数挤占剩余空间。

```xuantie
UI.行([UI.文本("左", {}), UI.弹性(1, {}), UI.文本("右", {})])   // 两端贴边
```

### 输入栏

`UI.输入栏(状态, 键, 选项)` —— 单行文本输入框。值直接绑定 `状态[键]`(读写双向),无需回调。

| 参数 | 含义 |
|------|------|
| `状态` | `UI.造状态` 的容器 |
| `键` | 绑定到状态容器的键名(字符串) |

**专属选项键**:

| 键 | 含义 |
|----|------|
| `占位` | 空值时灰色显示的提示文本 |
| `占位色` | 占位文本颜色(默认暗灰) |
| `只读` | 真 时不可编辑(仅展示/可选中复制) |
| `提交` | 回车回调:`{"提交": 函(值) { ... }}` |
| `焦框色` | 获焦时边框高亮色(默认亮蓝) |
| `焦框宽` | 获焦时边框厚度(默认 2) |
| `左图标`/`右图标` | 栏内左/右侧图标:图片路径字符串,支持 png/bmp/gif 与 **svg**(`.svg` 后缀自动走 nanosvg 光栅化;不支持 jpg/tga,详见渲染指南) |
| `图标尺寸` | 图标边长(像素);0 或省略 = 自动(栏内净高) |
| `图标距` | 图标与文本区间的间距(默认 6) |
| `图标色` | 图标着色 tint(默认白色 = 原图原色) |
| `右图标点击` | 右图标点击回调(典型用法:一键清空);命中右图标区不会移动光标/触发框选 |
| 通用 | `宽`(默认 240)/`高`/`字号`/`字距`/`底色`/`底色2`/`渐变向`/`羽化`/`阴影`/`字色`/`框色`/`边框宽`/`圆角`/`内边距` |

带图标文本区自动内缩;**长文本滚动被裁剪在文本区内,永远不会遮挡左右图标**。图标纹理按 `路径@尺寸` 缓存,常驻进程生命周期。

**交互全集**(免配置开箱即用):

- 鼠标:点击获焦(按点击处定位光标)、按住拖动框选、点外/ESC 失焦;悬浮栏上鼠标自动变 I 形文本光标(禁用时不变)
- 键盘:字符输入(**中文 IME 上屏正常,候选窗跟随光标**)、退格(按住连删)、左右方向键(按住连移)、上=到首/下=到尾、回车=提交
- 组合键:Ctrl+A 全选、Ctrl+C 复制、Ctrl+X 剪切、Ctrl+V 粘贴(自动去换行)
- 光标闪烁;内容超宽时横向滚动保持光标可见
- IME 约定:无输入栏焦点时输入法整体禁用(打字不弹候选窗);组合中点击**候选条带**由输入法消化(Windows 标准行为),点击条带之外的空白一击失焦

```xuantie
设 状态 = UI.造状态({"名字": ""})
UI.输入栏(状态, "名字", {"占位": "请输入名字(支持中文)...", "提交": 函(v) { 示("提交了: " & v) }})

// 图标 + 阴影 + 渐变圆角
UI.输入栏(状态, "搜索", {
    "占位": "搜索…",
    "左图标": "assets/search.svg",
    "右图标": "assets/clear.svg",
    "右图标点击": 函() { UI.态设(状态, "搜索", "") },
    "阴影": {"颜色": UI.色(0, 0, 0, 160), "模糊": 10, "y": 3},
    "圆角": 14, "底色": UI.色(40, 60, 120, 255), "底色2": UI.色(120, 40, 160, 255), "渐变向": "横"
})
```

**注意**:单行性——粘贴内容会被去掉换行;多行文本请用 [多行编辑](#多行编辑) 控件;暂不支持 Home/End、Shift 选择。

### 滚动容器

`UI.滚动容器(子列表, 选项)` —— 子项按列排布,内容超出视口高度时可滚轮滚动或拖拽滚动条,可视区自动裁剪。

| 键 | 含义 |
|----|------|
| `高` | 视口高度;不设则吃布局可用高(可配合 `弹性` 填满剩余空间) |
| `滚动条` | 默认 真;**内容不溢出时自动隐藏且不占位**(空容器同样无滚动条) |
| `条宽` | 滚动条宽度像素(默认 6) |
| `轨道色` | 轨道底色(默认 白,16/255 透明度) |
| `拇指色` | 滑块色(默认 白,90/255) |
| `拇指拖色` | 拖拽中的滑块色(默认 白,150/255) |

```xuantie
UI.滚动容器(条目列表, {
    高: 220, 宽: 300, 间距: 8,
    底色: UI.色(24, 27, 34, 255), 圆角: 8, 内边距: 10,
    条宽: 10, 拇指色: UI.色16("#FFAA3C"), 拇指拖色: UI.色16("#FF7828")
})
```

**交互规则**:

- 悬浮在容器上时滚轮滚动(丝滑补间,一格 40px);滚出视口的子项**不响应**悬浮与点击
- 滚动条**只有按住滑块(拇指)才拖得动**,拖拽中内容钉死跟随;点轨道空白不翻页
- 按下态跟随按压源:按住左键从别处扫过容器内按钮,按钮不会误显示按下态

**注意**:仅纵向滚动(无横向);内容高按子项列总高计算;暂无"滚动到指定位置"API;嵌套滚动容器尚未验证,暂不建议。

### 图片

`UI.图片(源, 选项)` —— 显示一张图片(png/bmp/gif,`.svg` 自动光栅化;不支持 jpg/tga,详见渲染指南)。固有尺寸取纹理原尺寸,不设 `宽`/`高` 即原大;设了则缩放到指定矩形。配 `点击` 即成图片按钮。

| 键 | 含义 |
|----|------|
| `源`(首参) | 图片路径;**状态覆写可换源**(见下) |
| `宽`/`高` | 显示尺寸(默认纹理原尺寸) |
| `图标色` | 着色 tint(默认白=原图) |
| `不透明度` | 0~100;0 = 不可见且**不响应命中**(点击穿透到下层) |
| `点击` | 回调:`{"点击": 函() { ... }}`,触发模式同按钮 |

**状态覆写**(全控件通用,v1.5.0 起):`悬浮`/`按下`/`禁用` 三个子字典不再只限颜色——**任意选项键都可按状态覆写**,解析顺序 禁用 > 按下 > 悬浮 > 常态。颜色三态继续可用平面键(`悬底色` 等)与子字典混写:

```xuantie
// 悬浮换图,按下换图 + 缩小 + 半透
UI.图片("a.png", {
    宽: 96, 高: 96,
    悬浮: {源: "b.png"},
    按下: {源: "c.png", 宽: 72, 高: 72, 不透明度: 60},
    点击: 函() { ... }
})
```

**两个注意**:① 宽/高覆写经上一帧悬浮/按压身份在布局期解析(一帧滞后,无感);② 不透明度对整棵子树连乘,渐变背景经绘制 tint 折算(不重建纹理)。

**组合示例:输入栏右侧"有字才浮现"的清除按钮**——`叠` 覆盖 + `动值` 驱动不透明度:

```xuantie
UI.叠([
    UI.输入栏(状态, "框", {宽: 340, 占位: "输入文字"}),
    UI.行([
        UI.弹性(1, {}),
        UI.图片("clear.png", {
            宽: 18, 高: 18, 图标色: UI.色彩["浅灰"],
            点击: 函() { UI.态设(状态, "框", "") },
            不透明度: UI.动值("清钮", 若 UI.态取(s, "框").字符数 > 0 { 100 } 否 { 0 })
        })
    ], {叉轴对齐: UI.叉中, 内边距: UI.四边(0, 12, 0, 0)})
], {})
```

### 图标按钮

`UI.图标按钮(源, 选项)` —— 纯图标按钮(无文本标签):一张图标(png/bmp/gif/svg,jpg/tga 不支持)居中,默认透明底,悬浮淡亮、按下加亮,禁用时图标降透 40%。无边框自绘标题栏三件套(最小化/最大化/关闭)的官配控件。

| 参数 | 含义 |
|------|------|
| `源`(首参) | 图标路径;**状态覆写可换源**(悬浮/按下/禁用 子字典) |

**专属选项键**:

| 键 | 含义 |
|----|------|
| `宽`/`高` | 按钮尺寸(默认 28×28) |
| `图标尺寸` | 图标边长(默认 0 = 自动,净区最大边) |
| `图标色` | 图标着色 tint(默认白 = 原图原色) |
| `悬底色` | 悬浮底色(默认 白,24/255) |
| `按底色` | 按下底色(默认 白,44/255) |
| `禁底色` | 禁用底色(默认透明) |
| 通用 | `圆角`(默认 6)/`内边距`(默认 4)/`禁用`/`点击`/`触发`/`连发间隔`/`首按延迟`(交互与按钮同套) |

```xuantie
// 关闭钮:悬浮变红
UI.图标按钮("close.svg", {悬底色: UI.色(220, 60, 70, 255), 点击: 函() { R.请求关闭窗口() }})

// 圆形大钮 + 禁用态
UI.图标按钮("setting.svg", {宽: 36, 高: 36, 圆角: 18, 图标尺寸: 20, 点击: 函() { ... }})
UI.图标按钮("setting.svg", {禁用: 真})
```

**注意**:图标纹理按 `路径@尺寸` 缓存常驻;按下态跟随按压源(带 `点击` 回调才登记,见 [按钮](#按钮) 的按下态登记规则)。

### 多行编辑

`UI.多行编辑(状态, 键, 选项)` —— 多行文本编辑器(铸造厂 CodeEdit 地基)。值直接绑定 `状态[键]` 整段字符串(`\n` 分行,读写双向),无需回调。

| 参数 | 含义 |
|------|------|
| `状态` | `UI.造状态` 的容器 |
| `键` | 绑定到状态容器的键名(字符串) |

**专属选项键**:

| 键 | 含义 |
|----|------|
| `占位` | 空值时灰色显示的提示文本 |
| `占位色` | 占位文本颜色(默认暗灰) |
| `行号` | 默认 真;`行号: 假` 关闭左侧行号槽 |
| `只读` | 真 时不可编辑(仅展示/可选中复制) |
| `焦框色` | 获焦时边框高亮色(默认亮蓝) |
| `焦框宽` | 获焦时边框厚度(默认 2) |
| `高亮` | `"玄铁"` 开启语法高亮:关键字/字符串/注释/数字逐段着色 |
| `高亮色` | 调色板覆写:`{"高亮色": {"关键字": UI.色(...), "字符串": ..., "注释": ..., "数字": ...}}`(只写要改的类) |
| 通用 | `宽`(默认 420)/`高`(默认 120)/`字号`/`字距`/`底色`/`字色`/`框色`/`边框宽`/`圆角`/`内边距` |

**交互全集**(免配置开箱即用):

- 鼠标:点击获焦(按点击处定位到行列)、按住拖动框选(允许拖出边界)、滚轮滚动(一格 40px)、拖拽滚动条拇指、点外/ESC 失焦;悬浮栏上鼠标自动变 I 形文本光标
- 键盘:字符输入(**中文 IME 上屏正常,组合串内联显示在光标处**)、退格/方向键(按住连发)、回车换行、Tab 插 4 空格、Home/End 行首行尾
- 组合键:Ctrl+A 全选、Ctrl+C 复制、Ctrl+X 剪切、Ctrl+V 粘贴(**保留换行**)
- 光标 500ms 闪烁;内容超视高时出现滚动条,滚动偏移自动夹取

```xuantie
设 状态 = UI.造状态({"源码": "// 在此写代码\n示(1)\n"})
UI.多行编辑(状态, "源码", {
    "宽": 640, "高": 300, "占位": "在此输入代码...",
    "字号": 15, "圆角": 8
})

// 无行号窄栏
UI.多行编辑(状态, "备注", {"宽": 640, "高": 90, "行号": 假, "占位": "备注..."})
```

**注意**:横向不滚动(超长行会被裁剪在文本区内);选区为字符索引模型(跨行选区高亮逐行求交);语法高亮经 `"高亮": "玄铁"` 开启,IME 组合中的光标行暂走纯文本通路(上屏即恢复高亮)。

## 锚点布局

全控件/容器通用的定位系统:节点按**比例+像素偏移**钉在父容器给定的槽位内,窗口调大小自动跟随。两个选项键:

| 键 | 含义 |
|----|------|
| `锚` | 子字典 `{"左": 比, "上": 比, "右": 比, "下": 比}`(0~1,任意子集;缺省=该侧不锚) |
| `偏` | 子字典像素偏移 `{"左": px, "上": px, "右": px, "下": px}`(默认 0,可负)。**正值把该侧边向外推**(远离锚边):`左`/`上` 正=向右/向下,`右`/`下` 正=向右/向下探出。贴边上移须写负值,如底部让位 68px 写 `"下": -68` |

**逐轴规则**(X/Y 各自独立判定):

- **双锚**(同轴两侧都设)= 拉伸:`起点 = 槽位起点 + 槽位尺寸×近侧比 + 近侧偏`,`尺寸 = 槽位尺寸×(远比-近比) + 远侧偏 - 近侧偏`——宽/高随父容器变
- **单锚**(只设一侧)= 定点 + 固有/选项尺寸:比值 `<0.5` 前向长(锚点是起点)、`>0.5` 后向长(锚点是终点)、`=0.5` 居中
- **无锚** = 维持现有 Flex/容器排布(默认行为,完全兼容旧代码)

**容器协同**:

- `叠` 内锚定子给满内容区槽位——IDE 壳(顶栏/侧栏/主区/状态栏)直接锚,无需 `绝对` 手算坐标
- `列`/`行` 内锚定子仍占 Flex 流位,在其格内锚定(配合 `弹性` 撑开的格,可做格内左/中/右对齐)
- `外边距` 不适用(锚/偏已含定位语义);`平移x/平移y` 仍叠加——`动值` 驱动即可做面板滑入滑出

**示例**
> IDE 壳四区 + 居中钮,窗口可调整,各区随窗口拉伸
```xuantie
引 "UI" 予 UI
引 "渲染" 予 R

设 状态 = UI.造状态({})

函 面板(标题, 底色, 选项) {
    设 o = {"底色": 底色, "圆角": 0, "内边距": UI.全边(10)}
    遍历 kv 于 选项 { o[kv] = 选项[kv] }
    返 UI.叠([
        UI.文本(标题, {"字号": 15, "字色": UI.色彩["浅灰"]})
    ], o)
}

UI.应用(状态, 函(s) {
    设 次 = UI.态取(s, "次")
    若 次 == 空 { 次 = 0 }
    返 UI.叠([
        面板("顶栏(双锚横拉, 高40)", UI.色(50, 56, 68, 255), {"锚": {"左": 0, "上": 0, "右": 1}, "高": 40}),
        面板("侧栏(宽220, 上下拉伸)", UI.色(38, 44, 56, 255), {"锚": {"左": 0, "上": 0, "下": 1}, "宽": 220, "偏": {"上": 40, "下": -28}}),
        面板("主区(四锚拉伸, 偏出让位)", UI.色(30, 34, 44, 255), {"锚": {"左": 0, "上": 0, "右": 1, "下": 1}, "偏": {"左": 220, "上": 40, "右": 0, "下": -28}}),
        UI.叠([
            UI.文本("状态栏(底锚后向, 高28) — 点击计数: " & 字(次), {"字号": 13, "字色": UI.色彩["浅灰"]})
        ], {"锚": {"左": 0, "右": 1, "下": 1}, "高": 28, "底色": UI.色(46, 52, 64, 255), "内边距": UI.全边(6)}),
        UI.按钮("居中钮(0.5居中)", {"锚": {"左": 0.5, "上": 0.5}, "宽": 140, "高": 36, "点击": 函() { UI.态设(s, "次", 次 + 1) }}),
        UI.按钮("右上钮(右锚后向)", {"锚": {"右": 1, "上": 0}, "宽": 110, "高": 28, "偏": {"右": -8, "上": 46}, "点击": 函() { UI.态设(s, "次", 次 + 1) }})
    ], {})
}, {"宽": 800, "高": 500, "标题": "锚点演示", "可调整": 真})
```
<video controls style="max-width:100%;border-radius:10px"><source src="https://xuantie-community.cn-nb1.rains3.com/img/docs/bandicam%202026-08-20%2009-02-42-179.mp4" type="video/mp4"></video>

**注意**:锚值/偏值暂不支持状态覆写(悬浮/按下/禁用 子字典不含 锚/偏);单锚轴的尺寸取 `宽`/`高` 选项,缺省回退固有尺寸(文本/按钮按内容,容器按子树)。

## 通用选项键速查

所有控件/容器的选项字典通用键(
    
**颜色值**:
 - `UI.色(r,g,b,a)` (RBG颜色值)
 - `UI.色16("#RRGGBB")`) (十六进制颜色值)

**颜色·背景三态**:
 - [`底色`](UI/选项键/底色.md)(背景颜色)
 - [`悬底色`](UI/选项键/悬底色.md)(鼠标悬浮于控件时的背景颜色)
 - [`按底色`](UI/选项键/按底色.md)(鼠标按住时)
 - [`禁底色`](UI/选项键/禁底色.md)(禁用时)

**颜色·文字三态**:
 - [`字色`](UI/选项键/字色.md)(字体颜色。`颜色` 同义)
 - [`悬字色`](UI/选项键/悬字色.md)
 - [`按字色`](UI/选项键/按字色.md)
 - [`禁字色`](UI/选项键/禁字色.md)

**外观增强**:
 - [`底色2`](UI/选项键/底色2.md)(渐变第二色,设置后背景变为 底色→底色2 渐变)
 - [`渐变向`](UI/选项键/渐变向.md)(`"横"`/`"竖"`,默认 `"竖"`)
 - [`羽化`](UI/选项键/羽化.md)(边缘羽化像素,1 = 仅抗锯齿,>1 产生柔边)
 - [`阴影`](UI/选项键/阴影.md)(字典 `{"颜色":色,"模糊":像素,"x":0,"y":2}`,默认关闭)
 - [`字距`](UI/选项键/字距.md)(字符间距像素,默认 1)
 - [`不透明度`](UI/选项键/不透明度.md)(0~100,子树连乘;0 = 不可见且不响应命中)

**尺寸布局**:
 - [`宽`](UI/选项键/宽.md)/[`高`](UI/选项键/高.md)(不写由内容撑开)
 - [`弹性`](UI/选项键/弹性.md)(flex-grow)
 - [`主轴对齐`](UI/选项键/主轴对齐.md)/[`叉轴对齐`](UI/选项键/叉轴对齐.md)
 - [`间距`](UI/选项键/间距.md)
 - [`内边距`](UI/选项键/内边距.md)/[`外边距`](UI/选项键/外边距.md)
 - [`x`](UI/选项键/x.md)/[`y`](UI/选项键/y.md)(仅绝对容器内)

**锚点定位**(详见 [锚点布局](#锚点布局)):
 - [`锚`](UI/选项键/锚.md)(Godot 风比例锚点:`{"左":比,"上":比,"右":比,"下":比}`,双锚拉伸/单锚定点/0.5 居中)
 - [`偏`](UI/选项键/偏.md)(锚点像素偏移,可负)

**文字形状**:
 - [`字号`](UI/选项键/字号.md)
 - [`圆角`](UI/选项键/圆角.md)
 - [`边框色`](UI/选项键/边框色.md)/[`边框宽`](UI/选项键/边框宽.md)
  
**交互**:
 - [`点击`](UI/选项键/点击.md)
 - [`触发`](UI/选项键/触发.md)
 - [`连发间隔`](UI/选项键/连发间隔.md)
 - [`首按延迟`](UI/选项键/首按延迟.md)
 - [`禁用`](UI/选项键/禁用.md)
 - [`可视`](UI/选项键/可视.md)
  
**变换与裁剪**:
 - [`平移x`](UI/选项键/平移x.md)/[`平移y`](UI/选项键/平移y.md)(像素,可负;渲染与命中间步偏移,不影响兄弟布局,`动值` 驱动即滑入滑出)
 - [`裁剪`](UI/选项键/裁剪.md)(容器:子树超出边界即裁,界外不响应交互)

**状态覆写**:
 - [`悬浮`](UI/选项键/悬浮.md)/[`按下`](UI/选项键/按下.md)/[`禁用`](UI/选项键/禁用.md) 子字典内可写**任意选项键**(颜色三态亦可平面键/子字典混写),按状态解析,见 3.11

**边距写法三选一**:`{"内边距": 20}`(裸数字自动转全边)、`UI.全边(20)`、`UI.四边(8,16,8,16)`。
**对齐常量**:主轴 `UI.对齐始/对齐中/对齐末/对齐间/对齐均/对齐均等`;叉轴 `UI.叉始/叉中/叉末/叉撑`。
**颜色常量**:`UI.色彩["白"/"黑"/"红"/"绿"/"蓝"/"黄"/"灰"/"深灰"/"浅灰"/"透明"/"主题"/"强调"]`。

---

## 组件动画(贝塞尔缓动)

UI 库内建声明式动画:每帧把"目标值"喂给 `UI.动值`,目标变化当帧起即从当前显示值平滑补间过去。数值与颜色(r/g/b/a 字典)皆可动画。

**两个 API**:

| API | 含义 |
|-----|------|
| `UI.造动画(键, 选项)` | 注册动画:`{"时长": 毫秒, "曲线": "丝滑"}`;幂等,可反复调用改配置 |
| `UI.动值(键, 目标值)` | 返回当前帧插值;目标变化自动从当前值起播(中途变向也顺滑) |

**曲线预设**(cubic-bezier):`线性` / `丝滑`(默认,ease-in-out 体感) / `缓入` / `缓出` / `回弹`(带过冲)。

规则与语义:

- 键全局唯一(建议带模块前缀,如 `"设置面板.x"`)
- 首帧不播:第一次喂目标值时直接钉住(避免开屏乱飞)
- `时长: 0` 即关闭动画,立即到位
- 动画状态存全局侧表,与节点每帧重建无关——键在,动画状态就在

```xuantie
UI.造动画("面板x", {"时长": 300, "曲线": "丝滑"})
// 构建函里:
UI.矩形({"x": UI.动值("面板x", 目标x), ...})   // 目标x 变化时自动滑过去
```

**内建缓动**(免配置):按钮悬浮/按下/禁用的底色与字色、输入栏聚焦边框色,状态切换时自动丝滑过渡(120~150ms)。官方组件内建动效一律使用「丝滑」曲线。

---

## 中文字体

UI 库默认字体**优先走 GDI 系统字体(微软雅黑)**:Windows 系统级灰阶抗锯齿(与记事本中渲染的字体完全同质感)、按需栅格化零启动成本、生僻字由系统字体引擎自动回落——**不写任何字体代码,中文即开即显且免字体文件**(一般来讲默认支持中文字体渲染应该是标配,更别说中文编程语言。但因为底层使用raylib,所以是一个相对特殊的存在)。

GDI 不可用时的回退链:内置鸿蒙字体(HarmonyOS Sans,按 铁铺已安装包 assets → `XUANTIE_HOME` 安装目录 → 程序自身目录 assets 查找)→ raylib 默认字体(仅 ASCII,中文会显示为问号并打印一行提示)。

```xuantie
UI.应用(状态, 构建函, {"宽": 640, "高": 420, "标题": "我的应用"})
// 无需 "字体" 选项,中文自动正常
```

**显式指定**(覆盖默认):`应用` 选项传 `"字体": "你的.ttf"`,手动渲染循环里用 `UI.设字体(路径, 字库字号)`(**必须在窗口初始化之后**调用,加载字形纹理需要 GL 上下文);或直接 `UI.设字体` 传 `R.加载系统字体("微软雅黑", 32)` 的句柄指定其他系统字体(如 "宋体"/"Consolas")。

- `字库字号`:栅格化字号(默认 32),越大越清晰、纹理越大。
- ttf 路径的全 CJK 字体纹理较大(数千字形),首次加载约 1-2 秒;框架会先画一帧背景再加载,避免白窗无响应感。

---

## 手动渲染循环(嵌入自定义绘制)

不用 `应用` 的主循环时,用 `刷新` 把 UI 嵌入你自己的帧循环:

```xuantie
引 "UI" 予 UI
引 "渲染" 予 R

R.初始化窗口(640, 420, "手动循环")
UI.设字体("HarmonyOS_Sans_SC_Regular.ttf", 32)
设 状态 = UI.造状态({})

当 非 R.窗口应关闭?() {
    R.开始绘图()
    R.清背景(40, 40, 50, 255)
    // 你自己的绘制...
    UI.刷新(状态, 函(s) { 返 UI.列([UI.文本("嵌入UI", {})], {}) }, 0, 0, 640, 420)
    R.结束绘图()
}
R.关闭窗口()
```

---

## 使用禁区与常见错误

| 错误用法 | 后果 | 正确做法 |
|----------|------|----------|
| `引 "UI" 予 UI` 后又 `引 "UI" 予 界面` | **编译报错**:同一模块只能用一个别名 | 统一别名 |
| 对 `空` 或数字做索引(如拿错类型的选项值) | **运行时明确报错**退出(索引守卫) | 按上表写选项;读取返回值前判空 |
| 在窗口初始化前 `UI.设字体` | 字体加载失败(无 GL 上下文) | 用 `应用` 的 `"字体"` 选项,或在 `初始化窗口` 之后调用 |
| 回调里直接改界面节点 | 无效——下一帧整棵树会被重建 | 只改状态(`态设`),界面跟着状态走 |
| 把控件节点存进变量,再挂进多个容器(双挂) | **运行时明确报错退出**(双挂守卫:同一实例一帧只允许布局一次,否则坐标被后挂载点覆盖、先挂载点内容全不可见) | 每帧在构建函数内现场调用构造函数;要复用就抽成 `函 造XX()` 每次调用造新实例 |
| `返 [w, h]` 这类直接返回字面量 | 旧版编译器会**静默返回空**;已修复 | 请升级到最新版编译器 |

---

## 能力边界

- 控件集:文本/按钮/矩形/容器/空白/弹性/输入栏/滚动容器/图片/多行编辑。**尚无**滑块、复选框(排期中)。
- 输入栏为单行:不支持多行、Home/End、Shift 选择;IME 候选窗跟随光标已支持(Windows)。
- 每帧整树重建,简单直接;**按需重绘**已让静止界面零耗帧,但大型界面(上千控件)每帧全量重建的增量更新仍是后续优化方向。
- 圆角矩形已支持(`圆角` 选项);文本无复杂排版(无富文本/多字体混排)。
- 已知问题与修复进度见项目仓库 issue/更新日志。

---

*上一篇:[11_多线程开发深度教程.md](./11_多线程开发深度教程.md)* | *相关:[09_渲染库编程指南.md](./09_渲染库编程指南.md)*