# Events (事件系统)

- **Summary**: Five 提供了一套基于发布/订阅模式的事件系统，用于监听用户交互、状态变更、数据加载及渲染生命周期。
- **Schema**: `on` / `once` / `off` 接口定义；`BaseEvent` / `BaseExtendableEvent` 事件对象。
- **Concepts**: Event 对象（`preventDefault` / `waitUntil`）、Gesture Events, State Events, Pano Events, Model Events, Lifecycle Events.
- **Examples**: 监听全景移动、手势交互及状态变更；使用 `preventDefault` 拦截默认行为；使用 `waitUntil` 延迟点位切换。

## Schema

> **Definition**: [Five](../../five/application/five.d.ts), [EventTypes](../../five/application/events.d.ts)

Five 实例提供了以下方法来管理事件监听：

### on
注册持久化事件监听器。

```typescript
five.on(
  name: string,
  callback: (...args: any[]) => void,
  once?: boolean
): () => void
```

- **name**: 事件名称（支持的事件见 Concepts）。
- **callback**: 事件触发时的回调函数。
- **once**: (可选) 是否仅执行一次，默认为 `false`。
- **返回**: 一个函数，调用该函数可取消当前监听。

### once
注册一次性事件监听器（触发一次后自动销毁）。

```typescript
five.once(
  name: string,
  callback: (...args: any[]) => void
): () => void
```

### off
取消事件监听。

```typescript
five.off(
  name?: string,
  callback?: (...args: any[]) => void
): void
```

- **name**: (可选) 若不传，则清空所有事件；若传，则取消指定类型的事件。
- **callback**: (可选) 若传，则仅取消该特定的回调函数；若不传，则取消该类型下的所有回调。

## Event 对象

> **Definition**: [BaseEvent / BaseExtendableEvent](../../five/utils/event.d.ts)

Five 中所有事件回调的参数都是一个 Event 对象，分为两种基础类型：

### BaseEvent

所有事件的基础接口，包含以下属性和方法：

```typescript
interface BaseEvent {
  /** 事件类型名称 */
  type: string;
  /** 事件触发时的时间戳 */
  timeStamp: number;
  /** 阻止事件的默认行为 */
  preventDefault(): void;
  /** 是否已调用过 preventDefault */
  readonly defaultPrevented: boolean;
}
```

### BaseExtendableEvent

继承自 `BaseEvent`，额外提供 `waitUntil` 方法，用于在事件处理中插入异步等待逻辑：

```typescript
interface BaseExtendableEvent extends BaseEvent {
  waitUntil(f: Promise<any>): void;
}
```

目前 `pano.prepare` 事件使用 `BaseExtendableEvent`（对应 `PanoPrepareEvent`），其余事件均基于 `BaseEvent`。

`PanoPrepareEvent` 额外包含以下属性：

| 属性 | 类型 | 描述 |
| :--- | :--- | :--- |
| `options` | `MovePanoOptions` | 点位切换时的选项（如 `effect`、`duration`、`longitude`、`latitude`、`fov` 等）。**支持在回调中修改此对象**，修改后的值将影响实际的切换行为。 |
| `prevMode` | `Mode` | 切换发生前的显示模式。 |
| `prevPano` | `Pano` | 切换前的点位信息。 |
| `pano` | `Pano` | 目标点位信息。 |

### preventDefault

调用 `event.preventDefault()` 可以阻止事件的默认行为。Five 内部会在触发事件后检查 `event.defaultPrevented`，若为 `true` 则跳过后续的默认处理逻辑。

比如：
- `gesture.tap`：阻止默认的点位跳转或相机弹跳动画。
- `intersect.update`：阻止默认的交互高亮行为。

```typescript
// 示例：阻止点击时的默认点位跳转
five.on("gesture.tap", (event) => {
  if (shouldBlockNavigation()) {
    event.preventDefault();
    // 执行自定义逻辑
    showCustomMenu(event);
  }
});

// 示例：阻止拖拽时的默认相机旋转
five.on("gesture.pan", (event) => {
  event.preventDefault();
  // 用拖拽手势实现自定义交互（如绘制标注）
  drawAnnotation(event);
});
```

### waitUntil

`waitUntil(promise)` 仅在 `ExtendableEvent` 上可用。调用后，Five 会等待传入的 Promise 完成后再继续后续流程。可多次调用，所有 Promise 会被并行等待。

目前仅 `pano.prepare` 事件支持 `waitUntil`。

