> For the complete documentation index, see [llms.txt](https://docs.myarc.icu/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.myarc.icu/gong-neng-pei-zhi/application-api.md).

# 开发者应用 API

通过 ArcMenu 的开发者 API，附属插件不仅可以在现有的 YAML 菜单上挂载业务回调、绑定会话级动态数据，还可以注册完全由代码托管的交互应用。聊天面板、音乐播放器、卡牌对局、公会管理或独立登录流程，都可以直接复用 ArcMenu 的屏幕平面、指针输入、提示框与路由历史。

## 引入依赖与获取服务

在构建配置中将 `arcmenu-api` 声明为 `provided` 依赖，并在插件的 `plugin.yml` 或 `paper-plugin.yml` 中将 `ArcMenu` 加入 `depend` 或 `softdepend`。在 Maven 中可以使用 `arcmenu.version` 属性管理依赖版本，并将其设置为你服务端所使用的 `arcmenu-api` 构件版本：

```xml
<properties>
    <!-- 请替换为服务端当前使用的 arcmenu-api 版本号 -->
    <arcmenu.version>...</arcmenu.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.fentai</groupId>
        <artifactId>arcmenu-api</artifactId>
        <version>${arcmenu.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
```

ArcMenu 启动后会通过 Bukkit 的 `ServicesManager` 注册 `ArcMenuApi` 以及各细分服务接口。Java 与 Kotlin 插件均可直接加载：

```java
import com.fentai.arcmenu.api.ArcMenuApi;
import com.fentai.arcmenu.api.ArcMenuCursorApi;
import org.bukkit.Bukkit;

ArcMenuApi api = Bukkit.getServicesManager().load(ArcMenuApi.class);
if (api == null) {
    throw new IllegalStateException("ArcMenu API service is unavailable");
}

// 可通过主接口获取子服务，也可直接向 ServicesManager 加载
ArcMenuCursorApi cursor = api.cursor();
```

请只引用 `com.fentai.arcmenu.api` 包下的公开接口与数据类型，不要直接引用 `paper` 或 `core` 模块下的内部实现类。当 ArcMenu 卸载时，服务注册会自动注销。

### 核心服务概览

`ArcMenuApi` 除了提供基础的菜单打开、关闭、动画控制与应用注册外，还提供以下九项细分服务访问器：

| 访问器              | 接口类型                  | 用途说明                                                      |
| ---------------- | --------------------- | --------------------------------------------------------- |
| `sessions()`     | `ArcMenuSessions`     | 查询玩家当前的运行时会话快照，并按精确的会话令牌（Token）安全投递异步任务。                  |
| `menus()`        | `ArcMenuMenus`        | 发起带完成回调的菜单导航、读取/校验/重载菜单目录快照、注册后端区域业务回调。                   |
| `state()`        | `ArcMenuState`        | 绑定隔离的会话级键值数据、读写 `META`/`DATA`/`GLOBAL` 状态、管理菜单参数与解析占位符文本。 |
| `textInput()`    | `ArcMenuTextInput`    | 读取已提交的 Arcitx 输入内容，或在当前会话中主动发起原生铁砧文本输入请求。                 |
| `effects()`      | `ArcMenuEffects`      | 播放、暂停、恢复或停止 Effect 过场动画，并监听播放结束原因。                        |
| `availability()` | `ArcMenuAvailability` | 查询服务端支持的功能集合，以及玩家当前的指针模式、Vision 握手状态和资源包状态。               |
| `preferences()`  | `ArcMenuPreferences`  | 读取、修改或清除玩家的指针灵敏度与光标大小档位。                                  |
| `cursor()`       | `ArcMenuCursorApi`    | 检测玩家当前连接是否已完成兼容的 ArcMenu Editor Vision 握手。                |
| `resources()`    | `ArcMenuResources`    | 查询已解析图片的像素与字形元数据，以及计算快捷栏滚轮步进。                             |

## 线程模型与区域调度

所有针对单个玩家的方法与回调都必须在**该玩家所属的线程**上执行：在 Paper 或 Leaf 上为主线程，在 Folia 上为该玩家实体当前所属的区域线程（Entity Region Thread）。不同玩家的回调可能并发运行，请勿在一个玩家的回调中直接操作其他玩家的实体。

当你在异步线程（例如数据库查询或网络请求回调）中完成了业务逻辑、需要更新界面时，请使用 `api.sessions().execute(...)` 或托管应用内的 `context.execute(...)` 切回玩家线程。切勿在异步回调中直接调用打开、刷新或实体修改接口。

菜单目录的全局校验 `menus().validate()`、重载 `menus().reload()` 以及 `reloadMenuFiles()` 必须在启动阶段或全局线程（Global Tick Thread）上调用。应用、路由和区域处理器的注册方法是线程安全的，可在插件启动时调用；当附属插件卸载时，ArcMenu 会自动清理该插件名下的所有注册项、会话绑定与打开请求。

## 托管应用开发（Hosted Applications）

如果你想用代码完全接管界面渲染与交互，可以注册一个托管应用。

### 注册应用与配置项

应用 ID 必须采用 `命名空间:路径` 格式（小写字母、数字、下划线、点、连字符或斜杠），例如 `myplugin:shop`。通过 `registerApplication` 注册应用工厂后，每当玩家打开该应用，工厂都会为其创建一个独立的 `ArcMenuApplicationSession` 实例：

```java
import com.fentai.arcmenu.api.ArcMenuApplicationHandle;
import com.fentai.arcmenu.api.ArcMenuApplicationOptions;
import com.fentai.arcmenu.api.ArcMenuCanvas;

ArcMenuApplicationHandle handle = api.registerApplication(
    this,
    "myplugin:shop",
    ArcMenuApplicationOptions.builder()
        .permission("myplugin.shop")
        .captureMouseScroll(true)
        .inheritCanvas(true)
        .canvas(new ArcMenuCanvas(320.0, 180.0, 100.0, 3.0))
        .tickInterval(1)
        .build(),
    ShopSession::new
);
```

`ArcMenuApplicationOptions` 支持以下配置值：

* `permission`：字符串，打开该应用所需的权限节点；默认空字符串表示不额外限制应用权限（玩家仍需具备基础使用权限）。
* `captureMouseScroll`：布尔值，默认为 `false`。设为 `true` 时，在鼠标相关模式下会捕获快捷栏滚轮并触发 `onScroll` 回调。
* `inheritCanvas`：布尔值，默认为 `true`。从已有菜单切换到应用时，是否沿用上一页面的画布尺寸与比例。
* `canvas`：`ArcMenuCanvas` 对象，定义应用的逻辑宽高（`width`、`height`）、每方块像素数（`pixelsPerBlock`）与设计距离（`designDistance`），所有数值必须为大于 `0` 的有限浮点数。
* `tickInterval`：整数，取值范围为 `1..1200`，定义触发 `onTick` 的间隔刻数，默认为 `1`。

注册后，除了在代码中调用 `api.openApplication(player, "myplugin:shop", List.of("featured"))`，也可以直接在 YAML 菜单动作中打开并传参：

```yaml
actions:
  right:
    - 'open-app: myplugin:shop featured'
```

此外，如果只需要将命名空间标识转发到已有菜单或自定义逻辑，可通过 `api.registerRoute(this, "myplugin:Entry", (player, args) -> ...)` 注册轻量级虚拟路由。

### 屏幕表面（Surface）与世界坐标转换

应用会话通过 `ArcMenuApplicationContext` 与当前屏幕交互。`context.surface()` 返回不可变的 `ArcMenuSurface` 快照，包含当前所在世界、屏幕原点 `origin()`、右向量 `right()`、上向量 `up()`、法向量 `normal()` 以及画布参数。

要在屏幕上摆放文本、物品展示实体或自定义特效，使用 `surface().toWorld(x, y, depth)` 即可将画布逻辑坐标转换为 Minecraft 世界坐标：

* `x` 与 `y` 为画布逻辑坐标（中心为 `0, 0`，向右为 `+x`，向上为 `+y`）。
* `depth` 为深度偏移（默认为 `0.0`，正数表示朝玩家视角方向拉近）。

当玩家从菜单切换到应用，或从应用通过 `context.openMenu(...)`、`context.openApplication(...)`、`context.back()` 导航时，ArcMenu 会复用同一个屏幕平面，界面不会随视角晃动而移位。

### 私有实体、资源托管与内部调度

在托管应用中创建的展示实体应当只对当前玩家可见，并随会话关闭自动销毁：

* `context.spawnPrivate(location, TextDisplay.class, display -> { ... })`：在指定位置生成一个仅当前玩家可见的私有实体，并自动纳入当前会话清理列表。
* `context.track(entity)` / `context.track(task)` / `context.track(closeable)`：将已有的实体、`BukkitTask` 或 `AutoCloseable` 资源登记到会话中。无论玩家是主动关闭、返回上一页、掉线还是因插件卸载而退出，所有被 `track` 的对象都会在会话结束时自动销毁或关闭。
* `context.execute(runnable)`：可安全从异步线程调用。仅当该应用会话仍然处于活动状态时，才会把任务投递到玩家所属区域线程执行。
* `context.runLater(delayTicks, runnable)` 与 `context.runTimer(delayTicks, periodTicks, runnable)`：在玩家所属区域线程上调度延迟或定时任务。Folia 下任务会自动跟随玩家跨区域迁移；如需提前取消，请使用返回的 `BukkitTask` 句柄。

### 会话生命周期与输入回调

继承 `ArcMenuApplicationSession` 后，可按需重写以下回调方法：

* `onSurfaceChanged(ArcMenuSurfaceEvent)`：会话初次建立（`initial == true`）或屏幕平面重新锚定时触发。
* `onPointerModeChanged(ArcMenuPointerModeEvent)`：玩家的输入模式在 `TOUCH`、`MOUSE`、`VISION` 之间切换时触发。
* `onPointerMove(ArcMenuPointerMoveEvent)`：指针在画布上移动时触发，提供当前坐标 `point` 与上一坐标 `previous`。
* `onActivate(ArcMenuActivateEvent)`：玩家点击时触发，包含点击坐标 `point`、按键 `button`（`LEFT`、`RIGHT`、`MIDDLE`）、指针模式 `pointerMode` 以及潜行状态 `sneaking`。
* `onScroll(ArcMenuScrollEvent)`：开启 `captureMouseScroll` 后，玩家滚动滚轮时触发。`steps` 为正数表示向下一个快捷栏槽位滚动，负数表示向上一个槽位滚动。
* `onTick(ArcMenuTickEvent)`：按配置的 `tickInterval` 定期触发。
* `onClose(ArcMenuApplicationCloseEvent)`：会话关闭时触发，`reason` 标明关闭原因（`REQUESTED`、`BACK`、`REPLACED`、`PLAYER_QUIT`、`PLUGIN_DISABLE`、`OWNER_DISABLED`、`ERROR`）。

在交互过程中，可调用 `context.setCursorInteractive(true)` 将光标切换为可交互样式，或调用 `context.showTooltip(point, lines)` 与 `context.hideTooltip()` 显示、隐藏原生提示框。

## 运行时会话与异步数据更新

对于普通 YAML 菜单或托管应用，`api.sessions().current(player)` 会返回当前的 `ArcMenuSessionSnapshot`（若当前未打开运行态菜单或应用，则返回 `null`）。快照包含：

* `getToken()`：当前物理会话的唯一 `UUID` 令牌。每次新打开或切换界面都会生成新令牌。
* `getKind()`：`MENU` 或 `APPLICATION`。
* `getId()`：当前菜单 ID 或应用 ID。
* `getArguments()`：不可变的参数列表。
* `getPointerMode()`：当前生效的指针模式。
* `getCatalogRevision()`：打开该会话时所使用的菜单目录修订号。

当你在异步线程查询数据库并将结果写回当前菜单时，结合**会话令牌**与\*\*会话绑定数据（Session Bindings）\*\*可以彻底避免“玩家已经翻页或关掉菜单，旧的异步结果才返回并污染新界面”的问题：

```java
import com.fentai.arcmenu.api.ArcMenuSessionBindings;
import com.fentai.arcmenu.api.ArcMenuSessionSnapshot;
import java.util.concurrent.CompletableFuture;

ArcMenuSessionSnapshot session = api.sessions().current(player);
if (session != null) {
    ArcMenuSessionBindings bindings = api.state().bind(this, player, session.getToken());
    if (bindings != null) {
        CompletableFuture.supplyAsync(() -> "1280").thenAccept(coins ->
            api.sessions().execute(this, player, session.getToken(), () -> {
                bindings.set("coins", coins);
                api.refresh(player, "coins-label");
            })
        );
    }
}
```

在 YAML 菜单文本中，可以使用 `{session:<命名空间>:键名}` 读取绑定值。`<命名空间>` 请以 `bindings.getNamespace()` 的返回值为准（由插件名转换出的小写安全标识符），例如 `{session:myplugin:coins}`。绑定数据的生命周期与该次会话及附属插件绑定，会话结束或调用 `bindings.close()` 时会自动清空，不会残留到全局或持久化数据中。

如需监听会话的精确开启与关闭，可注册 Bukkit 事件 `ArcMenuSessionOpenedEvent` 与 `ArcMenuSessionClosedEvent`。

## 菜单导航、历史策略与锁机制

除了返回布尔值的传统方法 `api.open(...)` 和 `api.close(player)`，`api.menus()` 提供了支持历史栈策略与最终结果回调的导航请求接口：

```java
import com.fentai.arcmenu.api.ArcMenuHistoryPolicy;
import com.fentai.arcmenu.api.ArcMenuOpenOutcomeKind;
import com.fentai.arcmenu.api.ArcMenuOpenRequest;
import java.util.List;

ArcMenuOpenRequest request = api.menus().open(
    this, player, "shop", List.of("featured"),
    ArcMenuHistoryPolicy.PUSH,
    outcome -> {
        if (outcome.getKind() == ArcMenuOpenOutcomeKind.OPENED) {
            player.sendMessage("已打开菜单：" + outcome.getSession().getId());
        }
    }
);
```

* **历史策略（`ArcMenuHistoryPolicy`）**：
  * `PUSH`：默认值，将当前页面压入返回栈，后续可通过 `back` 返回。
  * `RESET_ON_SUCCESS`：目标页面成功打开后清空返回栈（也可直接调用快捷方法 `api.menus().openRoot(...)`）。
  * `PRESERVE`：保留现有的返回栈不变。
* **打开结果（`ArcMenuOpenOutcomeKind`）**：
  * `OPENED`：菜单已成功打开。注意检查 `outcome.getSession().getId()`，因为权限回退菜单（`permission-denied-menu`）或回调可能会将实际打开的目标重定向到其他菜单。
  * `ROUTE_HANDLED`：请求被注册的扩展路由接管处理。
  * 其他状态包括 `REJECTED`（被拒绝）、`SUPERSEDED`（被后续导航覆盖）、`CANCELLED`（调用了 `request.cancel()`）、`TIMED_OUT`、`PLAYER_LEFT`、`OWNER_DISABLED` 与 `ERROR`。
* **返回上一页**：调用 `api.menus().back(this, player, outcome -> { ... })`，若返回失败会保留原有历史栈。

在普通运行态 YAML 菜单即将打开前，会触发通知事件 `ArcMenuBeforeOpenEvent`，可通过 `isSwitching()` 与 `isFirstOpen()` 判断是页面切换还是首次打开。

对于登录或新手引导等强制停留场景，可使用 `api.lockMenu(this, player, "login", List.of())` 获取 `ArcMenuLock`。锁处于激活状态时，玩家无法关闭菜单或切换到其他普通菜单；你可以通过 `lock.open("register", List.of())` 在锁定状态下切换页面，验证完成后调用 `lock.close()` 即可释放导航限制。

## 后端区域业务回调（Region Handlers）

如果你用可视化编辑器或 YAML 搭建好了菜单外观，希望由 Java/Kotlin 代码直接处理某个按钮的点击逻辑（例如购买校验、异步扣款），可以为指定菜单与区域 ID 注册专属的业务处理器：

```java
import com.fentai.arcmenu.api.ArcMenuRegionResult;
import com.fentai.arcmenu.api.ArcMenuRegistration;

ArcMenuRegistration reg = api.menus().registerRegionHandler(this, "shop", "buy-button", context -> {
    Player buyer = context.getPlayer();
    String inputAmount = context.getInputValue(); // 若该区域关联了 Arcitx 提交，可直接读取本次提交值
    // 启动异步业务或直接处理逻辑
    return ArcMenuRegionResult.SKIP_YAML;
});
```

每个 `menuId + regionId` 组合同一时间只允许注册一个业务处理器，它在区域条件（`condition`）通过且完成 Arcitx 提交记录之后、YAML 自身的 `actions` 执行之前运行：

* 返回 `ArcMenuRegionResult.CONTINUE_YAML`：继续执行 YAML 中为该区域配置的点击动作。
* 返回 `ArcMenuRegionResult.SKIP_YAML`：跳过 YAML 动作，完全由代码接管后续反应。若在处理器中发起了异步任务，请立即返回 `SKIP_YAML`，待异步完成后再通过 `sessions().execute(...)` 刷新或跳转。

若只需在点击发生后做旁路统计或日志监听，可监听只读事件 `ArcMenuRegionActivatedEvent`，并通过 `getYamlActionsSkipped()` 查看 YAML 动作是否被跳过。

## 状态管理与原地刷新

### 状态读写与占位符解析

通过 `api.state()` 可以直接读写菜单系统中的三类数据作用域（`ArcMenuStateScope`）：

* `META`：插件本次运行期间按玩家保存在内存中的临时数据（玩家退服不会清空，在显式删除或插件关闭时清空）。
* `DATA`：跟随玩家持久保存的数据。
* `GLOBAL`：全服共享的持久化数据。

```java
import com.fentai.arcmenu.api.ArcMenuStateScope;

api.state().set(player, ArcMenuStateScope.META, "selected-tab", "weapons");
String tab = api.state().get(player, ArcMenuStateScope.META, "selected-tab");
api.state().remove(player, ArcMenuStateScope.META, "selected-tab");

// 读取或修改当前菜单的位置参数（对应 {0}、{1} 等）
List<String> args = api.state().arguments(player);
api.state().setArguments(player, List.of("page-2"));

// 按照运行时规则解析一段包含占位符与数学表达式的文本
String text = api.state().resolveText(player, "当前余额：%vault_eco_balance%");
```

### 立即刷新运行中菜单

当玩家的金币、积分或任务进度发生变化时，无需重新打开菜单，直接调用 `refresh` 即可原地更新占位符内容：

```java
api.refresh(player);            // 刷新当前菜单中的所有文本、图片以及正悬停的提示框
api.refresh(player, "balance"); // 仅刷新指定 ID 的文本/图片节点或提示框区域（包含 update: -1 的静态节点）
```

`refresh` 必须在玩家所属线程调用。它会保留当前的菜单实体、指针位置与动画播放进度，并且只在解析后的显示内容确实发生变化时才发送更新包。当存在活动的运行态菜单（及对应目标节点）时返回 `true`，否则静默返回 `false`。

管理员或控制台也可以直接执行 `/arcmenu refresh <在线玩家> [元素ID]` 触发同样的原地刷新。

## 显式文本输入服务

`api.textInput()` 提供两项能力：

1. **读取 Arcitx 已提交内容**：调用 `api.textInput().committed(player, "search-box")` 可获取当前运行态菜单中指定 Arcitx 区域最后一次正式提交的字符串（不会返回尚未提交的编辑草稿）。你也可以监听 `ArcMenuInputSubmittedEvent` 获取提交事件。
2. **主动发起原生铁砧输入请求**：在运行态菜单或托管应用中，插件可以随时唤起一个绑定到当前会话的原生输入界面：

```java
import com.fentai.arcmenu.api.ArcMenuTextInputEndReason;
import com.fentai.arcmenu.api.ArcMenuTextInputHandle;
import com.fentai.arcmenu.api.ArcMenuTextInputOptions;

ArcMenuTextInputHandle handle = api.textInput().request(
    this, player,
    ArcMenuTextInputOptions.builder()
        .title("输入搜索关键词")
        .initialValue("")
        .maxLength(30)
        .mask("")
        .build(),
    result -> {
        if (result.getReason() == ArcMenuTextInputEndReason.SUBMITTED) {
            player.sendMessage("你提交了：" + result.getValue());
        }
    }
);
```

`ArcMenuTextInputOptions` 参数要求：

* `maxLength`：整数，取值范围 `1..50`（按 UTF-16 编码单元计算），默认 `50`。
* `initialValue`：初始字符串，长度不可超过 `maxLength`。
* `mask`：为空字符串或单个 Unicode 字符（如 `"*"`），用作界面预览的掩码提示。

返回的 `ArcMenuTextInputHandle` 可用于读取实时草稿 `value()`、由代码主动提交 `submit()` 或取消输入 `cancel()`。只有当玩家点击完成槽位或代码调用 `submit()` 时，结束原因才是 `SUBMITTED`；直接按 Esc 关闭输入界面会触发 `CANCELLED`，不会误提交草稿。

## Effect 过场动画控制

通过 `api.effects()` 可以在代码中播放并控制独立的 Effect 过场动画项目：

```java
import com.fentai.arcmenu.api.ArcMenuEffectHandle;

ArcMenuEffectHandle playback = api.effects().play(this, player, "rtp-overworld", end -> {
    player.sendMessage("过场动画结束，原因：" + end.getReason());
});

if (playback != null) {
    playback.pause();
    playback.resume();
    // 需要提前结束时可调用 playback.stop();
}
```

* `api.effects().ids()`：返回当前已加载的所有 Effect 项目 ID 集合。
* `api.effects().current(player)`：返回玩家当前正在播放的 `ArcMenuEffectStatus` 快照，包含播放令牌 `token`、`effectId`、当前帧 `frame`、总帧数 `durationFrames`、帧率 `fps` 以及暂停状态 `isPaused`。
* 每个 `ArcMenuEffectHandle` 仅控制其自身创建的那一次播放实例，不会误停后续替换的新动画。播放结束回调始终在玩家所属线程触发一次，原因枚举为 `COMPLETED`、`REQUESTED`、`REPLACED`、`PLAYER_LEFT`、`OWNER_DISABLED`、`PLUGIN_DISABLE` 或 `ERROR`。

## 可用性检测、Vision 握手与玩家偏好

### 功能可用性与状态查询

* `api.availability().serverFeatures()`：返回服务端支持的 `ArcMenuFeature` 集合（如 `TOUCH`、`MOUSE`、`VISION`、`RESOURCE_PACKS`、`TOOLTIPS`、`ANIMATIONS`、`CRAFT_ENGINE`、`EDITOR`、`EFFECTS`、`NATIVE_TEXT_INPUT`）。
* `api.availability().player(player)`：在玩家线程上查询该玩家的实时就绪状态 `ArcMenuPlayerAvailability`，包括：
  * `getRequestedPointerMode()` 与 `getEffectivePointerMode()`：玩家请求的指针模式与当前实际生效的指针模式。
  * `isVisionHandshakeReady()`：当前连接是否已完成兼容的 ArcMenu Editor Vision 握手。
  * `getResourcePackState()`：资源包状态枚举（`UNSUPPORTED`、`UNKNOWN`、`NOT_READY`、`READY`），以及 `isResourcePackAssumed()`（是否开启了默认视为已加载配置）。
* `api.cursor().hasCursorMod(player)`：直接判断玩家当前连接是否具备 Vision 握手支持。当玩家进服并完成 Vision 握手时，会触发一次 `ArcMenuCursorReadyEvent`。

### 玩家指针偏好

`api.preferences()` 用于读写玩家个人的鼠标灵敏度与光标缩放档位：

* `get(player)`：返回 `ArcMenuPointerPreferences`，包含 `sensitivityLevel`、`cursorScaleLevel`（均为 `1..9` 的整数，默认居中档位为 `5`）以及对应的倍率 `sensitivityMultiplier`、`cursorScaleMultiplier`（按 `2^((level - 5) / 4)` 计算，档位 `1` 为 `0.5x`，`5` 为 `1.0x`，`9` 为 `2.0x`）。
* `setSensitivity(player, level)` / `setCursorScale(player, level)`：设置 `1..9` 的整数档位，立即对当前打开的鼠标菜单生效。若输入超出 `1..9` 会返回 `false`。
* `clearSensitivity(player)` / `clearCursorScale(player)` / `clear(player)`：清除保存的个人偏好并恢复默认档位 `5`。
* 当通过命令或 API 改变偏好时，会触发 `ArcMenuPreferencesChangedEvent`。

## 图片资源元数据、滚轮步进与目录快照

* **图片资源元数据**：`api.resources().image("/ui/logo.png")` 返回已解析图片的 `ArcMenuImageAsset`（若图片不存在则返回 `null`）。你可以读取图片的真实像素宽高（`getPixelWidth()`、`getPixelHeight()`）、分配的字体（`getFont()`）、Unicode 码点（`getCodepoint()`）、字形宽高与步进宽度（`getGlyphWidth()`、`getGlyphHeight()`、`getGlyphAdvance()`、`getFullGlyphAdvance()`）。拼接文本组件时请直接使用 `asset.getCharacter()` 获取完整字符串（自动处理代理对字符）。
* **快捷栏滚轮步进计算**：`api.resources().hotbarScrollSteps(previousSlot, newSlot)`（或静态方法 `ArcMenuHotbarScroll.steps(prev, next)`）将相邻快捷栏槽位的变化（含 `8 -> 0` 与 `0 -> 8` 的首尾跨越）转换为 `+1` 或 `-1`，非相邻或无效槽位返回 `0`。
* **菜单目录快照与校验**：
  * `api.menus().catalog()` 返回当前的 `ArcMenuCatalogSnapshot`，包含修订号 `revision` 和所有已加载菜单的描述符 `ArcMenuMenuDescriptor`（内含菜单 ID、权限、所有前端节点 ID、后端区域 ID、绑定的动画轨道 ID 及 Arcitx 输入框 ID）。
  * `api.menus().validate()` 在不修改运行时状态的前提下校验磁盘上的菜单配置，返回 `ArcMenuReloadReport`（可通过 `getErrors().isEmpty()` 判断是否全部合法）。
  * `api.menus().reload()` 校验并在全部合法时原子发布新目录，发布成功后在全局线程触发 `ArcMenuCatalogChangedEvent`。
  * 当外部资源包插件（CraftEngine、Nexo 或 ItemsAdder）完成重载合并后，会在全局线程触发 `ArcMenuResourcesReadyEvent`。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.myarc.icu/gong-neng-pei-zhi/application-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
