# Ebox 用户指南 [English](ebox-user-guide.en.md) Ebox 是底层 Text/Box 布局与 buffer 渲染包。本指南只使用一套公共 author 语法, 并把框架集成接口单独说明。完整函数清单见[公共 API 参考](ebox-api-reference.zh.md)。 ## 1. 加载 Ebox ```elisp (require 'ebox) ``` 加载 Ebox 不会创建 buffer、在当前 buffer 启用 mode,也不会构建 Rust。 ## 2. 只学习一个 author 模型 Ebox 文档由 Text 和 Box 节点组成。author 语法只有七个入口: | 入口 | 含义 | | --- | --- | | `"text"` | 一个 Text 节点的简写。 | | `(text ... "text")` | 带显式文本 property 的 Text。 | | `(box ... CHILD...)` | 普通视觉 Box。 | | `(row ... CHILD...)` | 使用简单横向子布局的 Box。 | | `(column ... CHILD...)` | 使用简单纵向子布局的 Box。 | | `(flex ... CHILD...)` | 使用 Flex 子布局的 Box。 | | `(grid ... CHILD...)` | 使用 Grid 子布局的 Box。 | 子节点始终直接嵌套。布局 form 只是选择了子布局算法的 Box,不是另一种视觉对象。 ```elisp (defvar ebox-guide-input (ebox-build '(column :padding (1 2) :border (1 solid "#8A93A6") (text :color "#263244" "Research notes") (row :item-gap 1 (box :id "status" :background-color "#F4F6FB" "Inbox") (box :background-color "#EEF2FF" "Archive"))))) ``` `ebox-build` 返回不透明的 `CanonicalEboxInput`,把 canonical forest 与对应的 source generation 保持为一个整体。普通 author 代码只需把这个值原样传给渲染和发布函数, 不要取出或重新组装其中的内部 node。 需要空矩形区域时,使用只有几何 property、没有子节点的 `box`,不需要额外节点类型。 ## 3. 用布局名称直接表达意图 普通内容用 `box`;直接的一维组合用 `row` 或 `column`;需要分配剩余空间或换行时 用 `flex`;需要二维轨道或显式放置时用 `grid`。 ### Row 与 column `row` 和 `column` 接受 `:item-gap` 与 `:cross-align`。它们的子节点仍然只是 Text 或 Box。 ```elisp (ebox-build '(row :item-gap 2 :cross-align center (box :width 12 "Left") (box :width 12 "Right"))) ``` ### Flex Flex 容器 property 属于 `flex`。participation property 直接属于子 `box`,因为它 描述的是父子关系。 ```elisp (ebox-build '(flex :width (480) :flex-flow (row wrap) :gap (1 (12)) (box :flex (1 1 auto) "Primary") (box :flex-grow 2 "Secondary"))) ``` ### Grid Grid 放置从 1 开始。轨道可以是固定值、`auto`、分数、`minmax` 或 `repeat`。 placement property 同样直接属于子 `box`。 ```elisp (ebox-build '(grid :width (640) :grid-template-columns ((200) (fr 1) (fr 1)) :grid-template-rows (1 1) :gap (1 (12)) (box :grid-column (1 :span 3) "Header") (box :grid-column 1 :grid-row 2 "Navigation") (box :grid-column 2 :grid-row 2 "Main") (box :grid-column 3 :grid-row 2 "Aside"))) ``` ## 4. 使用几何与绘制 property 普通横向数字表示字符列,`(240)` 这样的单元素 list 表示像素宽度;纵向数字表示 行数。Text 只接受字体、前景/背景和文本装饰 property;padding、margin、border、 尺寸、`:outer`、overflow、可见性和换行策略只属于 Box。Box 上的字体与颜色可以 作为其 Text 子树的继承来源,但不会让 Text 获得 Box 几何。 ```elisp (ebox-build '(box :width (420) :padding (1 2) :margin (0 1) :border (1 solid "#8A93A6") :color "#263244" :background-color "#FFFFFF" "A readable panel")) ``` 用 `:outer inline` 或 `:outer block` 表达 Box 如何参与父布局。子布局算法仍由 form 名称决定。 ## 5. 渲染与发布 `ebox-render` 返回带属性文本,不修改 live buffer;`ebox-render-to-buffer` 挂载 retained TP surface 并返回 buffer。它们和 `ebox-display-buffer` 都接收 `ebox-build` 返回的不透明值。 ```elisp (ebox-render ebox-guide-input) (ebox-render-to-buffer "*Ebox Guide*" ebox-guide-input) ``` 重新构建 canonical input 后,用 `ebox-commit` 原子更新: ```elisp (ebox-commit "*Ebox Guide*" (ebox-build '(column :padding (1 2) (text "Updated notes") (box :key body "The new input is caller-owned.")))) ``` 验证、渲染或发布失败时,旧 buffer 与 runtime 状态保持不变。 `ebox-buffer-update-report` 返回最近一次成功更新报告的防御性副本。 ## 6. 查询和更新 mounted surface `:id`、`:class` 与 `:key` 是 author metadata。Selector 使用 ECSS 语义。直接更新 前,把 `:id` 解析为不透明、surface-scoped 的 handle: ```elisp (let ((handle (ebox-region-resolve "*Ebox Guide*" "status"))) (ebox-region-update handle :color "#166534")) ``` `ebox-selector-query-buffer` 按文档顺序返回 mounted buffer 中的匹配; `ebox-selector-update-buffer` 对所有可编辑匹配应用一次样式更新。数字 region id 只是诊断用渲染 metadata,不是稳定更新 handle。 ## 7. Resize 与滚动 有限高度加 `:overflow scroll` 会创建 scroll window。Ebox buffer mode 安装键盘和 滚轮命令:先交给最内层 scroll owner,无法继续消费时才回退到普通 Emacs 滚动。 ```elisp (ebox-build '(box :id log :width (420) :height 8 :overflow scroll "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9")) ``` 可见 mounted buffer 会跟随其 display window。集成层也可以显式应用 viewport: ```elisp (ebox-rerender-buffer-with-context (get-buffer "*Ebox Guide*") 800 30) ``` ## 8. 独立 `.ebox` 文件 `.ebox` 文件包含一个不加 quote 的结构 form: ```elisp (column :padding '(1 2) (text :color "#263244" "Title") (box :width '(240) "Body")) ``` 同级 `ebox-playground` 会先求值 property 表达式,再调用 `ebox-build`。因此 `.ebox` 文件中的 list 与 symbol 常量要加 quote,如上所示。直接调用 `ebox-build` 时,则像 前面的例子一样传入 inert data。 ## 9. Typed 集成 API 已经完成 author normalization 的框架可以绕过 list DSL;这是集成 API,不是第二套 author 语法。一个 source builder 拥有完整 source generation。每个 TextNode 或 BoxNode 都取得该 builder 返回的不透明 handle,并使用由同一份 normalized declarations 投影出的 node-owned facts。最后封存 builder,把 forest 与 source index 作为一个 `CanonicalEboxInput` 传递。 ```elisp (let* ((builder (ebox-source-builder-create)) (root-declarations nil) (left-declarations nil) (right-declarations nil) (root-handle (ebox-source-builder-bind builder :declarations root-declarations)) (left-handle (ebox-source-builder-bind builder :declarations left-declarations)) (right-handle (ebox-source-builder-bind builder :declarations right-declarations)) (left (ebox-text-create :value "Left" :source-handle left-handle :owned-facts (ebox-canonical-facts-from-declarations 'text left-declarations))) (right (ebox-text-create :value "Right" :source-handle right-handle :owned-facts (ebox-canonical-facts-from-declarations 'text right-declarations))) (root (ebox-box-create :layout (ebox-row-layout-create :item-gap 1 :cross-align 'center) :children (list left right) :source-handle root-handle :owned-facts (ebox-canonical-facts-from-declarations 'row root-declarations)))) (ebox-canonical-input-create (list root) (ebox-source-builder-finish builder))) ``` `ebox-source-builder-create`、`ebox-source-builder-bind`、 `ebox-source-builder-finish`、`ebox-canonical-facts-from-declarations`、typed node/layout constructor 与 `ebox-canonical-input-create` 只属于集成边界。这里的 `:layout`、`:children`、`:source-handle` 与 `:owned-facts` 是 evaluated constructor 字段,不是 author property,也没有扩展七入口语法。 ## 10. 可选 native reflow 与验证 Rust 模块是可选加速器,Ebox 加载时不会构建它: ```elisp (ebox-native-status) (ebox-native-build) ``` 在仓库根目录运行: ```sh make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs make dsl-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs ```