---
title: "GlassLab 컴포넌트 — zenit Zig UI"
description: "GlassBox 광학 파라미터 실험대: 굴절, 베젤 너비, 확대.warp_gain, bezel_width, magnification이 하나의 Metal 샘플…"
url: https://zenit.z.express/ko/components/glasslab
language: ko
alternate_en: https://zenit.z.express/components/glasslab.md
alternate_zh: https://zenit.z.express/zh/components/glasslab.md
alternate_es: https://zenit.z.express/es/components/glasslab.md
alternate_ja: https://zenit.z.express/ja/components/glasslab.md
alternate_fr: https://zenit.z.express/fr/components/glasslab.md
alternate_de: https://zenit.z.express/de/components/glasslab.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# GlassLab

`ui.widgets.GlassBox`

GlassBox 광학 파라미터 실험대: 굴절, 베젤 너비, 확대.

[Video](https://zenit.z.express/media/stories/glasslab.mp4?v=a24856a853)

**BEHAVIOR CONTRACT**

warp\_gain, bezel\_width, magnification이 하나의 Metal 샘플링 경로에 실시간으로 작용합니다.

**RECORDED INTERACTION**

영상에서 유리를 드래그하며 파라미터 효과를 보여 줍니다.

`examples/storybook/stories.zig:1782`

```zig
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;
}
```

[GitHub에서 소스 보기examples/storybook/stories.zig:1782](https://github.com/version-next/zenit/blob/HEAD/examples/storybook/stories.zig#L1782)

Storybook에 있는 이 story의 원본 코드입니다(zenit 5f9add5+wip 2026-09-30). `W`는 `ui.widgets`이며, `col` / `row` / `label`은 Storybook 내부의 작은 레이아웃 헬퍼입니다.

### GlassBoxProps

`ui.widgets.GlassBox`

[src/ui/components/glass\_box/mod.zig:48](https://github.com/version-next/zenit/blob/HEAD/src/ui/components/glass_box/mod.zig#L48)

```zig
fn GlassBox(props: GlassBoxProps) GlassBoxBuilder
```

| 필드 | 타입 | 기본값 | 설명 |
| --- | --- | --- | --- |
| `title` | `?[]const u8` | `null` | — |
| `subtitle` | `?[]const u8` | `null` | — |
| `width` | `?f32` | `null` | — |
| `height` | `?f32` | `null` | — |
| `padding` | `?Padding` | `null` | — |
| `body_gap` | `f32` | `12` | — |
| `radius` | `f32` | `18` | — |
| `readability` | `GlassBoxReadability` `.balanced` `.preferred` `.maximum` | `.preferred` | — |
| `emphasis` | `GlassBoxEmphasis` `.subtle` `.regular` `.prominent` | `.regular` | — |
| `variant` | `GlassBoxVariant` `.regular` `.clear` | `.regular` | — |
| `tint` | `?Color` | `null` | — |
| `interactive` | `bool` | `false` | Apple Liquid Glass interactive：hover 提亮高光、press 玻璃增厚 + 微放大。 |
| `scroll_edge` | `?ScrollEdgeBinding` | `null` | scroll-edge effect：绑定 ScrollArea 后，内容滚过玻璃边缘时出现渐进 模糊/压暗带（随滚动位置实时驱动 + 插值平滑）。 |

### \*Node

`ui.widgets.GlassBox`

mount는 `*Node`을(를) 직접 반환합니다. 부모 노드에 append하면 됩니다.
