GlassLab
ui.widgets.GlassBoxGlassBox 光学参数实验台:折射、边缘宽度与放大。A GlassBox optics bench: refraction, bezel width and magnification.
BEHAVIOR CONTRACT
warp_gain、bezel_width、magnification 实时作用于同一 Metal 采样路径。warp_gain, bezel_width and magnification act live on one Metal sampling path.
RECORDED INTERACTION
录像拖动玻璃并观察参数效果。The clip drags the glass to show the parameters.
pub fn buildGlassLab(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
const a = cx.allocator;
const c = try col(cx, 16);
// ── 画布:网格背景 + 可拖拽玻璃 ──
const bd = try glassBackdrop(cx, 300);
const drag_node = try glassCapsule(cx, 116, .{});
drag_node.meta.ownership.meta.test_id = "story.glasslab.drag";
drag_node.style.cursor = .pointer;
const body = capsuleBody(drag_node);
body.style.padding = .{ .left = 34, .right = 34, .top = 0, .bottom = 0 };
try body.appendChild(a, try glassLabel(cx, "Drag me", 650));
const drag_st = try a.create(GlassDragState);
drag_st.* = .{ .cx = cx, .target = drag_node };
try scope.registerResource(@ptrCast(drag_st), struct {
fn cleanup(ptr: *anyopaque, alloc: std.mem.Allocator) void {
const state: *GlassDragState = @ptrCast(@alignCast(ptr));
alloc.destroy(state);
}
}.cleanup);
drag_node.behavior.events.event_context = @ptrCast(drag_st);
drag_node.behavior.events.on_event = glassDragHandler;
try bd.appendChild(a, drag_node);
try c.appendChild(a, bd);
// ── 参数面板:两列滑杆 ──
const st = try a.create(GlassLabState);
st.cx = cx;
st.glass = capsuleBody(drag_node);
st.section = undefined;
st.light_arrow = undefined;
try scope.registerResource(@ptrCast(st), struct {
fn cleanup(ptr: *anyopaque, alloc: std.mem.Allocator) void {
const state: *GlassLabState = @ptrCast(@alignCast(ptr));
alloc.destroy(state);
}
}.cleanup);
const panel = try ui.box(cx, .{ .direction = .row, .gap = 16 }, .{});
var columns: [2]*ui.Node = undefined;
for (0..2) |i| {
columns[i] = try ui.box(cx, .{ .direction = .column, .gap = 10 }, .{});
try panel.appendChild(a, columns[i]);
}
// ── 镜片侧截面示意(实时跟随 thickness/bezel/distance)──
const section_col = try ui.box(cx, .{ .direction = .column, .gap = 6 }, .{});
try section_col.appendChild(a, try label(cx, "lens cross-section"));
const section_frame = try ui.box(cx, .{
.width = .{ .px = 260 },
.height = .{ .px = 130 },
.position = .relative,
.background = ui.Color.rgba(244, 246, 250, 255),
.border = .{ .width = 1, .color = ui.Color.rgba(210, 214, 224, 255), .radius = 10 },
}, .{});
(try section_frame.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);
// 底部背板线(玻璃悬浮参照)
const backdrop_line = try ui.box(cx, .{
.width = .{ .grow = .{} },
.height = .{ .px = 2 },
.position = .absolute,
.background = ui.Color.rgba(150, 158, 172, 255),
}, .{});
(try backdrop_line.style.ensureExtFallible(a)).inset = .{ .top = .{ .px = 114 } };
try section_frame.appendChild(a, backdrop_line);
// 玻璃截面本体(path 填充)
const section = try ui.box(cx, .{
.width = .{ .grow = .{} },
.height = .{ .grow = .{} },
.position = .absolute,
.background = ui.Color.rgba(120, 168, 255, 150),
}, .{});
try section_frame.appendChild(a, section);
// 光向指示箭头(右上角,绕中心旋转)
const arrow = try ui.box(cx, .{
.width = .{ .px = 30 },
.height = .{ .px = 3 },
.position = .absolute,
.background = ui.Color.rgba(240, 170, 40, 255),
}, .{});
const arrow_ext0 = try arrow.style.ensureExtFallible(a);
arrow_ext0.inset = .{ .top = .{ .px = 16 }, .left = .{ .px = 216 } };
arrow_ext0.corner_radius = ui.CornerRadius.uniform(1.5);
try section_frame.appendChild(a, arrow);
try section_col.appendChild(a, section_frame);
try panel.appendChild(a, section_col);
inline for (LAB_SPECS, 0..) |spec, i| {
const prow = try ui.box(cx, .{ .direction = .column, .gap = 2 }, .{});
try prow.appendChild(a, try ui.text(cx, spec.name, .{ .font_size = 12, .color = light.color.fg_secondary }));
const sl = try W.Slider(.{
.min = spec.min,
.max = spec.max,
.step = spec.step,
.initial_value = spec.default,
.width = 200,
.show_value = true,
.on_change = .{ .callback = labApply, .context = @ptrCast(st) },
}).mount(scope, cx);
st.sliders[i] = sl.state;
try prow.appendChild(a, sl.wrapper);
try columns[i % 2].appendChild(a, prow);
}
st.section = section;
st.light_arrow = arrow;
try c.appendChild(a, panel);
labApply(@ptrCast(st));
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.