> For the complete documentation index, see [llms.txt](https://docs.allout.game/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.allout.game/all-out-docs/docs-zh/ui/scroll-and-grid.md).

# 滚动与网格

***

滚动视图和网格让构建物品栏、列表、菜单和卡片布局变得容易。本指南展示两者的正确模式，包括布局、ID 和滚动条。

{% hint style="info" %}
遵循 UI 基础原则：从屏幕矩形开始，使用 cut 进行布局，并始终使用 `延后执行` 用于 push/pop 成对调用。
{% endhint %}

### 滚动视图

对于可能超出可用空间的内容使用滚动视图。滚动条必须位于滚动视图内容区域之外。

```go
draw_scrollable_list :: proc(items: []string) {
    rect := UI.get_safe_screen_rect().inset(50);

    // 先为滚动条预留空间
    scroll_bar_area := rect.cut_right(8).inset(2);

    settings: Scroll_View_Settings;
    settings.vertical = true;
    settings.horizontal = false;
    settings.clip_padding = {10, 10, 10, 10};

    sv := UI.push_scroll_view(rect, "my_scroll_view", settings); {
        defer UI.pop_scroll_view();

        content := sv.content_rect;
        ts := UI.default_text_settings();
        ts.size = 24;

        for i: 0..<items.count {
            item_rect := content.cut_top(40);
            UI.text(item_rect, ts, items[i]);
        }
    }

    scroll_bar_rect := sv.compute_scroll_bar_rect(scroll_bar_area);

    UI.quad(scroll_bar_area, core_globals.white_sprite, {0.025, 0.025, 0.025, 1});
    UI.quad(scroll_bar_rect, core_globals.white_sprite, {0.1, 0.1, 0.1, 1});
}
```

**重要：** 如果滚动条位于滚动视图内容内部，它会破坏边界和滚动行为。务必先将滚动条区域裁出来。

### 网格布局

使用 `Grid_Layout` 用于物品栏槽位或图标网格等大小相同的元素。网格从左上角开始，向右推进，然后换行到下一行。

```go
draw_inventory_grid :: proc(items: []string) {
    rect := UI.get_safe_screen_rect().inset(40);

    bs := UI.default_button_settings();
    ts := UI.default_text_settings();

    // 基于元素数量，4 列 x 6 行，8 点内边距
    grid := UI.make_grid_layout(rect, 4, 6, .ELEMENT_COUNT, 8);

    for i: 0..<items.count {
        UI.push_id("slot_%", {i});
        defer UI.pop_id();

        slot_rect := grid.next();
        if UI.button(slot_rect, bs, ts, items[i]).clicked {
            log_info("已选择项目 %", {i});
        }
    }
}
```

如果你更喜欢固定大小的平铺，请使用 `.ELEMENT_SIZE` 而不是 `.ELEMENT_COUNT`:

```go
grid := UI.make_grid_layout(rect, 100, 100, .ELEMENT_SIZE, 8);
```

其中 `next()` 方法会更新网格状态：

```go
slot_rect := grid.next();
grid.index; // 0, 1, 2, ...
grid.cur_x; // 列
grid.cur_y; // 行
```

### 可滚动网格

`Grid_Layout` 在内部使用时会自动扩展滚动视图。此模式非常适合大型物品栏：

```go
draw_scrollable_grid :: proc(items: []string) {
    rect := UI.get_safe_screen_rect().inset(50);
    scroll_bar_area := rect.cut_right(8).inset(2);

    settings: Scroll_View_Settings;
    settings.vertical = true;
    settings.horizontal = false;
    settings.clip_padding = {10, 10, 10, 10};

    sv := UI.push_scroll_view(rect, "inventory_scroll", settings); {
        defer UI.pop_scroll_view();

        grid := UI.make_grid_layout(sv.content_rect, 4, 6, .ELEMENT_COUNT, 8);
        bs := UI.default_button_settings();
        ts := UI.default_text_settings();

        for i: 0..<items.count {
            UI.push_id("slot_%", {i});
            defer UI.pop_id();

            slot_rect := grid.next();
            if UI.button(slot_rect, bs, ts, items[i].name).clicked {
                select_item(i);
            }
        }
    }

    scroll_bar_rect := sv.compute_scroll_bar_rect(scroll_bar_area);
    UI.quad(scroll_bar_area, core_globals.white_sprite, {0.025, 0.025, 0.025, 1});
    UI.quad(scroll_bar_rect, core_globals.white_sprite, {0.1, 0.1, 0.1, 1});
}
```

### 最佳实践

* 使用 `UI.push_id()` 用于列表或网格中重复出现的元素。
* 在创建滚动视图之前先裁出滚动条区域。
* 从 `UI.get_safe_screen_rect()` 开始，并推导所有布局。
* 尽量减少仅悬停交互（移动优先）。
