GlassMotion
ui.widgets.GlassBox玻璃的形变与随滚动浮动的工具条。Morphing glass and a toolbar that floats while scrolling.
BEHAVIOR CONTRACT
形变动画与滚动期间 backdrop 每帧重新采样。The backdrop is resampled every frame during morphs and scrolls.
RECORDED INTERACTION
Harness 触发 Morph 与 Scroll +80。The harness triggers Morph and Scroll +80.
pub fn buildGlassMotion(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
const a = cx.allocator;
const c = try col(cx, 16);
// ── morph:绝对定位玻璃在两个几何/profile 之间过渡 ──
try c.appendChild(a, try label(cx, "Morph — glass transitions between two geometries/profiles"));
const morph_stage = try glassBackdrop(cx, 200);
morph_stage.style.justify = .start;
morph_stage.style.align_items = .start;
const gm = try ui.widgets.GlassBox(.{ .title = "Morph", .width = 150, .height = 84 }).mountWithState(scope, cx);
gm.box.style.position = .absolute;
(try gm.box.style.ensureExtFallible(a)).inset = .{ .left = .{ .px = 12 }, .top = .{ .px = 12 } };
gm.box.meta.ownership.meta.test_id = "story.glassmotion.morph";
try morph_stage.appendChild(a, gm.box);
try c.appendChild(a, morph_stage);
// ── scroll-edge:玻璃工具条覆盖 ScrollArea 顶部,滚动出现边缘渐变 ──
try c.appendChild(a, try label(cx, "Scroll-edge — glass bar over scrolling content"));
const se_stage = try ui.box(cx, .{ .width = .{ .px = 420 }, .height = .{ .px = 220 }, .position = .relative }, .{});
const sa = try W.mountScrollArea(.{ .width = 420, .height = 220, .direction = .vertical }, scope, cx);
var i: usize = 0;
while (i < 40) : (i += 1) {
var buf: [32]u8 = undefined;
const txt = try std.fmt.bufPrint(&buf, "Content row {d}", .{i});
try sa.content.appendChild(a, try ui.text(cx, txt, .{ .font_size = 13, .color = light.color.fg_primary }));
}
try se_stage.appendChild(a, sa.container);
const bar = try ui.widgets.GlassBox(.{ .height = 44, .padding = ui.Padding.symmetric(8, 12) })
.scrollEdge(.{ .scroll = sa.state, .edge = .top, .fade_width = 28 })
.mountWithState(scope, cx);
bar.box.style.position = .absolute;
bar.box.style.width = .{ .px = 420 };
(try bar.box.style.ensureExtFallible(a)).inset = .{ .left = .{ .px = 0 }, .top = .{ .px = 0 } };
bar.box.meta.ownership.meta.test_id = "story.glassmotion.bar";
try bar.body.appendChild(a, try glassLabel(cx, "Floating glass toolbar", 600));
try se_stage.appendChild(a, bar.box);
try c.appendChild(a, se_stage);
const st = try cx.bindState(GlassMotionStory, .{ .glass = gm.state, .scroll = sa.state, .content = sa.content });
const controls = try ui.box(cx, .{ .direction = .row, .gap = 10 }, .{});
const morph_btn = try W.Button(.{ .label = "Morph", .on_click = cx.on(GlassMotionStory, st, GlassMotionStory.morph) }).mount(scope, cx);
morph_btn.meta.ownership.meta.test_id = "story.glassmotion.morphbtn";
try controls.appendChild(a, morph_btn);
const scroll_btn = try W.Button(.{ .label = "Scroll +80", .on_click = cx.on(GlassMotionStory, st, GlassMotionStory.scrollDown) }).mount(scope, cx);
scroll_btn.meta.ownership.meta.test_id = "story.glassmotion.scrollbtn";
try controls.appendChild(a, scroll_btn);
try c.appendChild(a, controls);
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.