CanvasEvents
ui.events画布级事件:滚动、缩放手势、拖拽与剪贴板图片往返。Canvas-level events: scroll, magnify, drag and a clipboard PNG round-trip.
BEHAVIOR CONTRACT
原始事件 payload 直达节点,不经过组件抽象。Raw event payloads reach the node without a component in between.
RECORDED INTERACTION
Harness 在画布上滚动,事件日志实时更新。The harness scrolls the canvas; the event log updates live.
pub fn buildCanvasEvents(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
const a = cx.allocator;
const c = try col(cx, 12);
try c.appendChild(a, try label(cx, "Canvas events — scroll modifiers / magnify / drag / clipboard image"));
const canvas = try ui.box(cx, .{
.width = .{ .px = 420 },
.height = .{ .px = 160 },
.background = light.color.bg_secondary,
}, .{});
canvas.meta.ownership.meta.test_id = "story.canvasevents.canvas";
const scroll_label = try ui.text(cx, "scroll (none)", .{ .font_size = 13, .color = light.color.fg_primary });
scroll_label.meta.ownership.meta.test_id = "story.canvasevents.scroll";
const magnify_label = try ui.text(cx, "magnify (none)", .{ .font_size = 13, .color = light.color.fg_primary });
magnify_label.meta.ownership.meta.test_id = "story.canvasevents.magnify";
const drag_label = try ui.text(cx, "drag (none)", .{ .font_size = 13, .color = light.color.fg_primary });
drag_label.meta.ownership.meta.test_id = "story.canvasevents.drag";
const clip_label = try ui.text(cx, "clip (none)", .{ .font_size = 13, .color = light.color.fg_primary });
clip_label.meta.ownership.meta.test_id = "story.canvasevents.clip";
const st = try cx.bindState(CanvasEventsStory, .{
.cx = cx,
.scroll_label = scroll_label,
.magnify_label = magnify_label,
.drag_label = drag_label,
.clip_label = clip_label,
});
canvas.behavior.events.on_scroll = CanvasEventsStory.onScroll;
canvas.behavior.events.on_event = CanvasEventsStory.onEvent;
canvas.behavior.events.event_context = st;
try c.appendChild(a, canvas);
try c.appendChild(a, scroll_label);
try c.appendChild(a, magnify_label);
try c.appendChild(a, drag_label);
try c.appendChild(a, clip_label);
const clip_btn = try W.Button(.{ .label = "Clipboard PNG roundtrip", .on_click = cx.on(CanvasEventsStory, st, CanvasEventsStory.clipboardRoundtrip) }).mount(scope, cx);
clip_btn.meta.ownership.meta.test_id = "story.canvasevents.clipbtn";
try c.appendChild(a, clip_btn);
return c;
}这是 Storybook 中该 story 的原样代码(zenit 5f9add5+wip 2026-09-30)。其中 W 即 ui.widgets,col / row / label 是 Storybook 内的布局小工具。The story's verbatim source from the Storybook (zenit 5f9add5+wip 2026-09-30). W is ui.widgets; col / row / label are small Storybook layout helpers.