拖拽与手势
直接操作——移动面板、缩放分栏、拖动滑块——与按钮按压建立在同一套事件机制上。Host 声明 :on-drag;Ebox 跟踪主键并逐阶段送出手势;ETAF 为每个阶段各开一次响应式批处理。框架在此之上加了两样应用不该自己重建的东西:每个指针手势的键盘等价操作,以及把手势位移变成调用者所有几何的 Behavior。本章把整个手势契约讲完:阶段与载荷、拖拽锁定、键盘路径、受控几何 Behavior 与随框架附带的缩放示例。事件定义 :on-drag 属性及其载荷,Host定义锁定修饰键,Action、Behavior 与错误边界定义 etaf-draggable、etaf-resizable 与它们的受控版本。
手势阶段与载荷
Section titled “手势阶段与载荷”一次拖拽是一串阶段,全部交给同一个处理器。主键按下时 Ebox 决定拥有者——指针下最近的 keymap 拥有者——并为整个手势保留这个目标,因此移出 Host 不会把拖拽交给指针现在下方的东西。每个阶段以一个 plist 到达,含阶段符号(start、move、end 或 cancel)、原始事件、节点、窗口与 buffer、窗口像素的起始与当前位置、相对起始的位移、取消原因,以及由按键合成的手势上的 :keyboard 标志。每个阶段各自一次响应式批处理,并在读取下一个指针事件前发布,因此写入响应式坐标的处理器在下一阶段看到的是自己已提交的布局,而不是慢一帧。
手势以两种方式之一结束。正常释放落 end;目标变得不可用——被移除、隐藏或禁用——或被 Escape、其他输入或错误中止的手势以 cancel 结束,:reason 是 target-unavailable、quit、input 或 error。处理器应把 cancel 当作「把乐观改动还原」,随附示例正是这么做的。
:drag-lock-modifier 让手势跨过按键释放继续存活。手势开始时按住该修饰键时,释放按钮不会结束手势:它继续跟踪指针(此时没有按住的按钮),直到下一次主键按下结束它,或 Escape 取消它。点击后拖拽因此合成一个手势,缩放把手在快速点击之后仍然跟着指针,就是这样实现的。修饰键与 :on-drag 一起声明在 Host 上,没有拖拽处理器的锁定修饰键在 Host 提交时被拒绝。随附示例用 shift,让这个行为可见且需要主动选择。
键盘等价操作
Section titled “键盘等价操作”每个指针手势都有键盘路径,而且由框架提供,而不是让每个应用自己发明按键。内置 Behavior 贡献各自的箭头绑定,见 Action、Behavior 与错误边界:可拖拽 Host 用 M-<方向> 沿它的轴移动,可缩放 Host 按它的边缘用 M-S-<方向> 放大缩小。聚焦且带 :on-drag 的 separator 或 slider 通过 etaf-direction 用普通方向键步进,见键盘。应用自己的命令则有一个说同一种手势语言的入口。
Function: etaf-drag-step (runtime host-ref columns rows)
Section titled “Function: etaf-drag-step (runtime host-ref columns rows)”从键盘把已挂载 RUNTIME 中的 HOST-REF 按 COLUMNS 与 ROWS 拖拽,并返回 HOST-REF。
参数。 COLUMNS 与 ROWS 是数字;可以为小数以做亚格移动。RUNTIME 必须已挂载,HOST-REF 必须指向带拖拽处理器的已提交 Host。
语义。 该调用恰好派发两个手势:位移为零的 start 与紧接着带位移的 end,因此所有对指针有效的处理器——包括可拖拽、可缩放 Behavior 与受控版本——无需特例就能从键盘工作。位移使用指针手势的单位:COLUMNS 个字符宽与 ROWS 行高,以像素计,按显示该 buffer 的窗口所在 frame 度量。手势带 :keyboard t,与指针输入区分;关心来源的应用也可以在事件层面查 etaf-event-pointer-p。由于只送 start 与 end,处理器看到一个起点与一个终点,括住手势的 on-start/on-end 回调照常运行。
返回。 HOST-REF。
错误。 参数不是数字时报 error→ etaf-event-error,消息 "Drag step COLUMNS and ROWS must be numbers";HOST-REF 未挂载时报 "No committed Host reference";处理器自身的错误照常传播;渲染期调用报 error→ etaf-render-side-effect-error。
示例。 (etaf-drag-step runtime 'handle 1 0)
参见。 :on-drag、etaf-draggable、etaf-resizable、etaf-direction。
内置 Behavior 覆盖每个坐标各自一个 ref 的常见情况。把几何当作一个聚合值的应用——管理器状态里的普通 alist、窗口控制器拥有的记录——改用受控版本。它们的形状相同:Behavior 消费 :on-drag,通过可选的 :delta 函数把像素位移换算成应用单位,再用下一份几何回调应用;应用始终是唯一持久权威,Behavior 在手势期间最多保留一份临时起点。cancel 时不写入任何东西并丢弃起点,因此被中止的手势不会留下更新到一半的记录。
etaf-draggable-controlled 读取几何 plist,推断或接收轴,并把移动后的坐标经 on-change 写回;etaf-resizable-controlled 读取 :x、:y、:width、:height,沿声明的 :edges 缩放,按声明的最小/最大值 clamp,并在左边缘或上边缘移动时移动原点,让对边保持不动。Action、Behavior 与错误边界定义它们的参数、校验与错误条件,包括移动与缩放处理器必须放在不同 Host 上这条规则:一个 Host 的每个 :on-drag 处理器都会收到每个手势。
键盘绑定随它们一起到来:受控可拖拽按它的轴贡献移动键,受控可缩放按它的边缘贡献放大缩小键,因此无论应用存四个 ref 还是一条记录,键盘路径都在。
保留式缩放示例
Section titled “保留式缩放示例”框架附带一个应用形态的示例,让整条路径——原生手势、受控几何、响应式发布与键盘取消——不必先写应用就能演练。它是一个普通的已挂载 Runtime,不是特殊模式:面板是一个绝对定位的普通 Host,带受控可缩放 Behavior,旁边的对等控件是独立、可聚焦的控件。
Function: etaf-retained-resize-open (&optional buffer-or-name)
Section titled “Function: etaf-retained-resize-open (&optional buffer-or-name)”挂载一个固定文本的缩放示例,返回 BUFFER-OR-NAME(默认 *ETAF Retained Resize*)。
参数。 BUFFER-OR-NAME 是要挂载进的 buffer 名;默认 *ETAF Retained Resize*。
语义。 buffer 被挂载一个通过受控可缩放 Behavior 上报几何的面板:拖拽缩放它的右边与下边,Shift 在按键释放后锁定手势,因此再按一次才结束它,Escape 取消锁定的手势并把尺寸还原到手势开始时的值。面板的正文是被盒子裁剪的固定文本,有意不做窗口边框或段落重排引擎;旁边的对等控件在面板缩放期间保持独立可聚焦、可激活。用同一个 buffer 重开会替换已有挂载;示例拒绝接管不是它创建的非空 buffer。示例不实现组件目录的窗口边框或文本重排。
返回。 buffer。
错误。 buffer 有不是示例挂载的内容时报 error→ user-error,消息 "Refusing to replace unrelated contents in %s";挂载错误照常传播。
示例。 (etaf-retained-resize-open)
参见。 etaf-resizable-controlled、etaf-drag-step、焦点。
手势失败遵循阶段模型。处理器报错会让手势以 cancel 结束,:reason 为 error,错误在该阶段的批处理排空后传播,剩余阶段不再运行。目标在手势中途消失会产生终止性的 cancel,:reason 为 target-unavailable,这是框架告诉处理器释放已获取资源的方式。锁定手势期间按 Escape 产生 :reason 为 quit 的 cancel。受控 Behavior 在每次 cancel 时丢弃起点,因此最安全的处理器模式就是示例用的那种:start 时记录起始值,move 与 end 时写入移动后的值,cancel 时还原记录。etaf-drag-step 先校验参数,报 etaf-event-error 而不是合成一个坏手势。