19 KiB
ebox-playground
ebox-playground 是 Ebox DSL 的独立通用文件运行器,只使用 Ebox 公共 API;具体布局都放在 examples/ 下易读的 .ebox fixture 中。flex-reference.ebox、grid-reference.ebox 和 size-reference.ebox 用克制的陶土色、鼠尾草绿、蓝色、紫色、赭石色和青绿色视觉语言,展示布局与尺寸规则。interaction-reference.ebox 展示 Ebox 原生交互及 retained 更新。
ECSS 0.1.0 与 TP 1.0.1 是互相独立的包,安装顺序任意;两者都安装后再安装 Ebox 2.0.1,最后安装 Ebox Playground 0.1.1。ebox-playground 不调用这些依赖的私有 API。
独立画廊入口默认使用明确的 720 px 画布,因此 batch 渲染和单窗口演示里的嵌套示例有稳定空间。分屏 .ebox 预览则会将 (vw 100) 解析为右侧预览窗口的显示安全宽度。章节分隔条使用所在 Column 的可用宽度,因此根容器缩窄时也会跟随。高层 Component/Runtime 展示仍由独立的 etaf-playground 提供。
独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 ebox-viewport-width,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 C-c C-c 渲染 .ebox 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 (vw 100) 解析为 Ebox 统一计算的显示安全宽度;Ebox 唯一且串行的 viewport controller 会立即通过 retained 路径发布每次 window-size 变化,Playground 不再拥有第二套 resize hook 或 timer。
(require 'ebox-playground)
(ebox-playground-open)
打开 examples/size-reference.ebox,按 C-c C-c 即可查看 Ebox Size Reference。
示例按允许的方向展示六种长度单位、六个尺寸约束属性支持的关键词、calc/min/max/clamp,以及字体、
间距、Flex、Grid 中的组合用法。调整预览宽度可以对照百分比和视口单位,并观察流式尺寸的变化。
精度和 buffer 渲染器的限制会直接标明,不把当前实现描述成与浏览器完全一致。
受限容器里的长文本用于区分内容宽度关键词,Row 对照用于区分 auto 和 stretch。
高度内容关键词会明确标注当前渲染器中的等效关系;min、max、clamp 使用相同的低、中、高输入展示边界变化。
实色矩形标明被测 Box 的完整范围(包含空白),浅色标尺表示父容器的可用宽度,白色卡片承载说明。
运行 make size-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs,可检查核心尺寸契约、
示例的实际属性覆盖、已知计算结果、背景所指示的尺寸差异、完整渲染及保留状态的视口更新。make check 也包含此示例的回归测试。
PARALLAX 可拖拽桌面
打开 examples/desktop-reference.ebox,按 C-c C-c,或执行:
(ebox-playground-open-file
(expand-file-name "examples/desktop-reference.ebox"
ebox-playground-directory))
深蓝工作台中放置五个内容各异的窗口:Studio、Notes、Signal、Terminal、Palette。 点击露出的标题、正文或空白区域,会将整窗置顶;按住标题栏拖动时持续更新位置, 保留抓取偏移,松手时应用最终位置。窗口可以部分或完全覆盖其他窗口,原有内容始终保留。
底部五个窗口入口可以找回被完全遮住的窗口;CYCLE 将最下面的窗口放到最上面, RESET 恢复原始位置和次序,也能找回拖出桌面裁剪边界的窗口。RET/SPC 激活当前 控件,标题或窗口入口上的 Alt+方向键移动对应窗口并支持连续按键;C-g 结束拖拽, 保留最后成功发布的位置。每次打开的预览状态互相独立。
横向按像素移动,纵向遵循 Ebox 的文本行网格。推荐至少 40 列、20 行,更大的预览 有更多摆放空间。所有文本保持当前字体大小,不使用额外字体或图片依赖。
运行 make desktop-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
检查真实 keymap 命令、中间运动帧、松手、遮挡归属、失败回滚和渲染一致性;
make check 已包含该套件。重复位置发布的耗时使用共享 interaction evaluator,
传入该示例、窗口入口 "01 ST" 和按键 "M-<right>",不将其冒充 GUI 重绘延迟。
滚动性能验证
使用共享 scripts/ebox-playground-flex-resize-evaluator.el 的入口:
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
--eval '(setq load-prefer-newer t native-comp-jit-compilation nil)' \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(ebox-playground-scroll-evaluator-run "examples/flex-reference.ebox" 104 6 36)'
参数依次为文件路径或路径列表、视口像素宽度、采样次数、视口行高及可选 options
plist。默认完全缓存根滚动内容,再测量顶部和中间位置的往返单行滚动。传入
(:region-id "log") 可选择声明了 :id "log" 的嵌套滚动框;(:cold t) 则从
初始渲染开始连续向前滚动,不预热、不主动物化全部内容,也不跳到中间位置。
两者可以组合,所选区域必须有足够的滚动空间完成采样。
初始渲染可能已包含前瞻缓存行;要测量缓存未命中,采样次数应超过该前缀,并检查
:prefix-renders(实际前缀渲染)和 :materializations(完整内容生产)计数。
已完成缓存的空操作不计入完整物化次数。
结果报告 mean/p95/max 毫秒、采样循环的 GC 次数/耗时、完整根渲染、通用文档准备、surface 计划构造、样式计算及缓存生产次数,并与独立渲染已提交输入、 恢复其滚动位置后的结果比较。比较保留 callback 和 keymap 身份,不重新加载业务 状态;移动未完成或绘制不一致都会失败。测试使用临时 buffer,并清理挂载、advice 和定时器,不改变现有预览。测量包含 Ebox/TP 发布,不包含 GUI 输入传递、原生窗口 滚动或 Emacs redisplay。此入口取代临时的逐示例滚动探针。
图层与原生菜单示例
打开 examples/layer-reference.ebox 并按 C-c C-c,可查看 ORBIT 空间桌面、
保留内容的重叠面板及锚定菜单;examples/layer-minimal.ebox 保留最小重叠示例。
工具栏每个按钮支持点击、RET 和 SPC。悬停绘制仅覆盖按钮及其自身 padding,
按钮间隙和行内剩余空间保持不变;被覆盖笔记、根菜单选项等有意设置为块级交互的
区域仍保留自己的悬停绘制。
layer-reference.ebox 保存布局,layer-reference.el 保存业务状态与操作。每个
操作通过公共 ebox-call-with-update-batch 将 region 变化与状态栏更新合并。
helper 加入已有 batch 时不 flush;自己拥有 batch 时只发布一次待处理变化。
回滚覆盖排队的 Ebox 更新,不撤销 companion 中任意业务状态变化,详见
批量更新契约。
运行 make layer-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs,
验证图层契约、实际原生命令及已提交快照的独立渲染一致性,无需打开 GUI 窗口。
使用共享入口测量桌面展开/收起路径:
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
--eval '(setq load-prefer-newer t native-comp-jit-compilation nil)' \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(let ((r (ebox-playground-interaction-evaluator-run "examples/layer-reference.ebox" "EXPLODE STACK" "RET" 6 104 100))) (cl-remf r :text) (pp r))'
根菜单关闭时,桌面三个工具栏命令只将受影响的叠层宿主和状态文字一起发布,不再 完整渲染候选树。宿主内部仍会重新合成堆叠组;这是 owner 级增量,还不是像素级 最小脏矩形。可见根浮层和依赖内容的几何仍保留扩大合成范围的回退。回归测试要求 每次命令只发布一次,并禁止完整候选渲染;耗时是诊断数据,不作为 CI 时间阈值。
原生交互实验室
使用已有文件运行器打开交互参考:
(require 'ebox-playground)
(ebox-playground-open-file
(expand-file-name "examples/interaction-reference.ebox"
ebox-playground-directory))
也可以访问 examples/interaction-reference.ebox 后按 C-c C-c,并排显示源码和
预览。Ebox Native Interaction Lab 先分别展示帮助文字、指针形状与悬停绘制,
再通过实时计数器和事件日志组合 :help-echo、:pointer、:hover-style 与 :keymap。
- 悬停在带边框的卡片上(包括 padding),对比静态帮助、动态帮助、原生指针以及 颜色和文本装饰变化。
- 点击 ADD / [+] 增加,点击独立的 [-] Text 减少;把 point 移到对应操作上
使用
RET/SPC,也可以使用其+/-快捷键。 SWITCH STEP 1 / 5 将其 keymap 替换为使用对应步长的命令。 - SWITCH HOVER PALETTE 替换悬停绘制;REMOVE / RESTORE 通过显式
nil清除四种能力,再恢复它们。Text 重置操作可以清零计数。 - UPDATE STATUS GROUP 通过一次公共
ebox-update-selector调用,共同更新 三个.demo-statusBox。 - 嵌套区域展示父节点覆盖、拥有独立命令的 Text 子节点,以及通过显式
nil阻止 外层能力覆盖的子节点。事件日志记录实际运行的命令。
Box 的帮助、指针和 keymap 范围包含内容、padding 和 border,不包含该 Box 自身的
margin 及结构性换行符。同一渲染行内,一个 hover owner 的文字与 padding 共享原生
mouse-face;未指定属性取自 owner 的基础样式,即使子文本的正常样式不同。
物理左右边框不参与悬停,水平边框线条保留自己的绘制。原生指针外观和帮助显示由
Emacs 设置及窗口系统决定,
见公共能力契约。
interaction-reference.el 负责业务函数与状态,interaction-reference.ebox 负责
布局;运行器在每次预览前重新加载对应 .el。Ebox 的 ebox-help-create 提供原生
帮助适配,ebox-keymap-create 绑定
零参数回调,使用 ebox-region-update 更新命名 region;鼠标回调自动在事件窗口的
buffer 中运行。Ebox 不增加应用状态或焦点导航系统,使用键盘绑定时通过普通 point
移动定位。Keymap 会创建快照,发布新绑定需要替换 :keymap。每次求值都会创建
独立的新示例。
在本目录运行交互检查:
make interaction-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
该入口验证核心交互契约、示例渲染后的能力、命令效果、替换与清除/恢复。
make check 也包含 Playground 交互回归测试。
检查重复按键行为和命令耗时时,使用共享交互 evaluator:
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(pp (ebox-playground-interaction-evaluator-run "examples/interaction-reference.ebox" "ADD " "RET" 30 720 200))'
函数签名为 (FILE LABEL &optional KEY STEPS WIDTH HEIGHT)。它创建全新的临时
预览,定位一次可见标签,然后在该 point 位置反复查找原生按键绑定,不在更新后重新
定位;失去节点绑定时检查失败。KEY 接受按键描述字符串或事件向量,默认参数为
RET、30 次命令、720 px 宽、200 行高;次数与尺寸必须为正整数。
返回的 plist 包含命令耗时的 mean/p95/max 毫秒数、GC 次数与耗时、cons 分配总数
(:cons-cells,用于观察分配压力)、发布次数、最终
纯文本,以及与独立渲染已提交快照的结果是否一致。计算计数器 :layout-calls、
:surface-plans、:fragment-scans 和 :node-registrations 分别统计布局渲染、
surface 规划、fragment 提取(包含局部切片)和节点树注册的调用次数。
局部补丁也会执行规划;:full-surface-renders 单独统计完整候选渲染次数。
:fragment-characters 累计传给 fragment 提取的字符数;结合调用次数与字符总量,
区分局部处理和全 surface 扫描。调用次数不是受影响节点数。快照对比不一致则检查失败。
计时包括同步命令执行、发布和 GC。耗时、GC、发布次数及计算计数器都只覆盖命令 循环,不包括初次挂载及最后的快照对比渲染。该入口也不测量 GUI redisplay、 操作系统按键重复延迟或空闲预热。采样循环在命令之间检查 30 秒期限;成功、报错、 超时或中断时都会清理临时预览和自身计时器,并保留现有预览与窗口。后续交互回归 复用此入口,不另建临时的重复按键计时脚本。
诊断已有可见预览中的原生点击分派时,加载同一 evaluator 并调用
(ebox-playground-interaction-evaluator-click BUFFER LABEL &optional OFFSET)。
BUFFER 是 live buffer 或其名称;第一个匹配标签必须位于显示窗口的可见区域内。
OFFSET 是标签内从零开始的整数字符偏移,默认为零。例如,在预览 buffer 中求值:
(load (expand-file-name "scripts/ebox-playground-flex-resize-evaluator.el"
ebox-playground-directory) nil t t)
(ebox-playground-interaction-evaluator-click (current-buffer) "ADD " 0)
(ebox-playground-interaction-evaluator-click (current-buffer) "[-]" 1)
它构造 Emacs 鼠标事件,查找原生绑定并通过 call-interactively 调用,不注入操作
系统鼠标点击。辅助函数自身不切换窗口或移动 point,返回被点击的位置;业务结果需
另行通过计数器或事件日志检查。
编写和渲染 .ebox 文件
这个包也拥有 .ebox 文件入口。访问 Ebox DSL 文件时会自动进入 ebox-dsl-mode,header line 会显示 C-c C-c Render preview;按 C-c C-c 会求值文件中的唯一一个 Elisp 表达式,将得到的布局数据传给 ebox-build,渲染并自动并排显示源文件和预览 buffer。
(add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode))
.ebox 中写的表达式与 Elisp 中传给 (ebox-build ...) 的参数完全一致。
静态布局对整个列表加 quote,内部的 list 和 symbol 属性不再单独加 quote:
'(column :padding ((lh 1) (ch 2))
:cross-align center
(box :width (px 240) "Hello"))
必要的业务函数与变量放在 .ebox 旁边可选的同名 .el 文件中。每次预览求值前,
运行器先加载存在的对应 .el 源码,再求值 .ebox 表达式。C-c C-c、
ebox-playground-open-file 和文件渲染入口都遵循这个顺序;保存 .el 修改后再次
预览即可生效,不使用 require 缓存,也不选择旧 bytecode。允许没有配套文件;
配套文件加载出错则停止求值。普通 Elisp 变量定义决定重新加载时保留还是重置状态。
例如,把数据与操作放在 notes.el:
;;; notes.el --- Notes example behavior -*- lexical-binding: t; -*-
(defvar notes-body "A long article.")
(defun notes-help ()
"Return business help for the article."
(format "Article: %s" notes-body))
(defun notes-activate ()
"Mark the article in the current Ebox buffer."
(ebox-region-update "article" :color "#166534"))
notes.ebox 专注表达布局:
`(column :padding ((lh 1) (ch 2))
(box :id "article" :width (px 480)
:help-echo ,(ebox-help-create #'notes-help)
:pointer hand
:keymap ,(ebox-keymap-create :activate #'notes-activate)
,notes-body))
ebox-help-create 把返回字符串或 nil 的零参数业务函数适配为原生帮助,并提供悬停
buffer 上下文。ebox-keymap-create 的 :activate 处理 RET、[return]、SPC
和 [mouse-1]。
可选的 :bindings alist 将按键描述字符串或事件向量与零参数回调配对;也可以直接
传入原生 keymap。详见公共辅助函数契约。
运行器每次渲染都以词法绑定求值一个 .ebox 表达式。反引号保留布局数据,, 插入
值,,@ 展开子节点列表。结果中的属性和子节点都是数据,运行器不会再单独求值。
业务定义留在配套文件中;原生帮助和命令适配使用 Ebox 公共接口,不要仅为了隐藏
重复 DSL 属性列表而添加辅助函数。
默认采用简洁写法:省略冗余默认值,把可继承的共同文字样式放到已有父节点,子节点只写差异。
示例正在讲解的属性仍显式保留。编写原则
说明了哪些属性可以继承。空 Box 的自动内容高度为零;一行分隔框显式设置 :height (lh 1)。
简化示例时,使用已有 evaluator 对比保存的基准目录和当前 examples 目录:
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(ebox-playground-compare-example-directories "/path/to/baseline" "examples")'
可选的第三、第四参数指定视口宽度和文件名;默认检查两个示例在 720、1000 px 下的首屏与全文。 比较内容包括文字、有效 face、display、交互属性和逐行宽度,发现差异时 batch 命令失败。 该入口不创建文件或 GUI 窗口,后续同类任务直接复用它,不再编写一次性比较脚本。
尺寸数据统一使用 (单位 数值),支持 px、%、vw、vh、ch、lh,
以及 calc、min、max、clamp。统一单位约束表
规定每个属性允许的单位;不能跨轴混用,嵌套函数的所有分支也遵循同一规则。
无效组合在 Ebox 构造树时就报错。边框厚度采用表中独立的绘制单位规则。
例如比视口少一个行高,写
:height (calc (- (vh 100) (lh 1)));Ebox 会在每次布局时解析这个表达式。
fit-content 只作为裸关键词使用,不带参数。Ebox buffer 后端的纵向内容仍按
完整行量化,具体规则见 Ebox 尺寸契约。
迁移旧的裸结构 .ebox 时,静态布局在最外层加 quote,并去掉内部 list 和 symbol
属性的 quote,例如 :padding '(1 2) 改成 :padding ((lh 1) (ch 2))。动态值使用
反引号和显式逗号,例如计算像素宽度时写 :width (px ,(+ 200 40))。
裸长度、单元素像素列表和旧的视口关键词也需要迁移;运行器不自动识别旧格式。
参考 fixture 直接位于 examples/ 下。例如:
(ebox-playground-open-file
(expand-file-name "examples/grid-reference.ebox"
ebox-playground-directory))
在该目录运行 make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs。