578 lines
31 KiB
Markdown
578 lines
31 KiB
Markdown
# Ebox 公共 API 参考
|
||
|
||
[English](ebox-api-reference.en.md)
|
||
|
||
本文是独立 Ebox 包面向用户的完整清单,覆盖节点模型、property、布局
|
||
辅助函数、渲染/发布、selector、滚动、测量、DSL 和可选 native 模块。
|
||
`ebox--*` 名称与没有在本文说明的模块辅助函数属于实现细节。加载
|
||
`(require 'ebox)` 后,也可以通过 `ebox-public-api` 查看门面入口清单。
|
||
|
||
Ebox 负责 box node、样式归一化、测量、布局、渲染、语义更新规划、selector
|
||
适配、Grid 和可选 Rust reflow 加速器。TP 负责 live buffer 发布、物理 diff、
|
||
revision 和 rollback;ECSS 负责 selector 解析、匹配与 cascade。Ebox 不提供
|
||
Component、应用状态、UI control 或应用生命周期。
|
||
|
||
## 1. 安装与加载
|
||
|
||
Ebox 需要 Emacs 29.1 或更新版本,以及 ECSS 和 TP。使用同级源码 checkout:
|
||
|
||
```elisp
|
||
(add-to-list 'load-path "/path/to/github/ecss")
|
||
(add-to-list 'load-path "/path/to/github/tp")
|
||
(add-to-list 'load-path "/path/to/github/ebox")
|
||
(require 'ebox)
|
||
```
|
||
|
||
加载 Ebox 会加载 Elisp 布局/runtime 模块,但不会创建 buffer、在当前 buffer
|
||
启用 mode、构建 Rust 或修改当前编辑 buffer。可选 native 模块只在其工作流
|
||
或合资格的 native 路径需要时加载。
|
||
|
||
## 2. 常规生命周期
|
||
|
||
公共工作流如下:
|
||
|
||
```elisp
|
||
(let ((root
|
||
(ebox-column
|
||
(ebox-create :id "status" :content "Ready" :width '(240))
|
||
(ebox-create :content "调用者拥有的 source tree"))))
|
||
;; 纯 materialization;返回多行带属性字符串。
|
||
(ebox-render root)
|
||
|
||
;; retained TP surface;返回 live buffer。
|
||
(ebox-render-to-buffer "*Ebox Demo*" root)
|
||
|
||
;; 后续构造新的声明式根树并原子发布。
|
||
(ebox-commit
|
||
"*Ebox Demo*"
|
||
(ebox-column
|
||
(ebox-create :id "status" :content "Updated" :width '(240))
|
||
(ebox-create :content "未修改调用者的 source tree。"))))
|
||
```
|
||
|
||
`ebox-render` 不产生 live buffer 副作用。没有 active stylesheet 和 inline
|
||
继承样式依赖的静态树可以使用隔离的 pure materialization;依赖 stylesheet
|
||
的 pure render 使用临时 TP materialization。两者都遵守同一个公共字符串
|
||
契约。两个 buffer 入口都使用 retained TP surface。Ebox 会在分配 runtime
|
||
identity 前复制声明式输入,因此同一 source tree 可以挂载到多个 buffer。
|
||
|
||
`ebox-display-buffer` 是同时需要展示 buffer 时的便捷包装;它会先删除其他
|
||
window,再切换到渲染后的 buffer。如果窗口选择由调用方负责,应使用
|
||
`ebox-render-to-buffer`。
|
||
|
||
## 3. 节点与组合
|
||
|
||
### Box 与简单布局构造器
|
||
|
||
```elisp
|
||
(ebox-create &rest properties)
|
||
(ebox-concat node-1 node-2) ; 两个节点横向排列
|
||
(ebox-stack node-1 node-2) ; 两个节点纵向排列
|
||
(ebox-row &rest nodes) ; 多个节点横向排列;单个 Range 仍保留 row parent
|
||
(ebox-column &rest nodes) ; 多个节点纵向排列;单个 Range 仍保留 column parent
|
||
(ebox-spacer &rest properties) ; 空白 box
|
||
```
|
||
|
||
`ebox-create` 返回声明式 box node。`:content` 是字符串内容;容器子节点通常
|
||
交给 `ebox-row`、`ebox-column`、`ebox-flex` 或 `ebox-grid`。`ebox-concat` 和
|
||
`ebox-stack` 接受两个节点;超过两个子节点时使用 row/column。nil 子节点会
|
||
被忽略;空 row/column 会变成 spacer。
|
||
|
||
`row` 中省略或 `auto` 的宽度按子节点自身的 intrinsic width 参与横向拼接;
|
||
父级 viewport 不会被重复当成每个子节点的宽度。显式 `viewport`、`stretch`
|
||
或 definite width 仍按指定 containing block 解析。`column` 会把较窄的布局
|
||
行补齐到自己的 containing block,但带视觉 box 的子节点会在自己的 used width
|
||
上绘制边框和 padding。
|
||
|
||
`:key` 是兄弟节点范围内的稳定业务 identity;`:id` 是 logical selector/update
|
||
标识;`:host-ref` 是应用拥有的不透明锚点,用于 host-reference 查询和逻辑
|
||
candidate replacement。一个 source tree 内的非 nil host-ref 必须唯一。
|
||
|
||
### Flex
|
||
|
||
```elisp
|
||
(ebox-flex
|
||
:width '(480)
|
||
:flex-flow '(row wrap)
|
||
:gap '(1 (12))
|
||
(ebox-flex-item (ebox-create :content "A")
|
||
:flex '(1 1 auto)
|
||
:align-self 'center)
|
||
(ebox-create :content "B" :flex-grow 2))
|
||
```
|
||
|
||
容器 property 是 `:flex-direction`、`:flex-wrap`、`:flex-flow`、
|
||
`:justify-content`、`:align-items`、`:align-content`、`:gap`、`:row-gap` 和
|
||
`:column-gap`,另加 `:width`、`:height`、padding、border、paint 等普通 box
|
||
property。默认方向是 row、nowrap,主轴是 flex-start,交叉轴是 stretch。
|
||
|
||
item property 是 `:order`、`:flex`、`:flex-grow`、`:flex-shrink`、`:flex-basis`
|
||
和 `:align-self`。它们可以直接放在 child box 上,也可以通过
|
||
`ebox-flex-item` 附加。传给 `ebox-flex-item` 的非 item property 会在 child
|
||
外包一层普通 box。
|
||
|
||
Flex preferred/min/max size 支持 Ebox 子集:`auto`、`min-content`、
|
||
`max-content`、`fit-content`、`stretch`、`contain`;max-size 还支持
|
||
`none`。`fit-content` 可以带数字或 viewport 相对限制。这些是 Ebox 布局值,
|
||
不表示完整浏览器 CSS 兼容性。
|
||
|
||
### Grid
|
||
|
||
```elisp
|
||
(ebox-grid
|
||
:width '(640)
|
||
:grid-template-columns '((200) 1fr 1fr)
|
||
:grid-template-rows '(1 1)
|
||
:gap '(1 (12))
|
||
(ebox-grid-item
|
||
(ebox-create :content "Header")
|
||
:grid-column '(1 :span 3))
|
||
(ebox-create :content "Main" :grid-column 1 :grid-row 2)
|
||
(ebox-create :content "Aside" :grid-column 2 :grid-row 2))
|
||
```
|
||
|
||
`ebox-grid-fr` 构造分数轨道:`(ebox-grid-fr 2)` 返回 `(fr 2)`。Grid 轨道支持
|
||
固定尺寸、`auto`、`(fr FACTOR)`、`1fr` 这样的 symbol、`minmax` 和 `repeat`。
|
||
没有显式给出的行列使用隐式轨道;`:grid-auto-columns`、`:grid-auto-rows`
|
||
和 `:grid-auto-flow`(`row` 或 `column`)控制隐式放置。
|
||
|
||
Grid placement 从 1 开始。`:grid-column` 和 `:grid-row` 接受正整数起点、
|
||
`(start :span 正整数)`,或 `start` 小于 `end` 的 `(start end)`。
|
||
`:grid-column-span` 与 `:grid-row-span` 接受正整数。`ebox-grid-item` 是便捷
|
||
包装器;placement property 也可以直接放在 child node 上。
|
||
|
||
## 4. 单位、box property 与文本属性
|
||
|
||
### 尺寸单位
|
||
|
||
| 值 | 含义 |
|
||
| --- | --- |
|
||
| 普通横向数字,例如 `12` | 字符列数,按当前 display 的空格宽度转换。 |
|
||
| 单元素横向 list,例如 `'(240)` | 绝对像素。 |
|
||
| 纵向数字,例如 `3` | 行数。 |
|
||
| `(viewport)` 或 `viewport` | 当前 viewport 的像素宽度。 |
|
||
| `(viewport-height)` 或 `viewport-height` | 当前 viewport 的行高。 |
|
||
| `auto`、`min-content`、`max-content`、`fit-content`、`stretch`、`contain` | 在对应 property 与 formatting context 支持时使用 intrinsic/preferred width。 |
|
||
|
||
`:box-sizing` 默认是 `border-box`,也支持 `content-box`。宽度 property 是
|
||
`:width`、`:min-width`、`:max-width`;高度 property 是 `:height`、
|
||
`:min-height`、`:max-height`。几何负值会被拒绝。依赖 viewport 的节点要在
|
||
viewport context 中渲染,或通过 `ebox-rerender-buffer-with-context` 重排。
|
||
|
||
### Box、paint 与文本 property
|
||
|
||
以下名称可用于 `ebox-create`。Stylesheet 规则使用其中的 style/geometry
|
||
property;`ebox-region-update` 接受其目标支持的可变 content/style/scroll
|
||
子集。Direct update 不会添加 child 或修改 selector metadata;结构变化使用
|
||
`ebox-commit` 或 logical candidate。结构 metadata、`:content` 和
|
||
`:surface-properties` 是节点/render 输入,不是 stylesheet declaration。表中
|
||
同时列出 canonical 名称与兼容别名。
|
||
|
||
| 范围 | Property |
|
||
| --- | --- |
|
||
| Content/geometry | `:content`、`:box-sizing`、`:width`、`:min-width`、`:max-width`、`:height`、`:min-height`、`:max-height` |
|
||
| Padding | `:padding`、`:padding-inline`、`:padding-block`;longhand `:padding-block-start`、`:padding-inline-end`、`:padding-block-end`、`:padding-inline-start`;别名 `:padding-top`、`:padding-right`、`:padding-bottom`、`:padding-left`,以及 `:padding-right-pixel`、`:padding-left-pixel`、`:padding-top-height`、`:padding-bottom-height` |
|
||
| Margin | `:margin`、`:margin-inline`、`:margin-block`;longhand `:margin-block-start`、`:margin-inline-end`、`:margin-block-end`、`:margin-inline-start`;别名 `:margin-top`、`:margin-right`、`:margin-bottom`、`:margin-left`,以及 `:margin-right-pixel`、`:margin-left-pixel`、`:margin-top-height`、`:margin-bottom-height` |
|
||
| Border | `:border`、`:border-top`、`:border-right`、`:border-bottom`、`:border-left`、`:border-width`、`:border-style`、`:border-color`;side longhand `:border-*-width`、`:border-*-style`、`:border-*-color`;Ebox boolean shorthand `:border-top-p`、`:border-bottom-p`;兼容别名 `:border-left-pixel`、`:border-right-pixel` |
|
||
| Paint | `:color`、`:background-color`、别名 `:bgcolor` |
|
||
| Typography | `:font`、`:font-family`、`:font-height` 和别名 `:font-size`、`:font-weight`、`:font-slant` |
|
||
| Text/layout | `:text-align`(`left`、`center`、`right`)、`:vertical-align`(`top`、`center`/`middle`、`bottom`)、`:overflow`(`scroll`、`hidden`、`visible`)、`:wrap-mode`(`word`、`char`、`kp` 或 nil)、`:visibility`(`visible` 或 `hidden`) |
|
||
| Structural style | `:display` 会在 node/layout context 中计算;有效的 display tuple 通常由布局构造器选择。 |
|
||
| Flex container/item | `:flex-direction`、`:flex-wrap`、`:flex-flow`、`:justify-content`、`:align-items`、`:align-content`、`:gap`、`:row-gap`、`:column-gap`(别名 `:grid-row-gap`、`:grid-column-gap`);item 名称 `:order`、`:flex`、`:flex-grow`、`:flex-shrink`、`:flex-basis`、`:align-self` |
|
||
| Grid container/item | `:grid-template-columns`、`:grid-template-rows`、`:grid-auto-columns`、`:grid-auto-rows`、`:grid-auto-flow`、`:justify-items`;item placement `:grid-column`、`:grid-row`、`:grid-column-span`、`:grid-row-span` |
|
||
| 额外文本属性 | `:surface-properties` 接受偶数长度的 Emacs text-property plist,应用到不含换行的渲染字符;内层非 nil 属性优先。 |
|
||
|
||
Box 字体入口是 `:font`,不是 `:face`。`:font` 接受 Emacs face symbol、face
|
||
名称字符串或 face plist;typography longhand 会合并到该 face。`:visibility
|
||
'hidden` 会隐藏 ink,但保留布局 footprint 与 metadata。`:overflow 'visible`
|
||
会发布超出高度的可见行;`hidden` 会裁剪;`scroll` 暴露有限窗口,并在内容
|
||
高于 box 时启用 Ebox 滚动。
|
||
|
||
Padding 与 margin 使用 CSS 的 1–4 值展开。横向值使用上述横向单位,顶部和
|
||
底部使用行单位。左右 border 是像素几何;上下 border 通过 Emacs overline/
|
||
underline face 实现,因此宽度是存在性标志而非任意像素厚度。
|
||
|
||
### Node metadata 与 selector 输入
|
||
|
||
这些输入用于标识节点,不会直接改变几何:
|
||
|
||
| 输入 | 含义 |
|
||
| --- | --- |
|
||
| `:id` | logical selector/update id。symbol 和其他标量会归一化为字符串;直接 update 解析时应保持 id 唯一。 |
|
||
| `:key` | reconciliation 使用的稳定兄弟 identity,同时作为内建 selector `:key` attribute 暴露。 |
|
||
| `:class` | 一个 class token 或 class token 列表,用于 selector matching。 |
|
||
| `:selector-state` | 一个 state token 或 state token 列表,用于匹配 `:hover`、`:active` 等 state pseudo。 |
|
||
| `:selector-attributes` | 显式 attribute alist,例如 `((role . button))`;content、布局值和 runtime 字段不会自动推断。 |
|
||
| `:host-ref` | 应用拥有的唯一不透明 anchor,用于 `ebox-host-ref-position`、`ebox-host-ref-bounds` 和 host-ref candidate replacement。 |
|
||
| `:scroll-offset` | scroll region 的初始/受控行偏移。交互优先使用滚动命令;update 必须使用 live region handle。 |
|
||
|
||
`:id`、`:key` 和 `:host-ref` 不是 stylesheet declaration。`:class`、
|
||
`:selector-state` 和 `:selector-attributes` 是 selector metadata,不是通用
|
||
应用状态。
|
||
|
||
## 5. Stylesheet 与 cascade
|
||
|
||
多数节点只需 inline property。需要 selector 驱动样式时使用隔离的 Ebox
|
||
stylesheet:
|
||
|
||
```elisp
|
||
(ebox-style-reset-rules)
|
||
(ebox-style-add-rule
|
||
".card"
|
||
'(:color "#1F2937"
|
||
:background-color "#F8FAFC"
|
||
:padding '(1 (12)))
|
||
:layer 'base)
|
||
(ebox-style-add-rule
|
||
".card:has(> .warning)"
|
||
'(:border-color "#DC2626")
|
||
:layer 'state)
|
||
```
|
||
|
||
`ebox-style-add-rule` 接受 selector string 或 AST、Ebox declaration plist,
|
||
以及 ECSS cascade 关键字 `:origin`、`:layer`、`:scope`。
|
||
`ebox-style-reset-rules` 清空 stylesheet。规则属于已加载的全局 Ebox style
|
||
registry;改变规则不会自动发布已有 mounted buffer,因此规则变化后要对
|
||
目标 buffer rerender 或 commit。`ebox-style-cascade-active-p` 可检查当前
|
||
是否存在规则。
|
||
|
||
基于 ECSS 的 style domain 包含 color 与 typography inheritance、computed
|
||
value、cascade layer、custom-property declaration,以及 paint/geometry/
|
||
structure dirty 分类。Ebox 只解释本文列出的 property;百分比、绝对定位、
|
||
z-index、阴影、border radius、浏览器级 bidi 和完整浏览器 typography 不属于
|
||
本包。
|
||
|
||
名称以 `--` 开头的 custom property 可以由 ECSS style domain 携带,但除非宿主
|
||
集成把它解析为本文支持的 Ebox property,否则不会影响 Ebox 输出。
|
||
|
||
## 6. 渲染、identity 与更新
|
||
|
||
### Region id 与 logical handle
|
||
|
||
`ebox-region-ids` 会从未渲染树或已渲染字符串按文档顺序返回 region id。需要
|
||
低层 source 映射时,在第一次 mount 前捕获:
|
||
|
||
```elisp
|
||
(let* ((root (ebox-column
|
||
(ebox-create :id "title" :content "Title")
|
||
(ebox-create :id "body" :content "Body")))
|
||
(ids (ebox-region-ids root)))
|
||
(ebox-render-to-buffer "*Ebox Demo*" root)
|
||
ids)
|
||
```
|
||
|
||
应用更新优先使用 logical id 与不透明 handle:
|
||
|
||
```elisp
|
||
(let ((handle (ebox-region-resolve "*Ebox Demo*" "body")))
|
||
(ebox-region-update handle :content "Changed" :color "#166534"))
|
||
```
|
||
|
||
`ebox-region-resolve` 在 buffer 不存在、logical id 缺失或 id 有歧义时发出
|
||
错误。Handle 绑定一个 retained TP surface;对象移除或 buffer kill 后会 stale。
|
||
数字 region id 是渲染 metadata,不能作为 `ebox-region-update` 的参数。
|
||
|
||
### Direct 与 selector 更新
|
||
|
||
`ebox-region-update` 接受可变 content/style/scroll property 关键字;除非处于
|
||
显式 batch,否则返回已提交的 update report。它先构造隔离 candidate,规划
|
||
最小安全 Ebox owner,再请求 TP 发布一次。它不会添加 child,也不会修改
|
||
`:id`、`:class`、`:key` 或其他 selector metadata。非法 declaration、render
|
||
失败、publication 失败或 callback 失败都会保留原 buffer/runtime/report。
|
||
|
||
`ebox-selector-update-buffer` 把 property 应用到 buffer 中所有可编辑的
|
||
selector match,返回:
|
||
|
||
```elisp
|
||
(:selector SELECTOR
|
||
:matched INTEGER
|
||
:updated INTEGER
|
||
:skipped ((:node-id ID :reason no-region) ...)
|
||
:reports (REPORT ...))
|
||
```
|
||
|
||
显式多更新 batch 只有在应用确实拥有该边界时才使用较低层 incremental batch
|
||
函数;一般场景使用 `ebox-selector-update-buffer`。
|
||
|
||
### Logical candidate
|
||
|
||
Candidate 是基于当前已发布 runtime 的一次性 transaction。多个稳定 subtree
|
||
需要一起替换并只提交一次时使用:
|
||
|
||
```elisp
|
||
(let* ((buffer (get-buffer "*Ebox Demo*"))
|
||
(candidate (ebox-candidate-begin buffer))
|
||
(match (car (ebox-selector-query-buffer buffer "#body"))))
|
||
(ebox-candidate-replace
|
||
candidate
|
||
(plist-get match :node-id)
|
||
(ebox-create :id "body" :content "Candidate replacement"))
|
||
(ebox-commit buffer candidate))
|
||
```
|
||
|
||
如果框架的 semantic owner 与 backend anchor 分离,可在选择更宽的发布 owner
|
||
之前使用只读函数 `ebox-range-ref-present-p`。当 semantic Range 嵌套在 material
|
||
anchor 内、当前 Ebox publication 没有独立地址时,它返回 nil。
|
||
|
||
`ebox-candidate-replace-host-ref` 使用应用拥有的 `:host-ref`,不需要 runtime
|
||
node id。两个 replacement 函数都接受可选的 `old-semantic-key` 与
|
||
`new-semantic-key`,用于有限的 detached identity 复用。Candidate 在 commit
|
||
时 sealed,不能复用;若捕获的 runtime 或 buffer tick 发生变化,它也会 stale。
|
||
|
||
`ebox-candidate-patch-host-paint` 比较前后 declarative Host 输出;只有全部变化
|
||
都属于 node-local paint 时才记录属性 patch。它保留子树与 runtime identity,
|
||
并在一次受影响子树遍历中更新继承前景;需要普通子树替换时返回 nil。
|
||
|
||
`ebox-candidate-replace-root` 使用私有且绑定 candidate 的 root 地址。它只接受
|
||
一个声明式节点、清除调用方 runtime identity,并采用 last-wins 语义。最终 root
|
||
replacement 会吸收其前后记录的 descendant node/host-ref operation,不添加
|
||
wrapper,也不暴露公共 reference。
|
||
|
||
`ebox-child-range` 在 material child list 中创建非节点 segment descriptor。
|
||
其非 nil ref 在 root 内全局唯一,items 直接参与 parent 的 key、selector、style
|
||
与 layout scope。`ebox-candidate-replace-range-ref` 接受 proper declarative
|
||
node list,只替换该 base payload;empty payload 仍可寻址。
|
||
|
||
### Host-reference 位置
|
||
|
||
```elisp
|
||
(ebox-host-ref-position "*Ebox Demo*" 'toolbar)
|
||
;; => 第一个 live 字符位置,或 nil
|
||
(ebox-host-ref-bounds "*Ebox Demo*" 'toolbar)
|
||
;; => (START . END),不含 margin,或 nil
|
||
```
|
||
|
||
位置与 bounds 属于当前 publication generation;每次更新后都要重新获取。
|
||
Host-reference table 与 logical selector id 是两套不同的机制。
|
||
|
||
### Report 与 viewport reflow
|
||
|
||
如果上层框架在调用 Ebox 前需要先分配新的 view tree,可以把整个输入 callback
|
||
纳入 Ebox 的 render GC budget:
|
||
|
||
```elisp
|
||
(ebox-call-with-render-burst
|
||
(lambda ()
|
||
(let ((next-root (build-framework-view)))
|
||
(ebox-commit buffer next-root))))
|
||
```
|
||
|
||
优先使用能保证异常清理的 `ebox-call-with-render-burst`。如果操作无法表达为
|
||
一次函数调用,`ebox-render-burst-begin` 会返回交给
|
||
`ebox-render-burst-end` 的不透明 token;调用方必须使用 `unwind-protect`,并按
|
||
后进先出顺序结束嵌套 token。交互 burst 与 Ebox commit 共用 deferred state,
|
||
在最外层 end 后 0.2 秒恢复调用方 GC 设置;新 burst 会取消并取代待执行的
|
||
restore。Batch burst 立即精确恢复 threshold 与 percentage,且不会在边界主动
|
||
执行 GC。这些函数只定义 allocation/GC ownership,不会 publish、commit,也
|
||
不会暴露 Ebox runtime state。
|
||
|
||
`ebox-buffer-update-report` 返回 mounted buffer 上一次成功报告的 defensive
|
||
copy;目标缺失或不是 Ebox runtime 时会报错。首次 mount 在第一次 update
|
||
前没有 update report。报告包含 Ebox strategy、publication scope、TP surface
|
||
revision、物理 operation 和 reconciliation 事实。
|
||
|
||
完整签名是 `(ebox-commit BUFFER NEXT-ROOT &optional FRAMEWORK-PUBLISH
|
||
FRAMEWORK-ROLLBACK)`。buffer、TP surface 与 Ebox runtime 一致后,publish 接收
|
||
report;若后续 phase 失败,rollback 最多一次接收同一个 report,其 error/quit
|
||
会被隔离。`:framework-participant-state`、
|
||
`:framework-participant-diagnostics` 与 `:scroll-finalization-diagnostics`
|
||
是只读 outcome 字段。
|
||
|
||
当 root 依赖新的 viewport 时使用 `ebox-rerender-buffer-with-context`:
|
||
|
||
```elisp
|
||
(ebox-rerender-buffer-with-context
|
||
(get-buffer "*Ebox Demo*")
|
||
800 ; viewport 像素宽度
|
||
30) ; 可选 viewport 行高
|
||
```
|
||
|
||
该调用保留 node/region identity,并通过 viewport dirty planning 处理变化。
|
||
`(viewport)` 与 `(viewport-height)` 从该 context 解析。纯渲染时也可以在
|
||
render 外部动态绑定 `ebox-viewport-width`(像素)与 `ebox-viewport-height`
|
||
(行数)。不要原地修改已经发布的 node tree。
|
||
|
||
可见的 mounted buffer 会自动跟随展示它的 window。Ebox 通过 retained 增量路径
|
||
立即发布每个 size event。Emacs hook 串行执行并拒绝重入;这里没有第二套宿主
|
||
hook、timer 或 viewport queue。
|
||
`ebox-viewport-window-width` 返回 controller 使用的同一个显示安全像素宽度;
|
||
宿主不应再安装第二套 resize hook 或宽度算法。
|
||
旧的 `ebox-viewport-resize-delay` 已删除;立即发布不再需要迁移 timer 配置。
|
||
|
||
## 7. Selector
|
||
|
||
`ebox-selector-parse` 委托 ECSS 并返回结构化 AST;
|
||
`ebox-selector-match-node-p` 匹配单个声明式节点;`ebox-selector-query-all`
|
||
从未 mounted tree 返回按文档顺序的 match;`ebox-selector-query-buffer` 查询
|
||
live buffer 的 retained runtime,并给每条 match 添加 `:buffer`,可编辑节点
|
||
还会添加 `:region-handle`。
|
||
|
||
ECSS 支持的 selector 语法包括:
|
||
|
||
- selector list、compound type/id/class selector;
|
||
- attribute presence 与 `=`、`~=`、`|=`、`^=`、`$=`、`*=`,以及 `i`/`s` flag;
|
||
- 通过 `:selector-state` 提供的 `:hover`、`:active` 等 state pseudo;
|
||
- `:is(...)`、`:where(...)`、`:not(...)` 与 relational `:has(...)`;
|
||
- 空格 descendant、子节点 `>`、相邻兄弟 `+`、一般兄弟 `~` combinator。
|
||
|
||
Ebox 的 node type 会映射为 selector type:`box`、`row`、`column`、`flex`、
|
||
`grid`、`item`。`:id` 与 `:key` 是内建 attribute;额外 attribute 必须显式
|
||
传入,例如 `:selector-attributes '((role . button))`。可见 content、布局
|
||
值和内部 runtime slot 不会被推断成 selector attribute。
|
||
|
||
```elisp
|
||
(ebox-selector-query-buffer "*Ebox Demo*" ".toolbar > box.action")
|
||
(ebox-selector-query-buffer "*Ebox Demo*" "div:has(> .warning)")
|
||
(ebox-selector-update-buffer "*Ebox Demo*" "[role=button]"
|
||
:color "#2563EB")
|
||
```
|
||
|
||
兼容别名 `ebox-select-all` 与 `ebox-update-selector` 分别指向
|
||
`ebox-selector-query-buffer` 和 `ebox-selector-update-buffer`。
|
||
|
||
## 8. 滚动与 viewport state
|
||
|
||
设置 `:overflow 'scroll`(默认值)和有限的 `:height` 创建滚动窗口:
|
||
|
||
```elisp
|
||
(ebox-create
|
||
:id "log"
|
||
:width '(420)
|
||
:height 8
|
||
:overflow 'scroll
|
||
:content (mapconcat #'identity lines "\n"))
|
||
```
|
||
|
||
`ebox-scroll-down`、`ebox-scroll-up`、`ebox-scroll-page-down` 和
|
||
`ebox-scroll-page-up` 操作 point 所在的最内层滚动区域;找不到可消费该命令
|
||
的 Ebox region 时委托普通 Emacs scrolling。`ebox-wheel-scroll-down` 与
|
||
`ebox-wheel-scroll-up` 接受 mouse event;当 `ebox-wheel-smooth-scroll` 非 nil
|
||
时会分步动画,否则委托 `mwheel-scroll`。`ebox-buffer-mode` 在本地安装
|
||
`ebox-scroll-map`;`ebox-render-to-buffer` 会在返回的 buffer 上启用该 mode。
|
||
|
||
`ebox-scroll-state` 接受数字 region id,返回用于 source-model 映射的只读
|
||
state plist。可用事实包括 `:scroll-offset`、content height/lines 与缓存的
|
||
visible lines。把 plist 当作 diagnostic/read-only 数据;用
|
||
`ebox-region-update` 的 `:scroll-offset` 或滚动命令改变位置。
|
||
|
||
Scroll content 会惰性预取,并按有界 slice 渲染。安全的 retained update 会
|
||
保留缓存 scroll state;发布失败时它与其他 runtime 状态一起 rollback。
|
||
|
||
## 9. 测量与缓存配置
|
||
|
||
`ebox-string-pixel-width` 返回字符串第一行的 display 像素宽度,同时考虑
|
||
text scale 和固定 `display` space;它也是布局使用的公共测量原语。
|
||
`ebox-display-signature` 返回测量缓存使用的当前 display 输入。
|
||
如果在正常 display-signature 信号之外修改了字体或 named face,可调用
|
||
`ebox-clear-cache`。
|
||
|
||
主要 customization 变量如下:
|
||
|
||
| 分组 | 变量与默认值 |
|
||
| --- | --- |
|
||
| Render cache | `ebox-render-cache-max-entries` 2048;`ebox-render-cache-max-bytes` 32 MiB;`ebox-render-root-cache-max-entries` 16;`ebox-render-root-cache-max-bytes` 8 MiB |
|
||
| 键盘/鼠标滚动 | `ebox-scroll-step` 1;`ebox-wheel-scroll-step` 1;`ebox-wheel-smooth-scroll` nil;`ebox-wheel-smooth-scroll-interval` 0.016;`ebox-wheel-smooth-scroll-lines-per-tick` 4;`ebox-wheel-smooth-scroll-target-ticks` 8。键盘以 point 为 anchor,滚轮以 event 位置为 anchor,剩余行沿嵌套 owner 向外冒泡。交互式、内容完整且无 chrome 的根 document owner 可在 idle 时一次 materialize,之后使用 Emacs 原生 window line start;普通更新/resize 只预热 retained index,不启动第二次 native publication;嵌套/lazy/chrome owner 继续走事务化 Ebox publication。 |
|
||
| Lazy scroll | `ebox-scroll-lazy-prefix-lookahead-lines` 8;`ebox-scroll-lazy-idle-prefetch-lines` 128;`ebox-scroll-lazy-idle-prefetch-slice-lines` 16;`ebox-scroll-lazy-idle-prefetch-delay` 0.15 |
|
||
| Runtime prewarm | `ebox-runtime-idle-prewarm` t;`ebox-runtime-idle-prewarm-delay` 0.1;`ebox-runtime-idle-prewarm-prefix-resume-delay` 2.0;`ebox-runtime-idle-prewarm-slice-size` 32;`ebox-native-buffer-scroll` t(仅 initial/visible-window handoff;严格 root-owner proof) |
|
||
| 预测 reflow | `ebox-runtime-idle-reflow-cache-prewarm` t;`ebox-runtime-idle-reflow-cache-prewarm-delay` 0.15 |
|
||
| Reflow GC | `ebox-reflow-cache-prewarm-gc-cons-threshold` `auto`;`ebox-reflow-cache-prewarm-gc-auto-frame-budget` 0.2;`ebox-reflow-cache-prewarm-gc-auto-min-threshold` 64 MiB;`ebox-reflow-cache-prewarm-gc-auto-max-threshold` 1 GiB;`ebox-reflow-cache-prewarm-gc-auto-initial-threshold` 512 MiB;`ebox-reflow-cache-prewarm-gc-auto-target-layouts` 24;`ebox-reflow-cache-prewarm-gc-cons-percentage` 0.1 |
|
||
| Visual verification | `ebox-visual-check-output-dir` 指向可选 screenshot/report 使用的临时目录。 |
|
||
|
||
Idle prewarm 不会发布 buffer text 或 runtime state。宿主应用不希望后台准备
|
||
工作时,可把对应的 boolean 设为 nil。
|
||
|
||
## 10. 独立 `.ebox` DSL
|
||
|
||
`ebox-build` 编译数据型 list form。支持的 tag 是 `box`/`ebox`、`row`、
|
||
`column`、`flex`、`item`、`grid`、`grid-item` 和 `spacer`:
|
||
|
||
```elisp
|
||
(ebox-build
|
||
'(grid :width (640)
|
||
:grid-template-columns ((200) 1fr 1fr)
|
||
:gap (1 (12))
|
||
(grid-item :grid-column (1 :span 3)
|
||
(box :content "Header"))
|
||
(box :content "Main")))
|
||
```
|
||
|
||
`box` 下的直接字符串会合并为换行内容;child form 会变成默认纵向 child
|
||
layout。`row` 与 `column` 可以接收 box property 并包住 child layout;`flex`
|
||
接收容器 property;`item` 接收 flex item property;`grid-item` 必须有一个
|
||
child;`spacer` 不能有 child。
|
||
|
||
在 `.ebox` 文件中,结构 form 保持不加 quote;property 位置的 list 和 symbol
|
||
常量要作为 data quote,例如 `:gap '(1 (12))` 或 `:justify-content 'center`;
|
||
要执行的 Elisp property expression 保持不 quote,交给同级 playground runner
|
||
求值。
|
||
|
||
## 11. 可选 Rust reflow
|
||
|
||
Native 模块是加速器,不是正确性的依赖。Rust 边界只接收有界的数值布局工作;
|
||
Ebox 保留完全等价的 Elisp fallback,加载时绝不构建 Rust。
|
||
|
||
```elisp
|
||
(ebox-native-status) ; 显示 toolchain、路径、ABI 与加载诊断
|
||
(ebox-native-build) ; 异步构建/安装
|
||
(ebox-native-build t) ; 先清理 Ebox 私有 Cargo cache
|
||
```
|
||
|
||
仓库命令是 `make native-build`。使用 `ebox-native-reflow-module-path` 配置
|
||
module 文件或目录。Session 限制为 `ebox-native-reflow-max-jobs` 512、
|
||
`ebox-native-reflow-max-results` 512、`ebox-native-reflow-max-result-bytes`
|
||
64 MiB。模块缺失或 ABI 不兼容时继续使用 Elisp;原因可由
|
||
`ebox-native-status` 查看。
|
||
|
||
`ebox-byte-compile` 会重新编译所有 active Ebox Elisp 源码,但不会构建 Rust。
|
||
成功后如需当前进程加载新的 `.elc`,请重启 Emacs。
|
||
|
||
## 12. 公共函数清单
|
||
|
||
下表覆盖门面清单中的公共函数;其下列出的 style-rule 函数是模块级公共
|
||
style API。
|
||
|
||
| 函数 | 用途 |
|
||
| --- | --- |
|
||
| `ebox-text-create`、`ebox-normal-layout-create`、`ebox-row-layout-create`、`ebox-column-layout-create`、`ebox-flex-layout-create`、`ebox-box-create` | 框架集成使用的 typed TextNode、Normal/Row/Column/Flex LayoutConfig 与 BoxNode 端口。 |
|
||
| `ebox-create`、`ebox-build` | 迁移期间创建旧节点或编译 `.ebox` list DSL。 |
|
||
| `ebox-concat`、`ebox-stack`、`ebox-row`、`ebox-column`、`ebox-spacer` | 组合简单横向/纵向布局与空白 box。 |
|
||
| `ebox-flex`、`ebox-flex-item` | 构建 Flex 容器与 item metadata。 |
|
||
| `ebox-grid`、`ebox-grid-fr`、`ebox-grid-item` | 构建 Grid、分数轨道和 placement metadata。 |
|
||
| `ebox-render`、`ebox-render-to-buffer`、`ebox-display-buffer` | 纯 materialization、retained mount 与展示包装。 |
|
||
| `ebox-commit`、`ebox-buffer-update-report`、`ebox-rerender-buffer-with-context`、`ebox-viewport-window-width` | 原子根提交、报告查询、viewport-context rerender 与统一的显示安全宽度采样。 |
|
||
| `ebox-region-ids`、`ebox-region-resolve`、`ebox-region-update` | Region 映射、logical handle 查询与直接更新。 |
|
||
| `ebox-child-range`、`ebox-range-ref-present-p`、`ebox-candidate-begin`、`ebox-candidate-replace`、`ebox-candidate-replace-range-ref`、`ebox-candidate-replace-root`、`ebox-candidate-replace-host-ref`、`ebox-candidate-patch-host-paint` | 持久 material child segment、backend anchor 查询、子树替换与 retained Host paint transaction。 |
|
||
| `ebox-host-ref-bounds`、`ebox-host-ref-position` | Host-reference 的 live bounds 与首位置。 |
|
||
| `ebox-selector-parse`、`ebox-selector-match-node-p`、`ebox-selector-query-all`、`ebox-selector-query-buffer`、`ebox-selector-update-buffer` | ECSS selector 编译、匹配、查询与批量更新。 |
|
||
| `ebox-select-all`、`ebox-update-selector` | 两个 buffer selector 函数的兼容别名。 |
|
||
| `ebox-buffer-mode`、`ebox-scroll-map`、`ebox-scroll-down`、`ebox-scroll-up`、`ebox-scroll-page-down`、`ebox-scroll-page-up`、`ebox-wheel-scroll-down`、`ebox-wheel-scroll-up`、`ebox-scroll-state` | 交互滚动、本地 keymap 与只读滚动状态。 |
|
||
| `ebox-string-pixel-width`、`ebox-display-signature`、`ebox-clear-cache` | Display-aware 测量与缓存管理。 |
|
||
| `ebox-native-build`、`ebox-native-status`、`ebox-byte-compile` | 可选 native 工作流与 Elisp byte compile。 |
|
||
|
||
加载 `(require 'ebox)` 后可用的 style inspection/rule 入口还包括
|
||
`ebox-style-add-rule`、`ebox-style-reset-rules`、
|
||
`ebox-style-cascade-active-p`、`ebox-style-compute`、
|
||
`ebox-style-expand-shorthands`、`ebox-style-compile-declarations`、
|
||
`ebox-style-merge-declarations`、`ebox-style-compute-subject`、
|
||
`ebox-style-property`、`ebox-style-canonical-name` 和
|
||
`ebox-style-dirty-kind`。前两个是应用通常使用的入口;其余函数供需要检查
|
||
或准备 ECSS-backed style data 的集成使用。
|
||
|
||
## 13. 验证与支持边界
|
||
|
||
在仓库根目录运行:
|
||
|
||
```sh
|
||
make load EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||
make compile EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||
make native-rust-tests
|
||
```
|
||
|
||
聚焦目标包括 `make core-tests`、`make grid-tests`、`make surface-tests`、
|
||
`make selector-tests`、`make dsl-tests`、`make flex-tests` 和
|
||
`make visual-check-tests`。可运行的 `.ebox` 示例由同级 `ebox-playground`
|
||
包负责;ETAF 负责 Component、state、control 和应用行为。本包不要调用
|
||
私有 `ebox--*` 函数,也不要在 Ebox 任务中编辑历史 Ebox checkout。
|