GlassMotion
ui.widgets.GlassBoxSich verformendes Glas und eine Toolbar, die beim Scrollen schwebt.
BEHAVIOR CONTRACT
Der Hintergrund wird bei Verformungen und beim Scrollen in jedem Frame neu gesampelt.
RECORDED INTERACTION
Der Harness löst Morph und Scroll +80 aus.
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;
}Der unveränderte Quellcode der Story aus dem Storybook (zenit 5f9add5+wip 2026-09-30). W steht für ui.widgets; col / row / label sind kleine Layout-Helfer des Storybooks.
GlassBoxProps
ui.widgets.GlassBoxfn GlassBox(props: GlassBoxProps) GlassBoxBuilderFeld
Typ
Standard
Beschreibung
title?[]const u8null—
subtitle?[]const u8null—
width?f32null—
height?f32null—
padding?Paddingnull—
body_gapf3212—
radiusf3218—
readabilityGlassBoxReadability.balanced.preferred.maximum.preferred—
emphasisGlassBoxEmphasis.subtle.regular.prominent.regular—
variantGlassBoxVariant.regular.clear.regular—
tint?Colornull—
interactiveboolfalseApple Liquid Glass interactive:hover 提亮高光、press 玻璃增厚 + 微放大。
scroll_edge?ScrollEdgeBindingnullscroll-edge effect:绑定 ScrollArea 后,内容滚过玻璃边缘时出现渐进 模糊/压暗带(随滚动位置实时驱动 + 插值平滑)。
*Node
ui.widgets.GlassBoxmount gibt *Node direkt zurück – hängen Sie es an einen Elternknoten an.