> 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/ui-reference.md).

# UI 参考

CSL UI 绘制的参考说明和模式。

本页收集了在你掌握基础后会很有帮助的 UI 规则、模式和示例。

阅读 [UI 基础](/all-out-docs/docs-zh/ui/fundamentals.md) 首先。

### 核心原则

1. **Y 轴向上增长。** 屏幕坐标使用左下角的 (0, 0)。
2. **从屏幕矩形开始。** 使用 `UI.get_safe_screen_rect()` 或 `UI.get_screen_rect()`，然后从这些矩形推导出其他一切。
3. **Push/pop 模式。** 使用 `defer` 用于每一次 push，以避免泄漏 UI 状态。
4. **移动优先。** 避免仅依赖悬停的交互。
5. **文本使用 UTF-8，但字符覆盖并不完整。** 字符仍然需要在所选字体或其回退字体中有对应字形。某些字符，包括表情符号，暂时还不支持。
6. **使用玩家的晚更新栈。** 将该玩家的所有 UI 绘制都从这个玩家的 `ao_late_update` 中， `在 is_local_or_server()`.

### 快速开始：一个简单的 HUD 按钮

```go
Player :: class : Player_Base {
    ao_late_update :: method(dt: float) {
        if this.is_local_or_server() {
            draw_my_hud(this);
        }
    }
}

draw_my_hud :: proc(player: Player) {
    rect := UI.get_safe_screen_rect()
        .bottom_right_rect()
        .grow(40, 150, 40, 150)
        .offset(-50, 50);

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

    if UI.button(rect, bs, ts, "操作").clicked {
        log_info("来自 % 的操作", {player.get_username()});
    }
}
```

### 基础绘制

#### 四边形和图像

```go
draw_ui :: proc() {
    rect := UI.get_screen_rect().center_rect().grow(100); // 200x200
    texture := get_asset(Texture_Asset, "my_icon.png");
    UI.quad(rect, texture);

    // 带颜色染色（RGBA 0-1）
    UI.quad(rect, texture, {1, 0, 0, 0.5});

    // 使用白色精灵绘制纯色
    UI.quad(rect, core_globals.white_sprite, {0, 0, 0, 0.75});
}
```

#### 填充四边形

传入 `Quad_Params.fill` 来自 `UI.quad_fill` 即可只显示四边形的一部分。可用的 `Fill_Direction` 值有 `.NONE`, `.RIGHT`, `.LEFT`, `.UP`, `.DOWN`，以及 `.RADIAL`.

线性填充使用从 `0` 到 `1`的数值。径向填充从 12 点钟方向开始，使用带符号的数值： `-1..0` 表示逆时针填充， `0..1` 表示顺时针填充。超出适用范围的值会被钳制。径向数值为 0 时不会绘制任何内容； `-1` 而 `1` 两者都会绘制完整的四边形。

```csl
draw_cooldown :: proc(rect: Rect, icon: Texture_Asset, amount: float) {
    params: Quad_Params;
    params.fill = UI.quad_fill(clamp(amount, -1, 1), .RADIAL);
    UI.quad(rect, icon, params=params);
}
```

对于变化中的填充，保留一个持久的 `Float_Interpolation_Helper` 并将其结果传给 `UI.quad_fill`。在调用 `update`之前先进行钳制；为了逆时针径向填充，必须保留符号。若同一种填充要应用于多个绘制调用，请使用 `UI.push_fill_amount` 而 `UI.pop_fill_amount` 。务必为 push 配对一个延迟的 pop。

#### 文本

```go
draw_text :: proc() {
    rect := UI.get_screen_rect().center_rect().grow(200, 300, 50, 300);

    ts := UI.default_text_settings();
    ts.size = 48;
    ts.color = {1, 1, 1, 1};
    ts.halign = .CENTER;
    ts.valign = .CENTER;

    UI.text(rect, ts, "你好，世界！");

    score := 1500;
    UI.text(rect, ts, "得分：%", {score});

    pct := 67;
    UI.text(rect, ts, "% %%", {pct}); // "67 %"

    // 返回实际渲染的矩形
    actual_rect := UI.text_sync(rect, ts, "动态文本");
}
```

### 布局：从矩形开始

```go
layout_example :: proc() {
    screen := UI.get_screen_rect();
    safe := UI.get_safe_screen_rect();

    center := screen.center_rect();
    top_left := screen.top_left_rect();
    bottom_right := screen.bottom_right_rect();

    button_rect := center.grow(50, 150, 50, 150);
    icon_rect := center.grow(64);
}
```