```typescript
// 示例：在切换点位前预加载资源
five.on("pano.prepare", (event) => {
  // event.pano 目标点位
  // event.prevPano 切换前点位
  // event.prevMode 切换前模式
  // 等待自定义资源加载完成后再继续点位切换
  event.waitUntil(
    loadCustomResources(event.pano)
  );

  // 可以多次调用，所有 Promise 会被并行等待
  event.waitUntil(
    preloadTextures(event.pano)
  );
});

// 示例：通过 waitUntil reject 来取消走点
five.on("pano.prepare", (event) => {
  event.waitUntil(
    showConfirmDialog("是否前往该点位？").then((confirmed) => {
      if (!confirmed) {
        throw new Error("用户取消走点");
      }
    })
  );
});

// 示例：修改 event.options 影响切换行为
five.on("pano.prepare", (event) => {
  // 修改切换动画效果为无动画
  event.options.effect = "instant";
  // 或修改切换后的相机朝向
  event.options.longitude = Math.PI / 4;
  event.options.latitude = 0;
  event.options.fov = 90;
  // 注意：直接修改 event.options 属性即可，不需要重新赋值 event.options
});
```

> **注意**: `preventDefault` 和 `waitUntil` 必须在事件回调的同步执行阶段调用，不能放在 `await` 之后。

## Concepts

Five 的事件系统将事件主要分为以下几类：

### Gesture Events (交互手势)
监听用户的鼠标或触摸操作。

- `gesture.tap`: 点击/轻触。
- `gesture.dbltap`: 双击/双击轻触。
- `gesture.press`: 长按。
- `gesture.pan`: 平移/拖拽。
- `gesture.pinch`: 缩放（双指或滚轮）。
- `gesture.mousewheel`: 鼠标滚轮滚动。
- `gesture.mousemove`: 鼠标移动。

### Pano Events (全景导航)
监听全景图的点位切换过程。

- `pano.prepare`: 准备切换到新点位。这是一个 `ExtendableEvent`，支持通过 `waitUntil(promise)` 延迟后续加载流程（如预加载资源），以及通过 `preventDefault()` 取消本次走点。事件对象包含 `options`（可修改，影响切换行为）、`prevMode`（切换前模式）、`prevPano`（切换前点位）、`pano`（目标点位）等属性。
- `pano.arrived`: 成功到达新点位（移动动画结束）。
- `pano.moving`: 正在移动到新点位（动画进行中）。
- `pano.willChange`: 即将发生点位变更（旧版兼容）。
- `pano.error`: 点位加载或移动出错。

### State Events (状态变更)
监听 Five 内部状态（State）的变化，常用于 UI 同步或数据记录。

- `state.change`: 状态发生变更（可能是用户操作或代码触发）。
- `currentState.change`: 当前状态发生变更（通常指即时状态）。
- `state.synced`: 状态已同步。

### Model & Work Events (数据加载)
监听场景数据（Work）或模型（Model）的加载生命周期。

- `works.load`: Work 数据开始加载。
- `works.ready`: Work 数据加载完成且 Controller 就绪。
- `model.load`: 模型开始加载。
- `models.load`: 所有模型加载完成。

### Render Events (渲染循环)
监听每一帧的渲染过程。

- `render`: 每一帧渲染后触发。
- `render.prepare`: 每一帧渲染前触发。

## Examples

### 快速上手 (Quick Example)
监听点击事件并打印日志。

```typescript
import { Five } from "@realsee/five";

const five = new Five();

// 监听点击事件
five.on("gesture.tap", (event) => {
  console.log("User tapped at:", event);
});

// 监听点位变更完成
five.on("pano.arrived", (event) => {
  console.log("Arrived at pano:", event.pano);
});
```

### 进阶用法 (Advanced Usage)
使用 `once` 监听一次性初始化事件，使用返回值取消监听。

```typescript
// 1. 等待首次数据加载完成
five.once("works.ready", () => {
  console.log("Five is ready!");
  // 执行初始化逻辑，如自动巡游
});

// 2. 动态绑定与解绑
const onRender = () => {
  // 每一帧执行的逻辑
  updateUI();
};

// 开始监听
five.on("render", onRender);

// 在某个时刻取消监听（例如组件销毁时）
// five.off("render", onRender);
```

## Debugging

- 使用 `five.on("error", (err) => console.error(err))` 捕获全局错误。

## Related

- [State](./state.md): 了解 State 的结构与变更机制。
- [Five](./five.md): Five 核心类说明。

---

```yaml
tags: [事件监听, 点击, 拖拽, 加载完成, 渲染回调, 生命周期, 回调, events, interaction, lifecycle, gesture, state, preventDefault, waitUntil, on, off, subscribe]
```
