GlassMotion
ui.widgets.GlassBoxガラスの変形と、スクロールに合わせて浮かぶツールバー。
BEHAVIOR CONTRACT
変形アニメーション中やスクロール中は、backdrop を毎フレーム再サンプリングします。
RECORDED INTERACTION
harness が Morph と 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 内の小さなレイアウトヘルパーです。
GlassBoxProps
ui.widgets.GlassBoxfn GlassBox(props: GlassBoxProps) GlassBoxBuilderフィールド
型
デフォルト値
説明
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 は *Node を直接返します。親ノードに append してください。