### 布局中使用 cut

cut 函数 **必须** 在放置多个 UI 元素时用于布局：

```go
draw_panel :: proc() {
    rect := UI.get_safe_screen_rect().inset(20);

    header := rect.cut_top(80);
    footer := rect.cut_bottom(60);
    body := rect;

    UI.quad(header, core_globals.white_sprite, {0.1, 0.1, 0.1, 0.8});
    UI.quad(footer, core_globals.white_sprite, {0.1, 0.1, 0.1, 0.8});
    UI.quad(body, core_globals.white_sprite, {0.05, 0.05, 0.05, 0.8});
}
```

### 自动缩放和未缩放矩形

常规矩形函数接受 **点** 并按 `UI.get_current_scale_factor()` 进行缩放（基于一个 1080 点高的参考画布）。

只在数值已经以实际屏幕像素表示时，才使用 `_unscaled` 变体，例如从已有矩形测得的尺寸：

```go
layout_with_unscaled :: proc(window_rect: Rect, items: []string) {
    ts := UI.default_text_settings();
    list_item := window_rect.top_rect().grow_bottom(20);

    for item: items {
        UI.text(list_item, ts, item);

        // height() 已经是屏幕像素，所以不要再次缩放它
        list_item = list_item.offset_unscaled(0, -list_item.height());
    }
}
```

### 按钮

```go
draw_buttons :: proc() {
    rect := UI.get_safe_screen_rect()
        .bottom_center_rect()
        .grow(40, 150, 40, 150)
        .offset(0, 100);

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

    bs.sprite = get_asset(Texture_Asset, "$AO/new/modal/buttons_2/button_2.png");
    bs.press_scaling = 0.35;

    result := UI.button(rect, bs, ts, "点击我！");
    if result.clicked {
        log_info("按钮被点击了！");
    }
}
```

#### 按钮精灵规则

`UI.default_button_settings()` 已经提供了完整的按钮样式。对于自定义按钮，设置 `sprite` ，并可选地设置 `sprite_hovered` 而 `sprite_pressed`。当可选精灵为空时，会复用基础精灵。

### 模态框

使用 `UI.begin_modal` / `UI.end_modal` 用于一个变暗的背景；当玩家点到模态框外部或按下 Escape 时，它会关闭。

```go
Player :: class : Player_Base {
    settings_open: bool;
}

draw_settings_modal :: proc(player: Player) {
    如果 !player.settings_open 则返回；

    UI.begin_modal(UI.get_screen_rect(), "settings_modal", ref player.settings_open);
    defer UI.end_modal();

    window := UI.get_safe_screen_rect().center_rect().grow(240, 360, 240, 360);
    UI.quad(window, core_globals.white_sprite, {0, 0, 0, 0.9});

    ts := UI.default_text_settings();
    ts.halign = .CENTER;
    ts.valign = .CENTER;
    UI.text(window, ts, "设置");
}
```

### UI 状态管理

使用 `defer` 用于每一对 push/pop：

```go
draw_layered_ui :: proc() {
    UI.push_screen_draw_context();
    defer UI.pop_draw_context();

    UI.push_layer(10); // 保持游戏 UI 位于原生聊天之下（层级 75）。
    defer UI.pop_layer();

    UI.push_color_multiplier({1, 1, 1, 0.5});
    defer UI.pop_color_multiplier();

    // 在这里绘制 UI
}
```

### 重复元素的 ID

在绘制列表或重复项时，推入唯一 ID：

```go
draw_list :: proc(items: []string) {
    rect := UI.get_safe_screen_rect().inset(20);
    bs := UI.default_button_settings();
    ts := UI.default_text_settings();

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

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

### 常见尺寸

一些在各种设备上看起来都不错的尺寸：

* 屏幕文本点数：标题 52，正文 36
* 屏幕矩形点数：简单对话框 600x400，标准按钮 210x74，退出按钮 65x65
* 世界空间文本大小：0.30 米

### 最佳实践

* 使用 `defer` 用于每一对 push/pop。
* 为重复元素推入 ID。
* 对计算得到的尺寸使用未缩放函数。
* 使用以下方式让图标宽高比适配： `rect.fit_aspect(texture.get_aspect())`.
* 始终检查 `在 is_local_or_server()` 在绘制交互式 UI 之前。
