@fenglimg/cocos-state-controller 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/assets/script/controller/Capability.ts +100 -0
- package/assets/script/controller/Capability.ts.meta +10 -0
- package/assets/script/controller/CapabilityRegistry.ts +116 -0
- package/assets/script/controller/CapabilityRegistry.ts.meta +10 -0
- package/assets/script/controller/EnumPropRefMap.ts +232 -0
- package/assets/script/controller/EnumPropRefMap.ts.meta +10 -0
- package/assets/script/controller/NestedCtrlData.ts +199 -0
- package/assets/script/controller/NestedCtrlData.ts.meta +10 -0
- package/assets/script/controller/PrefabIntrospection.ts +151 -0
- package/assets/script/controller/PrefabIntrospection.ts.meta +10 -0
- package/assets/script/controller/Props.meta +13 -0
- package/assets/script/controller/StateControllerV2.ts +1957 -0
- package/assets/script/controller/StateControllerV2.ts.meta +10 -0
- package/assets/script/controller/StateEnumV2.ts +165 -0
- package/assets/script/controller/StateEnumV2.ts.meta +10 -0
- package/assets/script/controller/StateErrorManagerV2.ts +217 -0
- package/assets/script/controller/StateErrorManagerV2.ts.meta +10 -0
- package/assets/script/controller/StatePropHandlerV2.ts +316 -0
- package/assets/script/controller/StatePropHandlerV2.ts.meta +10 -0
- package/assets/script/controller/StatePropertyControlService.ts +148 -0
- package/assets/script/controller/StatePropertyControlService.ts.meta +10 -0
- package/assets/script/controller/StateSelectV2.ts +4542 -0
- package/assets/script/controller/StateSelectV2.ts.meta +10 -0
- package/assets/script/controller/capabilities/AutoSyncCapability.ts +30 -0
- package/assets/script/controller/capabilities/AutoSyncCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/EventCapability.ts +144 -0
- package/assets/script/controller/capabilities/EventCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/MigrationCapability.ts +94 -0
- package/assets/script/controller/capabilities/MigrationCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts +157 -0
- package/assets/script/controller/capabilities/MultiCtrlBindingCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/PropertyControlCapability.ts +124 -0
- package/assets/script/controller/capabilities/PropertyControlCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/RecordingCapability.ts +69 -0
- package/assets/script/controller/capabilities/RecordingCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities/SelectedPageIdCapability.ts +88 -0
- package/assets/script/controller/capabilities/SelectedPageIdCapability.ts.meta +10 -0
- package/assets/script/controller/capabilities.meta +13 -0
- package/assets/script/controller/props/CtrlInspectorGroups.ts +138 -0
- package/assets/script/controller/props/CtrlInspectorGroups.ts.meta +10 -0
- package/assets/script/controller/props/SelectInspectorGroups.ts +104 -0
- package/assets/script/controller/props/SelectInspectorGroups.ts.meta +10 -0
- package/bin/csc.js +286 -0
- package/package.json +60 -0
- package/packages/state-controller-v2-panel/README.md +80 -0
- package/packages/state-controller-v2-panel/inspector-inject.js +917 -0
- package/packages/state-controller-v2-panel/inspector-probe.json +3767 -0
- package/packages/state-controller-v2-panel/lib/handlers.js +534 -0
- package/packages/state-controller-v2-panel/main.js +149 -0
- package/packages/state-controller-v2-panel/package.json +32 -0
- package/packages/state-controller-v2-panel/panel/build.js +23 -0
- package/packages/state-controller-v2-panel/panel/logic.js +1207 -0
- package/packages/state-controller-v2-panel/panel/styles.css +454 -0
- package/packages/state-controller-v2-panel/panel/template.html +296 -0
- package/packages/state-controller-v2-panel/scene-accessor.js +657 -0
- package/skills/cocos-state-controller/SKILL.md +28 -0
- package/skills/cocos-state-controller/refs/cli-usage.md +78 -0
- package/skills/cocos-state-controller/refs/editor-guide.md +127 -0
- package/skills/cocos-state-controller/refs/migrate.md +106 -0
- package/skills/cocos-state-controller/refs/upstream-pr.md +66 -0
- package/tools/migration/migrate-prefab-v1-to-v2.js +608 -0
- package/tools/state-controller-sync-manifest.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fenglimg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# 🎮 Cocos Creator UI状态控制器系统
|
|
2
|
+
|
|
3
|
+
问题记录:
|
|
4
|
+
1. 主控制器设置状态数量有问题,无法创建新状态。
|
|
5
|
+
|
|
6
|
+
> 💡 灵感来源
|
|
7
|
+
> 为了实现类似FairyGUI中控制器的效果,参考了【开源分享】一个基于Fgui状态控制器(controller)的creator3.0x版本,并将3.x版本改进为2.x版本,同时加入了诸多新特性。
|
|
8
|
+
|
|
9
|
+
## 📋 文档目录
|
|
10
|
+
|
|
11
|
+
- [🎯 项目概述](#-项目概述)
|
|
12
|
+
- [✨ 核心特性](#-核心特性)
|
|
13
|
+
- [🚀 快速开始](#-快速开始)
|
|
14
|
+
- [📖 详细使用指南](#-详细使用指南)
|
|
15
|
+
- [🔧 版本更新说明](#-版本更新说明)
|
|
16
|
+
- [📚 API文档](#-api文档)
|
|
17
|
+
- [🔬 高级功能](#-高级功能)
|
|
18
|
+
- [🛠️ 故障排除](#️-故障排除)
|
|
19
|
+
- [🏗️ 系统架构](#️-系统架构)
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🎯 项目概述
|
|
24
|
+
|
|
25
|
+
### 🎨 适用场景
|
|
26
|
+
| 场景类型 | 应用示例 | 效果展示 |
|
|
27
|
+
|---------|---------|---------|
|
|
28
|
+
| 按钮状态 | 普通/悬浮/按下/禁用 | 颜色、图片、缩放变化 |
|
|
29
|
+
| 面板动画 | 展开/收起/淡入/淡出 | 位置、大小、透明度变化 |
|
|
30
|
+
| 角色状态 | 健康/受伤/死亡/技能 | 血条、图标、文本变化 |
|
|
31
|
+
| 界面布局 | 横屏/竖屏/全屏/窗口 | 位置、锚点、大小变化 |
|
|
32
|
+
## 🚀 快速开始
|
|
33
|
+
|
|
34
|
+
### 📁 第一步:导入文件
|
|
35
|
+
|
|
36
|
+
将文件夹复制到项目中:
|
|
37
|
+
|
|
38
|
+
assets/script/controller/
|
|
39
|
+
├── 📄 StateController.ts # 状态控制器核心
|
|
40
|
+
├── 📄 StateSelect.ts # 状态选择器
|
|
41
|
+
├── 📄 StateEnum.ts # 枚举定义
|
|
42
|
+
├── 📄 StateErrorManager.ts # 错误处理系统
|
|
43
|
+
└── 📄 StatePropHandler.ts # 属性处理器系统
|
|
44
|
+
|
|
45
|
+
### 🎮 第二步:添加控制器
|
|
46
|
+
|
|
47
|
+
> 📌 重要提示
|
|
48
|
+
> StateController 应该添加到**父节点**上,它会自动管理所有子节点的状态。
|
|
49
|
+
|
|
50
|
+
1. 在需要状态控制的父节点上添加 `StateController` 组件
|
|
51
|
+
2. 在属性面板中设置状态数量和名称
|
|
52
|
+
3. 系统会自动创建默认状态:"0" 和 "1"
|
|
53
|
+
|
|
54
|
+
### 🎨 第三步:配置状态选择器
|
|
55
|
+
|
|
56
|
+
1. 在需要状态变化的**子节点**上添加 `StateSelect` 组件
|
|
57
|
+
2. 选择要控制的属性类型(Position、Color、Scale等)
|
|
58
|
+
3. 在不同状态下设置不同的属性值
|
|
59
|
+
4. 选择合适的同步模式
|
|
60
|
+
|
|
61
|
+
### ✅ 第四步:测试效果
|
|
62
|
+
|
|
63
|
+
在编辑器中切换 `StateController` 的 `selectedIndex`,观察子节点属性的变化。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 📖 详细使用指南
|
|
68
|
+
|
|
69
|
+
### 🎮 StateController(状态控制器)
|
|
70
|
+
|
|
71
|
+
> 🔧 核心组件
|
|
72
|
+
> 负责管理整个状态组,是系统的控制中心。
|
|
73
|
+
|
|
74
|
+
#### 基本属性
|
|
75
|
+
| 属性名 | 类型 | 说明 | 示例值 |
|
|
76
|
+
|--------|------|------|--------|
|
|
77
|
+
| `ctrlName` | string | 控制器名称(必须唯一) | "MainMenuCtrl" |
|
|
78
|
+
| `selectedIndex` | number | 当前选中的状态索引 | 0, 1, 2... |
|
|
79
|
+
| `states` | StateValue[] | 状态列表,可动态添加删除 | ["normal", "hover", "pressed"] |
|
|
80
|
+
| `previousIndex` | number | 上一个状态索引(只读) | 0 |
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
#### 代码控制示例
|
|
84
|
+
// 🎯 获取控制器组件
|
|
85
|
+
let controller = node.getComponent(StateController);
|
|
86
|
+
|
|
87
|
+
// 🔄 切换到指定状态
|
|
88
|
+
controller.selectedIndex = 1;
|
|
89
|
+
|
|
90
|
+
// 📝 通过状态名称切换
|
|
91
|
+
controller.selectedPage = "hover";
|
|
92
|
+
|
|
93
|
+
// 📊 获取当前状态信息
|
|
94
|
+
let currentState = controller.selectedPage;
|
|
95
|
+
let previousState = controller.previousIndex;
|
|
96
|
+
|
|
97
|
+
### 🎨 StateSelect(状态选择器)
|
|
98
|
+
|
|
99
|
+
> 🎯 执行组件
|
|
100
|
+
> 负责具体的状态变化,必须配合 StateController 使用。
|
|
101
|
+
|
|
102
|
+
#### 支持的属性类型
|
|
103
|
+
|
|
104
|
+
| 分类 | 属性类型 | 说明 | 值类型 |
|
|
105
|
+
|------|----------|------|--------|
|
|
106
|
+
| 节点基础 | `Active` | 显示/隐藏 | `boolean` |
|
|
107
|
+
| | `Position` | 位置坐标 | `cc.Vec3` |
|
|
108
|
+
| | `Euler` | 旋转角度 | `cc.Vec3` |
|
|
109
|
+
| | `Scale` | 缩放比例 | `number` |
|
|
110
|
+
| | `Anchor` | 锚点位置 | `cc.Vec2` |
|
|
111
|
+
| | `Size` | 尺寸大小 | `cc.Size` |
|
|
112
|
+
| | `Color` | 节点颜色 | `cc.Color` |
|
|
113
|
+
| | `Opacity` | 透明度 | `number` |
|
|
114
|
+
| 文本组件 | `Label_String` | 文本内容 | `string` |
|
|
115
|
+
| | `LabelOutline_Color` | 文本描边颜色 | `cc.Color` |
|
|
116
|
+
| | `Font` | 字体资源 | `cc.Font` |
|
|
117
|
+
| 图片组件 | `SpriteFrame` | 图片资源 | `cc.SpriteFrame` |
|
|
118
|
+
| 交互组件 | `Slider_Progress` | 滑动条进度 | `number` |
|
|
119
|
+
| | `Editbox_String` | 输入框文本 | `string` |
|
|
120
|
+
| 特效 | `GrayScale` | 灰度效果 | `boolean` |
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
#### 🔄 属性同步模式
|
|
124
|
+
|
|
125
|
+
enum SyncMode {
|
|
126
|
+
Independent = 0, // 🔸 独立模式:每个状态属性完全独立
|
|
127
|
+
AutoSync = 1, // 🔹 自动同步:添加/删除属性时自动同步到所有状态(推荐)
|
|
128
|
+
ManualSync = 2 // 🔺 手动同步:需要手动点击同步按钮
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
> 💡 推荐设置
|
|
132
|
+
> 对于大多数情况,建议使用 AutoSync 模式。
|
|
133
|
+
|
|
134
|
+
## 📚 API文档
|
|
135
|
+
|
|
136
|
+
### 🎮 StateController API
|
|
137
|
+
|
|
138
|
+
#### 核心属性
|
|
139
|
+
|
|
140
|
+
class StateController {
|
|
141
|
+
// 🏷️ 控制器名称
|
|
142
|
+
get ctrlName(): string;
|
|
143
|
+
set ctrlName(value: string);
|
|
144
|
+
|
|
145
|
+
// 🎯 当前选中状态索引
|
|
146
|
+
get selectedIndex(): number;
|
|
147
|
+
set selectedIndex(value: number);
|
|
148
|
+
|
|
149
|
+
// 📋 状态数组
|
|
150
|
+
get states(): StateValue[];
|
|
151
|
+
|
|
152
|
+
// 📝 通过名称访问状态
|
|
153
|
+
get selectedPage(): string;
|
|
154
|
+
set selectedPage(name: string);
|
|
155
|
+
|
|
156
|
+
// 📊 上一个状态索引(只读)
|
|
157
|
+
get previousIndex(): number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#### 生命周期方法
|
|
161
|
+
|
|
162
|
+
// 🔄 状态切换时自动调用,通知所有相关的StateSelect组件
|
|
163
|
+
private updateState(type: EnumUpdateType, value?: number): void;
|
|
164
|
+
|
|
165
|
+
### 🎨 StateSelect API
|
|
166
|
+
|
|
167
|
+
#### 核心属性
|
|
168
|
+
|
|
169
|
+
class StateSelect {
|
|
170
|
+
// 🎮 当前选中的控制器ID
|
|
171
|
+
get currCtrlId(): number;
|
|
172
|
+
|
|
173
|
+
// 🎯 当前控制的属性类型
|
|
174
|
+
get propKey(): EnumPropName;
|
|
175
|
+
set propKey(value: EnumPropName);
|
|
176
|
+
|
|
177
|
+
// 💎 当前属性值
|
|
178
|
+
get propValue(): TPropValue;
|
|
179
|
+
|
|
180
|
+
// 🔄 属性同步模式
|
|
181
|
+
syncMode: SyncMode;
|
|
182
|
+
|
|
183
|
+
// 📊 已修改的属性列表(只读)
|
|
184
|
+
changedProp: string[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#### 操作方法
|
|
188
|
+
|
|
189
|
+
// 🔄 手动同步当前属性到所有状态
|
|
190
|
+
set syncCurrentProp(value: boolean);
|
|
191
|
+
|
|
192
|
+
// 🗑️ 删除当前属性
|
|
193
|
+
set isDeleteCurr(value: boolean);
|
|
194
|
+
|
|
195
|
+
### 🛡️ 错误处理 API
|
|
196
|
+
|
|
197
|
+
class StateErrorManager {
|
|
198
|
+
// 📝 统一日志输出
|
|
199
|
+
static log(level: ErrorLevel, message: string, context?: IErrorContext): void;
|
|
200
|
+
|
|
201
|
+
// 🛡️ 优雅降级处理
|
|
202
|
+
static gracefulFallback<T>(operation: () => T, fallbackValue: T, errorMessage?: string): T;
|
|
203
|
+
|
|
204
|
+
// ✅ 节点有效性验证
|
|
205
|
+
static validateNode(node: cc.Node, context?: IErrorContext): boolean;
|
|
206
|
+
|
|
207
|
+
// 💬 用户友好的错误提示
|
|
208
|
+
static userFriendlyError(userMessage: string, technicalDetails?: string, context?: IErrorContext): void;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 🔬 高级功能
|
|
214
|
+
|
|
215
|
+
### 🔧 自定义属性处理器
|
|
216
|
+
|
|
217
|
+
如果需要支持新的属性类型,可以按以下步骤扩展:
|
|
218
|
+
|
|
219
|
+
#### 1️⃣ 添加枚举值
|
|
220
|
+
在 `StateEnum.ts` 中添加新的属性类型:
|
|
221
|
+
export enum EnumPropName {
|
|
222
|
+
// ... 现有属性
|
|
223
|
+
Button_Interactable = 16, // 🆕 新增:按钮可交互性
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
#### 2️⃣ 实现属性处理器
|
|
227
|
+
在 `StatePropHandler.ts` 中创建处理器类:
|
|
228
|
+
class ButtonInteractablePropHandler implements IPropHandler {
|
|
229
|
+
getValue(node: cc.Node) {
|
|
230
|
+
const button = node.getComponent(cc.Button);
|
|
231
|
+
return button ? button.interactable : undefined;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
setValue(node: cc.Node, value: TPropValue) {
|
|
235
|
+
const button = node.getComponent(cc.Button);
|
|
236
|
+
if (button) button.interactable = value as boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
getDefaultValue(node: cc.Node) {
|
|
240
|
+
return this.getValue(node);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#### 3️⃣ 注册处理器
|
|
245
|
+
|
|
246
|
+
PropHandlerManager.register(EnumPropName.Button_Interactable, new ButtonInteractablePropHandler());
|
|
247
|
+
|
|
248
|
+
### 🎯 实际应用示例
|
|
249
|
+
|
|
250
|
+
#### 📱 示例1:按钮状态控制
|
|
251
|
+
|
|
252
|
+
创建一个带有 normal、hover、pressed 三种状态的按钮:
|
|
253
|
+
ButtonNode (StateController)
|
|
254
|
+
├── 🖼️ Background (StateSelect - SpriteFrame + Color)
|
|
255
|
+
├── 🏷️ Label (StateSelect - Label_String + Color)
|
|
256
|
+
└── 🎨 Icon (StateSelect - Scale + Opacity)
|
|
257
|
+
|
|
258
|
+
🔧 配置步骤:
|
|
259
|
+
|
|
260
|
+
1. 在 `ButtonNode` 上添加 `StateController`,设置3个状态
|
|
261
|
+
2. 在 `Background` 上添加 `StateSelect`:
|
|
262
|
+
- 属性类型选择 `SpriteFrame`,设置不同状态的背景图
|
|
263
|
+
- 再添加一个选择 `Color`,设置不同的背景色调
|
|
264
|
+
3. 在 `Label` 上配置文本和颜色变化
|
|
265
|
+
4. 在 `Icon` 上配置缩放和透明度变化
|
|
266
|
+
|
|
267
|
+
#### 📋 示例2:面板展开/收起
|
|
268
|
+
|
|
269
|
+
创建一个可以展开收起的设置面板:
|
|
270
|
+
|
|
271
|
+
SettingsPanel (StateController)
|
|
272
|
+
├── 🎨 Background (StateSelect - Size + Opacity)
|
|
273
|
+
├── 🏷️ Title (StateSelect - Position)
|
|
274
|
+
├── 📝 Content (StateSelect - Active + Position)
|
|
275
|
+
└── ❌ CloseButton (StateSelect - Scale + Opacity)
|
|
276
|
+
|
|
277
|
+
#### 🎮 示例3:角色状态显示
|
|
278
|
+
|
|
279
|
+
显示角色的不同状态(健康、受伤、死亡):
|
|
280
|
+
|
|
281
|
+
CharacterUI (StateController)
|
|
282
|
+
├── ❤️ HealthBar (StateSelect - Scale + Color)
|
|
283
|
+
├── 🛡️ StatusIcon (StateSelect - SpriteFrame)
|
|
284
|
+
├── 📛 NameLabel (StateSelect - Color + LabelOutline_Color)
|
|
285
|
+
└── 🔢 LevelText (StateSelect - Label_String)
|
|
286
|
+
|
|
287
|
+
暂时无法在飞书文档外展示此内容
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability 框架接口 (Wave 2 Step 2).
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:
|
|
5
|
+
* - Core 永远薄 (StateControllerV2 + StateSelectV2 + 数据存取 + 切换协议),
|
|
6
|
+
* 其它能力 (Recording / PropertyControl / AutoSync / Migration / Tween …) 都是 capability.
|
|
7
|
+
* - Capability 之间通信靠 event dispatch (CapabilityRegistry.dispatch), 不直接调用.
|
|
8
|
+
* - 数据隔离: 每个 capability 通过 ctx.namespace(propData) 读写 `$$<capName>$$` 子空间.
|
|
9
|
+
* - 删掉所有 capability, core 仍能切 state (底线; T29/T30 验证).
|
|
10
|
+
*
|
|
11
|
+
* 三层插件结构:
|
|
12
|
+
* - L0 内置 (本仓库 capabilities/): PropertyControl, AutoSync, Recording, Migration (占位)
|
|
13
|
+
* - L1 官方扩展 (独立 cocos plugin): Tween, StatePreviewPanel, DiffViewer, CodeGen
|
|
14
|
+
* - L2 第三方 (用户自写)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EnumPropName } from "./StateEnumV2";
|
|
18
|
+
import { TPropValue } from "./StatePropHandlerV2";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 通用 dispatch context. 各 capability 自己负责从 ctx 取所需字段.
|
|
22
|
+
* 字段都可选, 由派发点 (StateControllerV2 / StateSelectV2) 决定填充哪些.
|
|
23
|
+
*/
|
|
24
|
+
export interface CapabilityContext {
|
|
25
|
+
/** 派发来源控制器 */
|
|
26
|
+
ctrl?: any
|
|
27
|
+
/** 涉及的 StateSelectV2 */
|
|
28
|
+
select?: any
|
|
29
|
+
/** 状态切换上下文 (StateWillChange / StateChanged) */
|
|
30
|
+
fromState?: number
|
|
31
|
+
toState?: number
|
|
32
|
+
/** prop apply 上下文 (onPropApply) */
|
|
33
|
+
propType?: EnumPropName
|
|
34
|
+
propValue?: TPropValue
|
|
35
|
+
/**
|
|
36
|
+
* W6-2b: prop 引用字符串 ("compName.propKey"), 与 propType 字段并存.
|
|
37
|
+
* - 内置 prop (EnumPropName 数字调用): 从 EnumPropRefMap 派生 propRef. AMBIGUOUS 4 项 (Position/Anchor/Size/GrayScale) 无映射 → propRef=undefined
|
|
38
|
+
* - 自定义 prop (string 调用): propRef = 调用方传入的字符串, propType = undefined 或 EnumPropName.Non
|
|
39
|
+
* - capability 实现可优先用 propRef, 再 fallback propType + EnumPropRefMap 派生
|
|
40
|
+
*/
|
|
41
|
+
propRef?: string
|
|
42
|
+
/** 自定义额外数据, capability 之间共享 (避免直接耦合) */
|
|
43
|
+
extra?: { [key: string]: unknown }
|
|
44
|
+
/**
|
|
45
|
+
* Capability namespace helper: 给定 propData, 返回该 capability 私有的 `$$<capName>$$` 子对象.
|
|
46
|
+
* 由 CapabilityRegistry.dispatch 注入, 调用方填 propData 即可.
|
|
47
|
+
*/
|
|
48
|
+
namespace?: (propData: any, capName: string) => { [key: string]: unknown }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Capability 接口 (所有 hook 都可选).
|
|
53
|
+
*
|
|
54
|
+
* name 是 capability 命名空间唯一 key, 决定 ctx.namespace(propData, name) 拿到的 `$$<name>$$` 子空间.
|
|
55
|
+
* 同名 register 会覆盖 (后注册赢).
|
|
56
|
+
*/
|
|
57
|
+
export interface ICapability {
|
|
58
|
+
/** 命名空间唯一 key */
|
|
59
|
+
name: string
|
|
60
|
+
|
|
61
|
+
/** 依赖的 capability 名字列表 (可选, T20+ 启用排序时使用; 当前 Wave 2 仅文档保留) */
|
|
62
|
+
dependsOn?: string[]
|
|
63
|
+
|
|
64
|
+
/** 切 state 之前 (录制中 commit diff 等) */
|
|
65
|
+
onStateWillChange?(ctx: CapabilityContext): void
|
|
66
|
+
|
|
67
|
+
/** 切 state 之后 (录制重拍 snapshot / 其它跟随状态变化的同步) */
|
|
68
|
+
onStateChanged?(ctx: CapabilityContext): void
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Runtime 启动初始化钩子 (Wave 3).
|
|
72
|
+
* StateControllerV2.onLoad runtime path 调用. 用于 runtime 启动时
|
|
73
|
+
* 自动跳到指定 state 等场景. 编辑期不触发.
|
|
74
|
+
*/
|
|
75
|
+
onRuntimeInit?(ctx: CapabilityContext): void
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Prop apply 钩子 (StateSelectV2.batchUpdateUI 内调用):
|
|
79
|
+
* 返回 TPropValue 则改写要 apply 的值; 返回 void / undefined 不改写.
|
|
80
|
+
*/
|
|
81
|
+
onPropApply?(ctx: CapabilityContext, prop: { type: EnumPropName, value: TPropValue }): TPropValue | void
|
|
82
|
+
|
|
83
|
+
/** 录制开始 / 结束 (Recording capability 内部用) */
|
|
84
|
+
onRecordingStart?(ctx: CapabilityContext): void
|
|
85
|
+
onRecordingStop?(ctx: CapabilityContext): void
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* W6-2b: prop 接入 / 解除 钩子. togglePropertyControl(on/off) 时派发.
|
|
89
|
+
* - ctx.propType: EnumPropName 数字 (内置 prop) 或 undefined (自定义 prop)
|
|
90
|
+
* - ctx.propRef: string 引用 (内置 propRef 派生自 EnumPropRefMap; 自定义 propRef 直接来自调用方)
|
|
91
|
+
*/
|
|
92
|
+
onPropertyControlled?(ctx: CapabilityContext): void
|
|
93
|
+
onPropertyReleased?(ctx: CapabilityContext): void
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 版本迁移 (用于 preset / migration capability).
|
|
97
|
+
* 当前 Wave 2 不实装迁移逻辑, 仅占位接口供 MigrationCapability 实现.
|
|
98
|
+
*/
|
|
99
|
+
onCtrlDataMigrate?(data: unknown, version: number): unknown
|
|
100
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability 静态注册表 (Wave 2 Step 2).
|
|
3
|
+
*
|
|
4
|
+
* 单实例, 进程全局共享. 每个 ICapability 通过 name 标识, 同名 register 覆盖 (后注册赢).
|
|
5
|
+
*
|
|
6
|
+
* dispatch(event, ctx) 同步遍历所有 capability, 调对应 hook (缺 hook 跳过, 不抛).
|
|
7
|
+
* ctx 自动注入 namespace helper, 各 capability 用 `ctx.namespace(propData, this.name)` 拿到隔离子空间.
|
|
8
|
+
*
|
|
9
|
+
* 非阻塞设计:
|
|
10
|
+
* - 当前 Wave 2 不引入异步 / 优先级排序 (dependsOn 仅声明用, 不影响调度);
|
|
11
|
+
* - hook 抛异常 → 捕获 + 走 StateErrorManager.warn, 不影响其它 capability 执行.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { CapabilityContext, ICapability } from "./Capability";
|
|
15
|
+
import { StateErrorManager } from "./StateErrorManagerV2";
|
|
16
|
+
|
|
17
|
+
type CapEvent
|
|
18
|
+
= | "onStateWillChange"
|
|
19
|
+
| "onStateChanged"
|
|
20
|
+
| "onPropApply"
|
|
21
|
+
| "onRecordingStart"
|
|
22
|
+
| "onRecordingStop"
|
|
23
|
+
| "onRecordingCancel"
|
|
24
|
+
| "onCtrlDataMigrate"
|
|
25
|
+
| "onRuntimeInit"
|
|
26
|
+
// W6-2b: prop 接入 / 解除 dispatch (togglePropertyControl on/off 触发, payload 含 propType + propRef 双字段)
|
|
27
|
+
| "onPropertyControlled"
|
|
28
|
+
| "onPropertyReleased";
|
|
29
|
+
|
|
30
|
+
/** namespace helper 注入. 给 propData 设置 / 读取 `$$<capName>$$` 子对象. */
|
|
31
|
+
function namespaceHelper(propData: any, capName: string): { [key: string]: unknown } {
|
|
32
|
+
if (!propData) return {};
|
|
33
|
+
const key = `$$${capName}$$`;
|
|
34
|
+
if (propData[key] === undefined || propData[key] === null) {
|
|
35
|
+
propData[key] = {};
|
|
36
|
+
}
|
|
37
|
+
return propData[key];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class CapabilityRegistry {
|
|
41
|
+
private static capabilities = new Map<string, ICapability>();
|
|
42
|
+
|
|
43
|
+
/** 注册 capability. 同名覆盖 (后注册赢). */
|
|
44
|
+
public static register(cap: ICapability): void {
|
|
45
|
+
if (!cap || !cap.name) {
|
|
46
|
+
StateErrorManager.warn("CapabilityRegistry.register: cap 缺少 name", {
|
|
47
|
+
component: "CapabilityRegistry",
|
|
48
|
+
method: "register",
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.capabilities.set(cap.name, cap);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 卸载 capability (按 name) */
|
|
56
|
+
public static unregister(name: string): void {
|
|
57
|
+
this.capabilities.delete(name);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 按 name 查询 capability */
|
|
61
|
+
public static get(name: string): ICapability | undefined {
|
|
62
|
+
return this.capabilities.get(name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 列出所有已注册 capability (顺序为注册顺序) */
|
|
66
|
+
public static list(): ICapability[] {
|
|
67
|
+
// ES5 compat: 不用 Array.from(iter), 改 forEach 累积
|
|
68
|
+
const out: ICapability[] = [];
|
|
69
|
+
this.capabilities.forEach(cap => out.push(cap));
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 全部清空 (测试用) */
|
|
74
|
+
public static clear(): void {
|
|
75
|
+
this.capabilities.clear();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 派发事件. 同步执行所有已注册 capability 的对应 hook.
|
|
80
|
+
*
|
|
81
|
+
* 返回值: hook 返回值的数组 (按注册顺序). 用于 onPropApply 这类有返回值的 hook;
|
|
82
|
+
* 普通 hook 返回值为 undefined 也照样收集 (调用方可忽略).
|
|
83
|
+
*/
|
|
84
|
+
public static dispatch(event: CapEvent, ctx: CapabilityContext): unknown[] {
|
|
85
|
+
// 注入 namespace helper, 避免 capability 各自实现一遍
|
|
86
|
+
if (!ctx.namespace) {
|
|
87
|
+
ctx.namespace = namespaceHelper;
|
|
88
|
+
}
|
|
89
|
+
const results: unknown[] = [];
|
|
90
|
+
// 注: 项目 tsconfig target=es5 且未开 downlevelIteration, 不能用 for-of Map.values().
|
|
91
|
+
// 改用 forEach 保证 ES5 兼容.
|
|
92
|
+
this.capabilities.forEach((cap) => {
|
|
93
|
+
const hook = (cap as any)[event] as ((...args: any[]) => any) | undefined;
|
|
94
|
+
if (typeof hook !== "function") return;
|
|
95
|
+
try {
|
|
96
|
+
let r: unknown;
|
|
97
|
+
if (event === "onPropApply") {
|
|
98
|
+
const prop = ctx.extra && (ctx.extra as any).prop;
|
|
99
|
+
r = hook.call(cap, ctx, prop);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
r = hook.call(cap, ctx);
|
|
103
|
+
}
|
|
104
|
+
results.push(r);
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
StateErrorManager.warn(`Capability "${cap.name}" hook "${event}" 抛异常`, {
|
|
108
|
+
component: "CapabilityRegistry",
|
|
109
|
+
method: "dispatch",
|
|
110
|
+
params: { error: (e as Error).message },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
return results;
|
|
115
|
+
}
|
|
116
|
+
}
|