---
title: "Gestures 컴포넌트 — zenit Zig UI"
description: "싱글, 더블, 트리플 클릭과 길게 누르기의 제스처 중재.하나의 히트 영역에서 여러 제스처를 시간 창으로 중재해 하나의 결과만 발생합니다."
url: https://zenit.z.express/ko/components/multiclick
language: ko
alternate_en: https://zenit.z.express/components/multiclick.md
alternate_zh: https://zenit.z.express/zh/components/multiclick.md
alternate_es: https://zenit.z.express/es/components/multiclick.md
alternate_ja: https://zenit.z.express/ja/components/multiclick.md
alternate_fr: https://zenit.z.express/fr/components/multiclick.md
alternate_de: https://zenit.z.express/de/components/multiclick.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# Gestures

`ui.interaction`

싱글, 더블, 트리플 클릭과 길게 누르기의 제스처 중재.

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

**BEHAVIOR CONTRACT**

하나의 히트 영역에서 여러 제스처를 시간 창으로 중재해 하나의 결과만 발생합니다.

**RECORDED INTERACTION**

harness가 제스처 영역을 더블 클릭하면 double 카운터가 1 증가합니다.

`examples/storybook/stories.zig:4695`

```zig
pub fn buildMultiClick(scope: *ui.Scope, cx: *ui.Cx) anyerror!*ui.Node {
    const a = cx.allocator;
    const c = try col(cx, 12);
    try c.appendChild(a, try label(cx, "Gesture arena — tap/double/triple counts + press-and-hold long_press (500ms) + pointer-cancel state"));

    const pad = try ui.box(cx, .{
        .width = .{ .px = 320 },
        .height = .{ .px = 140 },
        .background = light.color.bg_secondary,
        .align_items = .center,
        .justify = .center,
    }, .{});
    (try pad.style.ensureExtFallible(a)).corner_radius = ui.CornerRadius.uniform(10);
    pad.meta.ownership.meta.test_id = "story.multiclick.pad";
    try pad.appendChild(a, try ui.text(cx, "click / double-click / hold", .{ .font_size = 13, .color = light.color.fg_secondary }));
    try c.appendChild(a, pad);

    const single_label = try ui.text(cx, "single: 0", .{ .font_size = 13, .color = light.color.fg_primary });
    single_label.meta.ownership.meta.test_id = "story.multiclick.single";
    const double_label = try ui.text(cx, "double: 0", .{ .font_size = 13, .color = light.color.fg_primary });
    double_label.meta.ownership.meta.test_id = "story.multiclick.double";
    const triple_label = try ui.text(cx, "triple: 0", .{ .font_size = 13, .color = light.color.fg_primary });
    triple_label.meta.ownership.meta.test_id = "story.multiclick.triple";
    const long_label = try ui.text(cx, "long: idle", .{ .font_size = 13, .color = light.color.fg_primary });
    long_label.meta.ownership.meta.test_id = "story.multiclick.long";
    try c.appendChild(a, single_label);
    try c.appendChild(a, double_label);
    try c.appendChild(a, triple_label);
    try c.appendChild(a, long_label);

    const st = try cx.bindState(MultiClickStory, .{
        .cx = cx,
        .single_label = single_label,
        .double_label = double_label,
        .triple_label = triple_label,
        .long_label = long_label,
    });
    st.alive = true;
    st.rec_ids[0] = try cx.registerGesture(pad, .tap, .{}, MultiClickStory.onGesture, @ptrCast(st));
    st.rec_ids[1] = try cx.registerGesture(pad, .double_tap, .{}, MultiClickStory.onGesture, @ptrCast(st));
    st.rec_ids[2] = try cx.registerGesture(pad, .triple_tap, .{}, MultiClickStory.onGesture, @ptrCast(st));
    st.rec_ids[3] = try cx.registerGesture(pad, .long_press, .{}, MultiClickStory.onGesture, @ptrCast(st));

    pad.meta.per_frame.hooks.slots.anim_state = @ptrCast(st);
    pad.meta.per_frame.hooks.before_render.main = MultiClickStory.tickHook;

    const rc = try a.create(MultiClickCleanup);
    rc.* = .{ .cx = cx, .st = st };
    try scope.registerResource(@ptrCast(rc), MultiClickCleanup.destroy);
    return c;
}
```

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

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