@eva/plugin-ui 2.1.0-beta.1 → 2.1.0-beta.3
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/README.md +4 -4
- package/package.json +4 -4
- package/dist/EVA.plugin.ui.js +0 -7948
- package/dist/EVA.plugin.ui.min.js +0 -1
- package/dist/plugin-ui.cjs.js +0 -1926
- package/dist/plugin-ui.cjs.prod.js +0 -1
- package/dist/plugin-ui.d.ts +0 -623
- package/dist/plugin-ui.esm.js +0 -1902
package/dist/plugin-ui.cjs.js
DELETED
|
@@ -1,1926 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var eva_js = require('@eva/eva.js');
|
|
6
|
-
var pluginRendererGraphics = require('@eva/plugin-renderer-graphics');
|
|
7
|
-
var pixi_js = require('pixi.js');
|
|
8
|
-
var ui = require('@pixi/ui');
|
|
9
|
-
|
|
10
|
-
/** UI 形状类型枚举 */
|
|
11
|
-
exports.UIShapeType = void 0;
|
|
12
|
-
(function (UIShapeType) {
|
|
13
|
-
UIShapeType["RECT"] = "rect";
|
|
14
|
-
UIShapeType["CIRCLE"] = "circle";
|
|
15
|
-
UIShapeType["ELLIPSE"] = "ellipse";
|
|
16
|
-
UIShapeType["ROUNDED_RECT"] = "roundedRect";
|
|
17
|
-
})(exports.UIShapeType || (exports.UIShapeType = {}));
|
|
18
|
-
/**
|
|
19
|
-
* UI 组件
|
|
20
|
-
*
|
|
21
|
-
* 基于 `@eva/plugin-renderer-graphics` 实现 rect/circle/ellipse/roundedRect 的
|
|
22
|
-
* 矢量绘制,支持纯色 / linear-gradient 填充与描边。`shapes` 数组允许在同一个
|
|
23
|
-
* GameObject 上层叠多个形状。
|
|
24
|
-
*/
|
|
25
|
-
class UI extends eva_js.Component {
|
|
26
|
-
constructor() {
|
|
27
|
-
super(...arguments);
|
|
28
|
-
/** 实例侧 componentName,DSL 序列化时使用 */
|
|
29
|
-
this.componentName = 'UI';
|
|
30
|
-
this.shapes = [];
|
|
31
|
-
}
|
|
32
|
-
/** 编辑器 Inspector 元数据,主仓 ComponentInspector 通过静态方法读取 */
|
|
33
|
-
static getInspectorMetadata() {
|
|
34
|
-
const styleFields = [
|
|
35
|
-
{ name: 'fill', type: 'color', isArray: false },
|
|
36
|
-
{ name: 'stroke', type: 'color', isArray: false },
|
|
37
|
-
{ name: 'lineWidth', type: 'number', isArray: false },
|
|
38
|
-
{ name: 'alpha', type: 'number', isArray: false },
|
|
39
|
-
{ name: 'x', type: 'number', isArray: false },
|
|
40
|
-
{ name: 'y', type: 'number', isArray: false },
|
|
41
|
-
{ name: 'width', type: 'number', isArray: false },
|
|
42
|
-
{ name: 'height', type: 'number', isArray: false },
|
|
43
|
-
{ name: 'radius', type: 'number', isArray: false },
|
|
44
|
-
];
|
|
45
|
-
return {
|
|
46
|
-
name: UI.componentName,
|
|
47
|
-
type: 'object',
|
|
48
|
-
isArray: false,
|
|
49
|
-
isFolder: true,
|
|
50
|
-
children: [
|
|
51
|
-
{ name: 'componentName', type: 'string', isArray: false },
|
|
52
|
-
{
|
|
53
|
-
name: 'shapes',
|
|
54
|
-
type: 'object',
|
|
55
|
-
isArray: true,
|
|
56
|
-
children: [
|
|
57
|
-
{ name: 'type', type: 'string', isArray: false },
|
|
58
|
-
{ name: 'style', type: 'object', isArray: false, children: styleFields },
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
{ name: 'type', type: 'string', isArray: false },
|
|
62
|
-
{ name: 'style', type: 'object', isArray: false, children: styleFields },
|
|
63
|
-
],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
init(params) {
|
|
67
|
-
var _a;
|
|
68
|
-
if (!params)
|
|
69
|
-
return;
|
|
70
|
-
this.componentName = (_a = params.componentName) !== null && _a !== void 0 ? _a : this.componentName;
|
|
71
|
-
if (params.shapes) {
|
|
72
|
-
this.shapes = params.shapes;
|
|
73
|
-
}
|
|
74
|
-
else if (params.type && params.style) {
|
|
75
|
-
this.shapes = [
|
|
76
|
-
{
|
|
77
|
-
type: params.type,
|
|
78
|
-
style: params.style,
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
awake() {
|
|
84
|
-
this.redraw();
|
|
85
|
-
}
|
|
86
|
-
// 在 awake 之后再 schedule 一次重绘,绕过 v8 graphics context 在 awake 时
|
|
87
|
-
// 的 fillStyle 懒赋值问题(每条 fill 指令记录的 fillStyle 都被后续 fill 调用覆盖)。
|
|
88
|
-
start() {
|
|
89
|
-
this.redraw();
|
|
90
|
-
// 再延迟一次,确保 RenderSystem 已把 graphics 实例 attach 到 stage
|
|
91
|
-
queueMicrotask(() => this.redraw());
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* 解析 linear-gradient 字符串
|
|
95
|
-
* 支持格式: linear-gradient(180deg, #340033 4%, #93005D 83%, #CB2269 99%)
|
|
96
|
-
*/
|
|
97
|
-
parseLinearGradient(gradientStr) {
|
|
98
|
-
const match = gradientStr.match(/linear-gradient\s*\(\s*([^,]+),\s*(.+)\)/);
|
|
99
|
-
if (!match)
|
|
100
|
-
return null;
|
|
101
|
-
const angleStr = match[1].trim();
|
|
102
|
-
const angle = parseFloat(angleStr);
|
|
103
|
-
const colorStopsStr = match[2];
|
|
104
|
-
const colorStops = [];
|
|
105
|
-
const stops = colorStopsStr.split(/,(?![^(]*\))/);
|
|
106
|
-
for (const stop of stops) {
|
|
107
|
-
const stopMatch = stop.trim().match(/(.+?)\s+(\d+)%/);
|
|
108
|
-
if (stopMatch) {
|
|
109
|
-
const color = stopMatch[1].trim();
|
|
110
|
-
const position = parseFloat(stopMatch[2]) / 100;
|
|
111
|
-
colorStops.push({ color, position });
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return { angle, colorStops };
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* 创建 PixiJS FillGradient 对象
|
|
118
|
-
* CSS linear-gradient 角度定义:
|
|
119
|
-
* - 0deg = 从下到上(↑)
|
|
120
|
-
* - 90deg = 从左到右(→)
|
|
121
|
-
* - 180deg = 从上到下(↓)
|
|
122
|
-
* - 270deg = 从右到左(←)
|
|
123
|
-
*
|
|
124
|
-
* FillGradient 的坐标范围是 0~1 的相对坐标
|
|
125
|
-
*/
|
|
126
|
-
createGradient(angle, colorStops, width, height, _offsetX = 0, _offsetY = 0) {
|
|
127
|
-
let x0 = 0;
|
|
128
|
-
let y0 = 0;
|
|
129
|
-
let x1 = 0;
|
|
130
|
-
let y1 = 0;
|
|
131
|
-
if (angle === 0) {
|
|
132
|
-
x0 = 0.5;
|
|
133
|
-
y0 = 1;
|
|
134
|
-
x1 = 0.5;
|
|
135
|
-
y1 = 0;
|
|
136
|
-
}
|
|
137
|
-
else if (angle === 90) {
|
|
138
|
-
x0 = 0;
|
|
139
|
-
y0 = 0.5;
|
|
140
|
-
x1 = 1;
|
|
141
|
-
y1 = 0.5;
|
|
142
|
-
}
|
|
143
|
-
else if (angle === 180) {
|
|
144
|
-
x0 = 0.5;
|
|
145
|
-
y0 = 0;
|
|
146
|
-
x1 = 0.5;
|
|
147
|
-
y1 = 1;
|
|
148
|
-
}
|
|
149
|
-
else if (angle === 270) {
|
|
150
|
-
x0 = 1;
|
|
151
|
-
y0 = 0.5;
|
|
152
|
-
x1 = 0;
|
|
153
|
-
y1 = 0.5;
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
// 通用角度:CSS 0deg 朝上,顺时针
|
|
157
|
-
const mathAngle = (90 - angle) * (Math.PI / 180);
|
|
158
|
-
const centerX = 0.5;
|
|
159
|
-
const centerY = 0.5;
|
|
160
|
-
const maxDimension = Math.max(width, height);
|
|
161
|
-
const diagonalLength = Math.sqrt(width * width + height * height);
|
|
162
|
-
const normalizedLength = diagonalLength / (2 * maxDimension);
|
|
163
|
-
x0 = centerX - Math.cos(mathAngle) * normalizedLength;
|
|
164
|
-
y0 = centerY - Math.sin(mathAngle) * normalizedLength;
|
|
165
|
-
x1 = centerX + Math.cos(mathAngle) * normalizedLength;
|
|
166
|
-
y1 = centerY + Math.sin(mathAngle) * normalizedLength;
|
|
167
|
-
}
|
|
168
|
-
const gradient = new pixi_js.FillGradient(x0, y0, x1, y1);
|
|
169
|
-
colorStops.forEach((stop) => {
|
|
170
|
-
gradient.addColorStop(stop.position, stop.color);
|
|
171
|
-
});
|
|
172
|
-
return gradient;
|
|
173
|
-
}
|
|
174
|
-
/** 计算形状包围盒,供渐变坐标使用 */
|
|
175
|
-
getShapeBounds(type, style) {
|
|
176
|
-
const x = style.x || 0;
|
|
177
|
-
const y = style.y || 0;
|
|
178
|
-
switch (type) {
|
|
179
|
-
case exports.UIShapeType.RECT:
|
|
180
|
-
case exports.UIShapeType.ROUNDED_RECT:
|
|
181
|
-
return { width: style.width, height: style.height, x, y };
|
|
182
|
-
case exports.UIShapeType.CIRCLE:
|
|
183
|
-
return { width: style.radius * 2, height: style.radius * 2, x, y };
|
|
184
|
-
case exports.UIShapeType.ELLIPSE:
|
|
185
|
-
return { width: style.width, height: style.height, x, y };
|
|
186
|
-
default:
|
|
187
|
-
return { width: 100, height: 100, x, y };
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
/** 重新绘制所有形状 */
|
|
191
|
-
redraw() {
|
|
192
|
-
if (!this.gameObject)
|
|
193
|
-
return;
|
|
194
|
-
let graphicsComponent = this.gameObject.getComponent('Graphics');
|
|
195
|
-
if (!graphicsComponent) {
|
|
196
|
-
graphicsComponent = this.gameObject.addComponent(new pluginRendererGraphics.Graphics());
|
|
197
|
-
}
|
|
198
|
-
const graphics = graphicsComponent.graphics;
|
|
199
|
-
if (!graphics) {
|
|
200
|
-
console.warn('Graphics component has no graphics instance');
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
graphics.clear();
|
|
204
|
-
this.shapes.forEach((shape) => {
|
|
205
|
-
this.drawShape(graphics, shape.type, shape.style);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
/** 绘制单个形状 */
|
|
209
|
-
drawShape(graphics, type, style) {
|
|
210
|
-
if (style.alpha !== undefined) {
|
|
211
|
-
graphics.alpha = style.alpha;
|
|
212
|
-
}
|
|
213
|
-
if (style.stroke && style.lineWidth !== undefined) {
|
|
214
|
-
graphics.setStrokeStyle({ color: style.stroke, width: style.lineWidth });
|
|
215
|
-
}
|
|
216
|
-
else if (style.stroke) {
|
|
217
|
-
graphics.setStrokeStyle({ color: style.stroke, width: 1 });
|
|
218
|
-
}
|
|
219
|
-
switch (type) {
|
|
220
|
-
case exports.UIShapeType.RECT:
|
|
221
|
-
this.drawRect(graphics, style);
|
|
222
|
-
break;
|
|
223
|
-
case exports.UIShapeType.CIRCLE:
|
|
224
|
-
this.drawCircle(graphics, style);
|
|
225
|
-
break;
|
|
226
|
-
case exports.UIShapeType.ELLIPSE:
|
|
227
|
-
this.drawEllipse(graphics, style);
|
|
228
|
-
break;
|
|
229
|
-
case exports.UIShapeType.ROUNDED_RECT:
|
|
230
|
-
this.drawRoundedRect(graphics, style);
|
|
231
|
-
break;
|
|
232
|
-
default:
|
|
233
|
-
console.warn(`Unknown shape type: ${type}`);
|
|
234
|
-
}
|
|
235
|
-
if (style.fill) {
|
|
236
|
-
if (typeof style.fill === 'string' && style.fill.includes('linear-gradient')) {
|
|
237
|
-
const gradientInfo = this.parseLinearGradient(style.fill);
|
|
238
|
-
if (gradientInfo) {
|
|
239
|
-
const bounds = this.getShapeBounds(type, style);
|
|
240
|
-
const gradient = this.createGradient(gradientInfo.angle, gradientInfo.colorStops, bounds.width, bounds.height, bounds.x, bounds.y);
|
|
241
|
-
graphics.fill(gradient);
|
|
242
|
-
}
|
|
243
|
-
else {
|
|
244
|
-
console.warn(`Failed to parse gradient: ${style.fill}`);
|
|
245
|
-
graphics.fill('#000000');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
graphics.fill(style.fill);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
drawRect(graphics, style) {
|
|
254
|
-
const x = style.x || 0;
|
|
255
|
-
const y = style.y || 0;
|
|
256
|
-
graphics.rect(x, y, style.width, style.height);
|
|
257
|
-
}
|
|
258
|
-
drawCircle(graphics, style) {
|
|
259
|
-
const x = style.x || 0;
|
|
260
|
-
const y = style.y || 0;
|
|
261
|
-
graphics.circle(x, y, style.radius);
|
|
262
|
-
}
|
|
263
|
-
drawEllipse(graphics, style) {
|
|
264
|
-
const x = style.x || 0;
|
|
265
|
-
const y = style.y || 0;
|
|
266
|
-
graphics.ellipse(x, y, style.width / 2, style.height / 2);
|
|
267
|
-
}
|
|
268
|
-
drawRoundedRect(graphics, style) {
|
|
269
|
-
const x = style.x || 0;
|
|
270
|
-
const y = style.y || 0;
|
|
271
|
-
const radius = style.radius || 0;
|
|
272
|
-
graphics.roundRect(x, y, style.width, style.height, radius);
|
|
273
|
-
}
|
|
274
|
-
/** 追加一个形状 */
|
|
275
|
-
addShape(type, style) {
|
|
276
|
-
this.shapes.push({ type, style });
|
|
277
|
-
this.redraw();
|
|
278
|
-
}
|
|
279
|
-
/** 清除所有形状 */
|
|
280
|
-
clearShapes() {
|
|
281
|
-
this.shapes = [];
|
|
282
|
-
this.redraw();
|
|
283
|
-
}
|
|
284
|
-
/** 更新指定索引的形状 */
|
|
285
|
-
updateShape(index, type, style) {
|
|
286
|
-
if (index >= 0 && index < this.shapes.length) {
|
|
287
|
-
this.shapes[index] = { type, style };
|
|
288
|
-
this.redraw();
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
/** 移除指定索引的形状 */
|
|
292
|
-
removeShape(index) {
|
|
293
|
-
if (index >= 0 && index < this.shapes.length) {
|
|
294
|
-
this.shapes.splice(index, 1);
|
|
295
|
-
this.redraw();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
/** 获取形状数量 */
|
|
299
|
-
getShapeCount() {
|
|
300
|
-
return this.shapes.length;
|
|
301
|
-
}
|
|
302
|
-
/** 获取指定索引的形状(只读视图) */
|
|
303
|
-
getShape(index) {
|
|
304
|
-
if (index >= 0 && index < this.shapes.length) {
|
|
305
|
-
return this.shapes[index];
|
|
306
|
-
}
|
|
307
|
-
return null;
|
|
308
|
-
}
|
|
309
|
-
/** 设置所有形状的透明度 */
|
|
310
|
-
setAlpha(alpha) {
|
|
311
|
-
this.shapes.forEach((shape) => {
|
|
312
|
-
shape.style.alpha = alpha;
|
|
313
|
-
});
|
|
314
|
-
this.redraw();
|
|
315
|
-
}
|
|
316
|
-
/** 设置所有形状的填充色 */
|
|
317
|
-
setFill(fill) {
|
|
318
|
-
this.shapes.forEach((shape) => {
|
|
319
|
-
shape.style.fill = fill;
|
|
320
|
-
});
|
|
321
|
-
this.redraw();
|
|
322
|
-
}
|
|
323
|
-
/** 设置所有形状的描边 */
|
|
324
|
-
setStroke(stroke, lineWidth = 1) {
|
|
325
|
-
this.shapes.forEach((shape) => {
|
|
326
|
-
shape.style.stroke = stroke;
|
|
327
|
-
shape.style.lineWidth = lineWidth;
|
|
328
|
-
});
|
|
329
|
-
this.redraw();
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
/** 组件名称 */
|
|
333
|
-
UI.componentName = 'UI';
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Eva GameObject -> PIXI.Container resolver.
|
|
337
|
-
*
|
|
338
|
-
* 通过 RendererSystem.containerManager 拿到 GameObject 对应的 PIXI.Container。
|
|
339
|
-
* Eva 引擎的 RendererSystem 在 Transform ADD 时为每个 GameObject 创建空 Container,
|
|
340
|
-
* 渲染插件 (Img/Sprite/Graphics/...) 把自己的显示对象 addChild 到该 Container。
|
|
341
|
-
*
|
|
342
|
-
* plugin-ui 在 attach @pixi/ui 实例时也走同一路径,与现有渲染管线对齐。
|
|
343
|
-
*
|
|
344
|
-
* @returns 该 GameObject 的 PIXI.Container,未就绪时返回 null
|
|
345
|
-
*/
|
|
346
|
-
function getEvaContainer(game, go) {
|
|
347
|
-
var _a, _b, _c;
|
|
348
|
-
if (!game || !go)
|
|
349
|
-
return null;
|
|
350
|
-
const rendererSystem = findRendererSystem(game);
|
|
351
|
-
const containerManager = (_a = rendererSystem === null || rendererSystem === void 0 ? void 0 : rendererSystem.containerManager) !== null && _a !== void 0 ? _a : (_b = rendererSystem === null || rendererSystem === void 0 ? void 0 : rendererSystem.rendererManager) === null || _b === void 0 ? void 0 : _b.containerManager;
|
|
352
|
-
if (!(containerManager === null || containerManager === void 0 ? void 0 : containerManager.getContainer))
|
|
353
|
-
return null;
|
|
354
|
-
return (_c = containerManager.getContainer(go.id)) !== null && _c !== void 0 ? _c : null;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* 反查 RendererSystem 实例。
|
|
358
|
-
*
|
|
359
|
-
* 注意:`@eva/plugin-renderer` 的实际 class name 是 `Renderer`,`static systemName = 'Renderer'`。
|
|
360
|
-
* 外部 import 时通常起别名 `RendererSystem`(import/export 别名),
|
|
361
|
-
* 但 Eva.js 的 game.getSystem(string) 走 `system.name === S` 匹配,所以查询字符串是 'Renderer'。
|
|
362
|
-
*
|
|
363
|
-
* 同时遍历 systems 数组兜底:有 containerManager 字段的 system 即可识别为 RendererSystem。
|
|
364
|
-
*/
|
|
365
|
-
function findRendererSystem(game) {
|
|
366
|
-
var _a, _b, _c, _d, _e, _f;
|
|
367
|
-
// 1) 优先 string getSystem('Renderer')
|
|
368
|
-
const tryStr = (_b = (_a = game).getSystem) === null || _b === void 0 ? void 0 : _b.call(_a, 'Renderer');
|
|
369
|
-
if (tryStr)
|
|
370
|
-
return tryStr;
|
|
371
|
-
// 2) 遍历:系统名 / 类名 / 独有的 containerManager 字段
|
|
372
|
-
const systems = (_c = game.systems) !== null && _c !== void 0 ? _c : [];
|
|
373
|
-
for (const sys of systems) {
|
|
374
|
-
const ctorName = (_e = (_d = sys === null || sys === void 0 ? void 0 : sys.constructor) === null || _d === void 0 ? void 0 : _d.systemName) !== null && _e !== void 0 ? _e : (_f = sys === null || sys === void 0 ? void 0 : sys.constructor) === null || _f === void 0 ? void 0 : _f.name;
|
|
375
|
-
if (ctorName === 'Renderer' || ctorName === 'RendererSystem')
|
|
376
|
-
return sys;
|
|
377
|
-
if ((sys === null || sys === void 0 ? void 0 : sys.name) === 'Renderer' || (sys === null || sys === void 0 ? void 0 : sys.name) === 'RendererSystem')
|
|
378
|
-
return sys;
|
|
379
|
-
if (sys === null || sys === void 0 ? void 0 : sys.containerManager)
|
|
380
|
-
return sys;
|
|
381
|
-
}
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* 把 @pixi/ui 实例 attach 到 GameObject 的 PIXI.Container。
|
|
386
|
-
*
|
|
387
|
-
* 行为:addChildAt(instance, 0) — 与 Img/Graphics 系列保持一致放在 z=0 底层。
|
|
388
|
-
* 如果 Container 还没就绪(同帧 Transform 尚未处理),返回 false,调用方应在
|
|
389
|
-
* queueMicrotask 后重试一次。
|
|
390
|
-
*/
|
|
391
|
-
function attachToGameObject(game, go, instance) {
|
|
392
|
-
const container = getEvaContainer(game, go);
|
|
393
|
-
if (!container)
|
|
394
|
-
return false;
|
|
395
|
-
try {
|
|
396
|
-
if (instance.parent && instance.parent !== container) {
|
|
397
|
-
instance.parent.removeChild(instance);
|
|
398
|
-
}
|
|
399
|
-
if (typeof container.addChildAt === 'function') {
|
|
400
|
-
container.addChildAt(instance, 0);
|
|
401
|
-
}
|
|
402
|
-
else {
|
|
403
|
-
container.addChild(instance);
|
|
404
|
-
}
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
catch (_) {
|
|
408
|
-
return false;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* 反之:从 GameObject Container 移除并 destroy @pixi/ui 实例。
|
|
413
|
-
*
|
|
414
|
-
* destroy({ children: true }) 会递归销毁内部 view,但 Texture 不被销毁
|
|
415
|
-
* (texture: false 默认)以避免共享纹理被误销毁。
|
|
416
|
-
*/
|
|
417
|
-
function detachFromGameObject(game, go, instance) {
|
|
418
|
-
if (!instance)
|
|
419
|
-
return;
|
|
420
|
-
const container = getEvaContainer(game, go);
|
|
421
|
-
try {
|
|
422
|
-
if (container && instance.parent === container) {
|
|
423
|
-
container.removeChild(instance);
|
|
424
|
-
}
|
|
425
|
-
if (typeof instance.destroy === 'function') {
|
|
426
|
-
instance.destroy({ children: true });
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
catch (_) {
|
|
430
|
-
// ignore — destroy 二次调用 / 已 detach 都视为成功
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* 在 Container 未就绪时延后执行 fn。
|
|
435
|
-
* 行为:同步尝试一次,失败 queueMicrotask 重试,最多 4 次(每 microtask 回合一次)。
|
|
436
|
-
* 主要应对 plugin-ui Component 与核心 Transform 在同帧 ADD 时的时序竞争。
|
|
437
|
-
*/
|
|
438
|
-
function whenContainerReady(game, go, fn, attempts = 4) {
|
|
439
|
-
const container = getEvaContainer(game, go);
|
|
440
|
-
if (container) {
|
|
441
|
-
fn(container);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
if (attempts <= 0)
|
|
445
|
-
return;
|
|
446
|
-
queueMicrotask(() => whenContainerReady(game, go, fn, attempts - 1));
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* 把 ViewRef 解析为 PIXI 显示对象 (Container | Sprite | Graphics)。
|
|
451
|
-
*
|
|
452
|
-
* @param game Game 实例(用来反查 RendererSystem.containerManager / resource)
|
|
453
|
-
* @param go 宿主 GameObject(entityName 类型 ViewRef 在它的子里查找)
|
|
454
|
-
* @returns PIXI 显示对象,失败返回 null
|
|
455
|
-
*/
|
|
456
|
-
function resolveViewRef(game, go, ref) {
|
|
457
|
-
var _a;
|
|
458
|
-
if (!ref)
|
|
459
|
-
return null;
|
|
460
|
-
if ('entityName' in ref) {
|
|
461
|
-
const child = findChildEntity$1(go, ref.entityName);
|
|
462
|
-
if (child) {
|
|
463
|
-
const childContainer = getEvaContainer(game, child);
|
|
464
|
-
if (childContainer) {
|
|
465
|
-
// 把子 entity 的 PIXI Container 从原父 detach,@pixi/ui 内部会 addChild 它
|
|
466
|
-
try {
|
|
467
|
-
if (childContainer.parent) {
|
|
468
|
-
childContainer.parent.removeChild(childContainer);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
catch (_) { }
|
|
472
|
-
return childContainer;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
if (ref.fallback)
|
|
476
|
-
return resolveViewRef(game, go, ref.fallback);
|
|
477
|
-
return null;
|
|
478
|
-
}
|
|
479
|
-
if ('ui' in ref) {
|
|
480
|
-
return (_a = drawInlineShape(ref.ui)) !== null && _a !== void 0 ? _a : (ref.fallback ? resolveViewRef(game, go, ref.fallback) : null);
|
|
481
|
-
}
|
|
482
|
-
if ('texture' in ref) {
|
|
483
|
-
const tex = resolveTexture(game, ref.texture);
|
|
484
|
-
if (tex)
|
|
485
|
-
return new pixi_js.Sprite(tex);
|
|
486
|
-
if (ref.fallback)
|
|
487
|
-
return resolveViewRef(game, go, ref.fallback);
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
490
|
-
if ('color' in ref) {
|
|
491
|
-
try {
|
|
492
|
-
const g = new pixi_js.Graphics();
|
|
493
|
-
if (ref.radius && ref.radius > 0) {
|
|
494
|
-
g.roundRect(0, 0, ref.width, ref.height, ref.radius);
|
|
495
|
-
}
|
|
496
|
-
else {
|
|
497
|
-
g.rect(0, 0, ref.width, ref.height);
|
|
498
|
-
}
|
|
499
|
-
g.fill(ref.color);
|
|
500
|
-
return g;
|
|
501
|
-
}
|
|
502
|
-
catch (e) {
|
|
503
|
-
console.error('[view-resolver] color view create failed', e);
|
|
504
|
-
return null;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return null;
|
|
508
|
-
}
|
|
509
|
-
/** 把 ViewRef 解析为已存在的 Texture(用于 ProgressBar/Slider/Input 的 nineSlice 路径) */
|
|
510
|
-
function resolveTexture(game, key) {
|
|
511
|
-
var _a, _b;
|
|
512
|
-
// 1) Eva resource pool
|
|
513
|
-
const resourceModule = (_a = game === null || game === void 0 ? void 0 : game.resource) !== null && _a !== void 0 ? _a : null;
|
|
514
|
-
if ((_b = resourceModule === null || resourceModule === void 0 ? void 0 : resourceModule.instances) === null || _b === void 0 ? void 0 : _b[key]) {
|
|
515
|
-
const inst = resourceModule.instances[key];
|
|
516
|
-
if (inst instanceof pixi_js.Texture)
|
|
517
|
-
return inst;
|
|
518
|
-
if ((inst === null || inst === void 0 ? void 0 : inst.image) instanceof pixi_js.Texture)
|
|
519
|
-
return inst.image;
|
|
520
|
-
}
|
|
521
|
-
// 2) global asset cache (Texture.from)
|
|
522
|
-
try {
|
|
523
|
-
return pixi_js.Texture.from(key);
|
|
524
|
-
}
|
|
525
|
-
catch (_) {
|
|
526
|
-
return null;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
function drawInlineShape(shape) {
|
|
530
|
-
var _a, _b, _c;
|
|
531
|
-
if (!shape || !shape.type)
|
|
532
|
-
return null;
|
|
533
|
-
const g = new pixi_js.Graphics();
|
|
534
|
-
const { style } = shape;
|
|
535
|
-
const w = (_a = style.width) !== null && _a !== void 0 ? _a : 0;
|
|
536
|
-
const h = (_b = style.height) !== null && _b !== void 0 ? _b : 0;
|
|
537
|
-
const r = (_c = style.radius) !== null && _c !== void 0 ? _c : 0;
|
|
538
|
-
switch (shape.type) {
|
|
539
|
-
case 'rect':
|
|
540
|
-
g.rect(0, 0, w, h);
|
|
541
|
-
break;
|
|
542
|
-
case 'roundedRect':
|
|
543
|
-
g.roundRect(0, 0, w, h, r);
|
|
544
|
-
break;
|
|
545
|
-
case 'circle':
|
|
546
|
-
g.circle(r, r, r);
|
|
547
|
-
break;
|
|
548
|
-
case 'ellipse':
|
|
549
|
-
g.ellipse(w / 2, h / 2, w / 2, h / 2);
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
if (style.fill !== undefined)
|
|
553
|
-
g.fill(style.fill);
|
|
554
|
-
if (style.stroke !== undefined && style.lineWidth !== undefined) {
|
|
555
|
-
g.setStrokeStyle({ color: style.stroke, width: style.lineWidth });
|
|
556
|
-
g.stroke();
|
|
557
|
-
}
|
|
558
|
-
if (style.alpha !== undefined)
|
|
559
|
-
g.alpha = style.alpha;
|
|
560
|
-
return g;
|
|
561
|
-
}
|
|
562
|
-
/**
|
|
563
|
-
* 在 GameObject 的 transform.children 中查找指定名字的子 entity。
|
|
564
|
-
* 不递归 — plugin-ui 的视觉子项语义只允许直接子节点。
|
|
565
|
-
*/
|
|
566
|
-
function findChildEntity$1(go, name) {
|
|
567
|
-
var _a, _b, _c;
|
|
568
|
-
if (typeof go.findChildByName === 'function') {
|
|
569
|
-
try {
|
|
570
|
-
const c = go.findChildByName(name);
|
|
571
|
-
if (c)
|
|
572
|
-
return c;
|
|
573
|
-
}
|
|
574
|
-
catch (_) { }
|
|
575
|
-
}
|
|
576
|
-
const children = (_b = (_a = go.transform) === null || _a === void 0 ? void 0 : _a.children) !== null && _b !== void 0 ? _b : [];
|
|
577
|
-
for (const tf of children) {
|
|
578
|
-
if (((_c = tf === null || tf === void 0 ? void 0 : tf.gameObject) === null || _c === void 0 ? void 0 : _c.name) === name)
|
|
579
|
-
return tf.gameObject;
|
|
580
|
-
}
|
|
581
|
-
return null;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/**
|
|
585
|
-
* Generic ECS wrapper factory for @pixi/ui components.
|
|
586
|
-
*
|
|
587
|
-
* 设计目标:每个 @pixi/ui 类的 ECS wrapper 只需声明一份 metadata(字段定义 + view ref keys
|
|
588
|
-
* + options builder + signal map + change syncer),不再写 class 样板。
|
|
589
|
-
*
|
|
590
|
-
* 不能塌缩进 metadata 的特殊行为(由 ComponentDefinition 上的 hook 字段表达):
|
|
591
|
-
* - postCreate: 初始 prop sync(enabled / selectedTint 等)
|
|
592
|
-
* - onAttachedExtra: ADD 完成后跑(Dialog 挂 contentChild、MaskedFrame 挂 borderView)
|
|
593
|
-
* - syncOnChange: CHANGE 时把 wrapper 字段写回 @pixi/ui 实例
|
|
594
|
-
* - mixin: 额外 instance method(ProgressBar.getProgressPct)
|
|
595
|
-
*
|
|
596
|
-
* RadioGroup 因跨实体协调 + queueMicrotask 时序耦合,**不走** factory,保留独立 class。
|
|
597
|
-
*/
|
|
598
|
-
// ============================================================
|
|
599
|
-
// Generated wrapper base class
|
|
600
|
-
// ============================================================
|
|
601
|
-
/**
|
|
602
|
-
* 由 defineUiComponent 创建的 wrapper class 基础形状。
|
|
603
|
-
* 所有由 factory 生成的 class 共享下面这套 init / applyDeclarativeProps / getInspectorMetadata
|
|
604
|
-
* / 内部状态计数器逻辑。
|
|
605
|
-
*/
|
|
606
|
-
class PixiUiComponent extends eva_js.Component {
|
|
607
|
-
constructor() {
|
|
608
|
-
super(...arguments);
|
|
609
|
-
/** runtime 计数,供 spec / debug 验证(toggleCount / pressCount / hoverCount / ...)*/
|
|
610
|
-
this.toggleCount = 0;
|
|
611
|
-
this.pressCount = 0;
|
|
612
|
-
this.hoverCount = 0;
|
|
613
|
-
this.changeCount = 0;
|
|
614
|
-
this.updateCount = 0;
|
|
615
|
-
this.selectCount = 0;
|
|
616
|
-
/** Input 私有 focused */
|
|
617
|
-
this.focused = false;
|
|
618
|
-
}
|
|
619
|
-
init(p) {
|
|
620
|
-
if (p)
|
|
621
|
-
this.applyParams(p);
|
|
622
|
-
}
|
|
623
|
-
applyDeclarativeProps(next, _prev) {
|
|
624
|
-
this.applyParams(next);
|
|
625
|
-
}
|
|
626
|
-
/** 通用字段 apply 循环:按 fieldSpec 走对应分支 */
|
|
627
|
-
applyParams(p) {
|
|
628
|
-
const def = this.constructor.__def;
|
|
629
|
-
if (!def)
|
|
630
|
-
return;
|
|
631
|
-
for (const [name, spec] of Object.entries(def.fields)) {
|
|
632
|
-
if (!(name in p))
|
|
633
|
-
continue;
|
|
634
|
-
const v = p[name];
|
|
635
|
-
if (v === undefined)
|
|
636
|
-
continue;
|
|
637
|
-
switch (spec.kind) {
|
|
638
|
-
case 'scalar':
|
|
639
|
-
this[name] = v;
|
|
640
|
-
break;
|
|
641
|
-
case 'shallowMerge':
|
|
642
|
-
this[name] = Object.assign(Object.assign({}, this[name]), v);
|
|
643
|
-
break;
|
|
644
|
-
case 'arrayCopy':
|
|
645
|
-
if (Array.isArray(v))
|
|
646
|
-
this[name] = v.slice();
|
|
647
|
-
break;
|
|
648
|
-
case 'tuple':
|
|
649
|
-
if (Array.isArray(v) && (!spec.tupleLength || v.length === spec.tupleLength)) {
|
|
650
|
-
this[name] = v.slice();
|
|
651
|
-
}
|
|
652
|
-
break;
|
|
653
|
-
case 'opaque':
|
|
654
|
-
this[name] = v;
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
function defineUiComponent(def) {
|
|
661
|
-
const factoryName = def.name;
|
|
662
|
-
// 用 anonymous class + 静态 cast 绕过 TS2417(static __def 类型与 base 不严格匹配)
|
|
663
|
-
class Generated extends PixiUiComponent {
|
|
664
|
-
constructor(params) {
|
|
665
|
-
super(params);
|
|
666
|
-
this.componentName = factoryName;
|
|
667
|
-
// 所有 field 都必须在实例上预声明(即使是 undefined),否则 Eva
|
|
668
|
-
// componentObserver 的 `(key in obj)` 检查会失败,导致 prop 不被监听。
|
|
669
|
-
for (const [name, spec] of Object.entries(def.fields)) {
|
|
670
|
-
if (spec.default !== undefined) {
|
|
671
|
-
this[name] = cloneDefault(spec.default);
|
|
672
|
-
}
|
|
673
|
-
else if (!(name in this)) {
|
|
674
|
-
this[name] = undefined;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
static getInspectorMetadata() {
|
|
679
|
-
return buildInspectorMetadata(def);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
// 必要 cast,因为 TypeScript 不支持\"派生类窄化 static 字段类型\"
|
|
683
|
-
const Cls = Generated;
|
|
684
|
-
Cls.componentName = factoryName;
|
|
685
|
-
Cls.__def = def;
|
|
686
|
-
// class.name(jest snapshot / Inspector 反射用)
|
|
687
|
-
try {
|
|
688
|
-
Object.defineProperty(Cls, 'name', { value: factoryName });
|
|
689
|
-
}
|
|
690
|
-
catch (_) { }
|
|
691
|
-
// mixin 注入 prototype(ProgressBar.getProgressPct 等)
|
|
692
|
-
if (def.mixin) {
|
|
693
|
-
for (const [k, fn] of Object.entries(def.mixin)) {
|
|
694
|
-
Generated.prototype[k] = fn;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
return Cls;
|
|
698
|
-
}
|
|
699
|
-
function cloneDefault(v) {
|
|
700
|
-
if (v === null || typeof v !== 'object')
|
|
701
|
-
return v;
|
|
702
|
-
if (Array.isArray(v))
|
|
703
|
-
return v.slice();
|
|
704
|
-
return Object.assign({}, v);
|
|
705
|
-
}
|
|
706
|
-
function buildInspectorMetadata(def) {
|
|
707
|
-
const children = [];
|
|
708
|
-
for (const [name, spec] of Object.entries(def.fields)) {
|
|
709
|
-
if (spec.inspector) {
|
|
710
|
-
children.push(Object.assign(Object.assign({}, spec.inspector), { name: spec.inspector.name || name }));
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
return {
|
|
714
|
-
name: def.name,
|
|
715
|
-
type: 'object',
|
|
716
|
-
isArray: false,
|
|
717
|
-
isFolder: true,
|
|
718
|
-
children,
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
// ============================================================
|
|
722
|
-
// View resolution helper(by ViewSchema)
|
|
723
|
-
// ============================================================
|
|
724
|
-
function resolveViewsBySchema(game, go, component, schema) {
|
|
725
|
-
var _a, _b;
|
|
726
|
-
if (!schema)
|
|
727
|
-
return {};
|
|
728
|
-
switch (schema.kind) {
|
|
729
|
-
case 'single': {
|
|
730
|
-
const ref = component[schema.key];
|
|
731
|
-
const v = resolveViewRef(game, go, ref);
|
|
732
|
-
if (schema.required && !v)
|
|
733
|
-
return null;
|
|
734
|
-
return { single: v };
|
|
735
|
-
}
|
|
736
|
-
case 'object': {
|
|
737
|
-
const folder = (_a = component[schema.folder]) !== null && _a !== void 0 ? _a : {};
|
|
738
|
-
const out = {};
|
|
739
|
-
for (const { name, required } of schema.keys) {
|
|
740
|
-
const v = resolveViewRef(game, go, folder[name]);
|
|
741
|
-
if (required && !v)
|
|
742
|
-
return null;
|
|
743
|
-
out[name] = v;
|
|
744
|
-
}
|
|
745
|
-
return { object: out };
|
|
746
|
-
}
|
|
747
|
-
case 'array': {
|
|
748
|
-
const refs = (_b = component[schema.key]) !== null && _b !== void 0 ? _b : [];
|
|
749
|
-
const out = refs.map((r) => resolveViewRef(game, go, r)).filter(Boolean);
|
|
750
|
-
if (out.length < schema.minLength)
|
|
751
|
-
return null;
|
|
752
|
-
return { array: out };
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/**
|
|
758
|
-
* @pixi/ui v2.x 包装层 — 14 个 ECS Component 由 metadata 驱动生成。
|
|
759
|
-
*
|
|
760
|
-
* 替代旧的 14 份 lib/components/*.ts 样板文件。新增组件 = 在本文件加一份 metadata,无需新文件。
|
|
761
|
-
*
|
|
762
|
-
* RadioGroup 因跨实体协调(收集子 CheckBox 实例 + queueMicrotask)无法 metadata 化,
|
|
763
|
-
* 保留在 ./radio-group.ts 单独维护。
|
|
764
|
-
*/
|
|
765
|
-
// ============================================================
|
|
766
|
-
// Common helpers
|
|
767
|
-
// ============================================================
|
|
768
|
-
/** 绝大多数 wrapper 共享 enabled / bindToStore 字段。 */
|
|
769
|
-
const ENABLED = { kind: 'scalar', default: true, inspector: { name: 'enabled', type: 'boolean' } };
|
|
770
|
-
const BIND_STORE = { kind: 'scalar', inspector: { name: 'bindToStore', type: 'string' } };
|
|
771
|
-
/** 用 mixin 把 getProgressPct 注入 ProgressBar / CircularProgressBar */
|
|
772
|
-
function getProgressPctMixin() {
|
|
773
|
-
const [min, max] = this.valueRange;
|
|
774
|
-
if (max <= min)
|
|
775
|
-
return 0;
|
|
776
|
-
const clamped = Math.max(min, Math.min(max, this.value));
|
|
777
|
-
return ((clamped - min) / (max - min)) * 100;
|
|
778
|
-
}
|
|
779
|
-
/** Dialog backdropColor 字符串 → 数字转换 */
|
|
780
|
-
function backdropColorToNumber(c) {
|
|
781
|
-
if (c === undefined)
|
|
782
|
-
return undefined;
|
|
783
|
-
if (typeof c === 'string')
|
|
784
|
-
return parseInt(c.replace('#', ''), 16);
|
|
785
|
-
return c;
|
|
786
|
-
}
|
|
787
|
-
// ============================================================
|
|
788
|
-
// 14 个 component definitions
|
|
789
|
-
// ============================================================
|
|
790
|
-
const BUTTON_DEF = {
|
|
791
|
-
name: 'Button',
|
|
792
|
-
signalPrefix: 'button',
|
|
793
|
-
pixiClass: ui.ButtonContainer,
|
|
794
|
-
fields: {
|
|
795
|
-
enabled: ENABLED,
|
|
796
|
-
view: { kind: 'opaque', inspector: { name: 'view', type: 'object' } },
|
|
797
|
-
},
|
|
798
|
-
views: { kind: 'single', key: 'view', required: true },
|
|
799
|
-
// PixiButton 构造是位置参数: new PixiButton(view)
|
|
800
|
-
positional: true,
|
|
801
|
-
optionsBuilder: (_c, v) => [v.single],
|
|
802
|
-
postCreate: (inst, c) => { inst.enabled = c.enabled; },
|
|
803
|
-
signalMap: (c) => ({
|
|
804
|
-
press: () => { c.pressCount += 1; return {}; },
|
|
805
|
-
down: true, up: true, hover: true,
|
|
806
|
-
}),
|
|
807
|
-
syncOnChange: (inst, c) => { inst.enabled = c.enabled; },
|
|
808
|
-
};
|
|
809
|
-
const FANCY_BUTTON_DEF = {
|
|
810
|
-
name: 'FancyButton',
|
|
811
|
-
signalPrefix: 'fancybutton',
|
|
812
|
-
pixiClass: ui.FancyButton,
|
|
813
|
-
fields: {
|
|
814
|
-
enabled: ENABLED,
|
|
815
|
-
state: { kind: 'scalar', default: 'default', inspector: { name: 'state', type: 'string' } },
|
|
816
|
-
selected: { kind: 'scalar', default: false, inspector: { name: 'selected', type: 'boolean' } },
|
|
817
|
-
text: { kind: 'scalar', inspector: { name: 'text', type: 'string' } },
|
|
818
|
-
padding: { kind: 'scalar', inspector: { name: 'padding', type: 'number' } },
|
|
819
|
-
selectedTint: { kind: 'scalar', default: '#FFD680', inspector: { name: 'selectedTint', type: 'color' } },
|
|
820
|
-
views: { kind: 'shallowMerge', default: {}, inspector: { name: 'views', type: 'object', isFolder: true, children: [
|
|
821
|
-
{ name: 'default', type: 'object' }, { name: 'hover', type: 'object' },
|
|
822
|
-
{ name: 'pressed', type: 'object' }, { name: 'disabled', type: 'object' },
|
|
823
|
-
] } },
|
|
824
|
-
offset: { kind: 'shallowMerge', default: {} },
|
|
825
|
-
textOffset: { kind: 'shallowMerge', default: {} },
|
|
826
|
-
nineSliceSprite: { kind: 'opaque' },
|
|
827
|
-
},
|
|
828
|
-
views: {
|
|
829
|
-
kind: 'object', folder: 'views',
|
|
830
|
-
keys: [
|
|
831
|
-
{ name: 'default', required: false },
|
|
832
|
-
{ name: 'hover', required: false },
|
|
833
|
-
{ name: 'pressed', required: false },
|
|
834
|
-
{ name: 'disabled', required: false },
|
|
835
|
-
],
|
|
836
|
-
},
|
|
837
|
-
optionsBuilder: (c, v) => {
|
|
838
|
-
var _a, _b, _d, _e;
|
|
839
|
-
// FancyButton 内部 updateView 不接受 null;只把已 resolve 的 view 字段传过去
|
|
840
|
-
const opts = {
|
|
841
|
-
text: c.text, padding: c.padding,
|
|
842
|
-
offset: c.offset, textOffset: c.textOffset,
|
|
843
|
-
nineSliceSprite: c.nineSliceSprite,
|
|
844
|
-
};
|
|
845
|
-
if ((_a = v.object) === null || _a === void 0 ? void 0 : _a.default)
|
|
846
|
-
opts.defaultView = v.object.default;
|
|
847
|
-
if ((_b = v.object) === null || _b === void 0 ? void 0 : _b.hover)
|
|
848
|
-
opts.hoverView = v.object.hover;
|
|
849
|
-
if ((_d = v.object) === null || _d === void 0 ? void 0 : _d.pressed)
|
|
850
|
-
opts.pressedView = v.object.pressed;
|
|
851
|
-
if ((_e = v.object) === null || _e === void 0 ? void 0 : _e.disabled)
|
|
852
|
-
opts.disabledView = v.object.disabled;
|
|
853
|
-
return opts;
|
|
854
|
-
},
|
|
855
|
-
postCreate: (inst, c) => {
|
|
856
|
-
inst.enabled = c.enabled;
|
|
857
|
-
if (c.selected)
|
|
858
|
-
applySelectedTint(inst, c);
|
|
859
|
-
},
|
|
860
|
-
signalMap: (c) => ({
|
|
861
|
-
press: () => { c.pressCount += 1; return {}; },
|
|
862
|
-
hover: () => { c.hoverCount += 1; return {}; },
|
|
863
|
-
down: true, up: true,
|
|
864
|
-
}),
|
|
865
|
-
syncOnChange: (inst, c) => {
|
|
866
|
-
inst.enabled = c.enabled;
|
|
867
|
-
if (c.text !== undefined && inst.text !== c.text)
|
|
868
|
-
inst.text = c.text;
|
|
869
|
-
if (c.padding !== undefined && inst.padding !== c.padding)
|
|
870
|
-
inst.padding = c.padding;
|
|
871
|
-
applySelectedTint(inst, c);
|
|
872
|
-
},
|
|
873
|
-
};
|
|
874
|
-
function applySelectedTint(inst, c) {
|
|
875
|
-
try {
|
|
876
|
-
const dv = inst.defaultView;
|
|
877
|
-
if (dv && 'tint' in dv)
|
|
878
|
-
dv.tint = c.selected ? c.selectedTint : 0xFFFFFF;
|
|
879
|
-
}
|
|
880
|
-
catch (_) { }
|
|
881
|
-
}
|
|
882
|
-
const CHECKBOX_DEF = {
|
|
883
|
-
name: 'CheckBox',
|
|
884
|
-
signalPrefix: 'checkbox',
|
|
885
|
-
pixiClass: ui.CheckBox,
|
|
886
|
-
fields: {
|
|
887
|
-
enabled: ENABLED,
|
|
888
|
-
checked: { kind: 'scalar', default: false, inspector: { name: 'checked', type: 'boolean' } },
|
|
889
|
-
text: { kind: 'scalar', inspector: { name: 'text', type: 'string' } },
|
|
890
|
-
bindToStore: BIND_STORE,
|
|
891
|
-
views: { kind: 'shallowMerge', default: {}, inspector: { name: 'views', type: 'object', isFolder: true, children: [
|
|
892
|
-
{ name: 'checked', type: 'object' }, { name: 'unchecked', type: 'object' },
|
|
893
|
-
] } },
|
|
894
|
-
disabledStyle: { kind: 'shallowMerge', default: { alpha: 0.4, scale: 1 } },
|
|
895
|
-
},
|
|
896
|
-
views: {
|
|
897
|
-
kind: 'object', folder: 'views',
|
|
898
|
-
keys: [
|
|
899
|
-
{ name: 'checked', required: true },
|
|
900
|
-
{ name: 'unchecked', required: true },
|
|
901
|
-
],
|
|
902
|
-
},
|
|
903
|
-
optionsBuilder: (c, v) => ({
|
|
904
|
-
style: { checked: v.object.checked, unchecked: v.object.unchecked },
|
|
905
|
-
text: c.text, checked: c.checked,
|
|
906
|
-
}),
|
|
907
|
-
signalMap: (c) => ({
|
|
908
|
-
check: (state) => { c.checked = state; c.toggleCount += 1; return { value: state }; },
|
|
909
|
-
}),
|
|
910
|
-
syncOnChange: (inst, c) => {
|
|
911
|
-
if (inst.checked !== c.checked)
|
|
912
|
-
inst.checked = c.checked;
|
|
913
|
-
},
|
|
914
|
-
};
|
|
915
|
-
const SWITCHER_DEF = {
|
|
916
|
-
name: 'Switcher',
|
|
917
|
-
signalPrefix: 'switcher',
|
|
918
|
-
pixiClass: ui.Switcher,
|
|
919
|
-
fields: {
|
|
920
|
-
active: { kind: 'scalar', default: 0, inspector: { name: 'active', type: 'number' } },
|
|
921
|
-
views: { kind: 'arrayCopy', default: [] },
|
|
922
|
-
triggerEvent: { kind: 'scalar', default: 'onPress', inspector: { name: 'triggerEvent', type: 'string' } },
|
|
923
|
-
bindToStore: BIND_STORE,
|
|
924
|
-
},
|
|
925
|
-
views: { kind: 'array', key: 'views', minLength: 2 },
|
|
926
|
-
// PixiSwitcher 构造是位置参数: new PixiSwitcher(views, triggerEvent, activeViewID)
|
|
927
|
-
positional: true,
|
|
928
|
-
optionsBuilder: (c, v) => [v.array, c.triggerEvent, c.active],
|
|
929
|
-
signalMap: (c) => ({
|
|
930
|
-
change: (state) => {
|
|
931
|
-
c.active = typeof state === 'number' ? state : (state ? 1 : 0);
|
|
932
|
-
c.changeCount += 1;
|
|
933
|
-
return { active: c.active };
|
|
934
|
-
},
|
|
935
|
-
}),
|
|
936
|
-
syncOnChange: (inst, c) => {
|
|
937
|
-
if (inst.active !== c.active)
|
|
938
|
-
inst.active = c.active;
|
|
939
|
-
},
|
|
940
|
-
};
|
|
941
|
-
const SLIDER_DEF = {
|
|
942
|
-
name: 'Slider',
|
|
943
|
-
signalPrefix: 'slider',
|
|
944
|
-
pixiClass: ui.Slider,
|
|
945
|
-
fields: {
|
|
946
|
-
enabled: ENABLED,
|
|
947
|
-
value: { kind: 'scalar', default: 0, inspector: { name: 'value', type: 'number' } },
|
|
948
|
-
min: { kind: 'scalar', default: 0, inspector: { name: 'min', type: 'number' } },
|
|
949
|
-
max: { kind: 'scalar', default: 100, inspector: { name: 'max', type: 'number' } },
|
|
950
|
-
step: { kind: 'scalar', default: 1, inspector: { name: 'step', type: 'number' } },
|
|
951
|
-
orientation: { kind: 'scalar', default: 'horizontal', inspector: { name: 'orientation', type: 'string' } },
|
|
952
|
-
showValue: { kind: 'scalar', default: false, inspector: { name: 'showValue', type: 'boolean' } },
|
|
953
|
-
views: { kind: 'shallowMerge', default: {} },
|
|
954
|
-
bindToStore: BIND_STORE,
|
|
955
|
-
},
|
|
956
|
-
views: {
|
|
957
|
-
kind: 'object', folder: 'views',
|
|
958
|
-
keys: [
|
|
959
|
-
{ name: 'bg', required: true },
|
|
960
|
-
{ name: 'fill', required: true },
|
|
961
|
-
{ name: 'thumb', required: true },
|
|
962
|
-
],
|
|
963
|
-
},
|
|
964
|
-
optionsBuilder: (c, v) => {
|
|
965
|
-
centerThumbPivot(v.object.thumb);
|
|
966
|
-
return {
|
|
967
|
-
bg: v.object.bg, fill: v.object.fill, slider: v.object.thumb,
|
|
968
|
-
min: c.min, max: c.max, step: c.step, value: c.value, showValue: c.showValue,
|
|
969
|
-
};
|
|
970
|
-
},
|
|
971
|
-
signalMap: (c) => ({
|
|
972
|
-
update: (vv) => { c.value = vv; c.updateCount += 1; return { value: vv }; },
|
|
973
|
-
change: (vv) => { c.value = vv; c.changeCount += 1; return { value: vv }; },
|
|
974
|
-
}),
|
|
975
|
-
syncOnChange: (inst, c) => {
|
|
976
|
-
if (inst.value !== c.value)
|
|
977
|
-
inst.value = c.value;
|
|
978
|
-
},
|
|
979
|
-
};
|
|
980
|
-
/**
|
|
981
|
-
* @pixi/ui Slider 的 update 逻辑:
|
|
982
|
-
* slider.x = (bg.width/100 * progress) - (slider.width / 2) // x 已居中
|
|
983
|
-
* slider.y = bg.height / 2 // y 未居中(bug)
|
|
984
|
-
*
|
|
985
|
-
* 因 y 维度 @pixi/ui 假设 thumb anchor / pivot 在中心(对 Sprite 自动 anchor.set(0.5),
|
|
986
|
-
* 但 Graphics 没 anchor),Graphics 渲染时左上角对齐 bg 中线,thumb 中心落到中线下方。
|
|
987
|
-
*
|
|
988
|
-
* 修法:只设 pivot.y = h/2 让 thumb 沿 y 居中。pivot.x 保持 0(x 在 update
|
|
989
|
-
* 里已用 -width/2 居中过,再设 pivot.x 会双倍偏移)。
|
|
990
|
-
*/
|
|
991
|
-
function centerThumbPivot(thumb) {
|
|
992
|
-
var _a, _b, _d, _e;
|
|
993
|
-
if (!thumb)
|
|
994
|
-
return;
|
|
995
|
-
if (thumb.anchor)
|
|
996
|
-
return; // Sprite — @pixi/ui 自己 anchor.set(0.5)
|
|
997
|
-
let h = 0;
|
|
998
|
-
try {
|
|
999
|
-
const b = (_a = thumb.getBounds) === null || _a === void 0 ? void 0 : _a.call(thumb);
|
|
1000
|
-
h = (_d = (_b = b === null || b === void 0 ? void 0 : b.height) !== null && _b !== void 0 ? _b : thumb.height) !== null && _d !== void 0 ? _d : 0;
|
|
1001
|
-
}
|
|
1002
|
-
catch (_) {
|
|
1003
|
-
h = (_e = thumb.height) !== null && _e !== void 0 ? _e : 0;
|
|
1004
|
-
}
|
|
1005
|
-
if (h > 0 && thumb.pivot) {
|
|
1006
|
-
thumb.pivot.set(0, h / 2);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
const DOUBLE_SLIDER_DEF = {
|
|
1010
|
-
name: 'DoubleSlider',
|
|
1011
|
-
signalPrefix: 'doubleslider',
|
|
1012
|
-
pixiClass: ui.DoubleSlider,
|
|
1013
|
-
fields: {
|
|
1014
|
-
enabled: ENABLED,
|
|
1015
|
-
value1: { kind: 'scalar', default: 0, inspector: { name: 'value1', type: 'number' } },
|
|
1016
|
-
value2: { kind: 'scalar', default: 100, inspector: { name: 'value2', type: 'number' } },
|
|
1017
|
-
min: { kind: 'scalar', default: 0, inspector: { name: 'min', type: 'number' } },
|
|
1018
|
-
max: { kind: 'scalar', default: 100, inspector: { name: 'max', type: 'number' } },
|
|
1019
|
-
step: { kind: 'scalar', default: 1 },
|
|
1020
|
-
showValue: { kind: 'scalar', default: false, inspector: { name: 'showValue', type: 'boolean' } },
|
|
1021
|
-
views: { kind: 'shallowMerge', default: {} },
|
|
1022
|
-
bindToStore1: { kind: 'scalar' },
|
|
1023
|
-
bindToStore2: { kind: 'scalar' },
|
|
1024
|
-
},
|
|
1025
|
-
views: {
|
|
1026
|
-
kind: 'object', folder: 'views',
|
|
1027
|
-
keys: [
|
|
1028
|
-
{ name: 'bg', required: true }, { name: 'fill', required: true },
|
|
1029
|
-
{ name: 'slider1', required: true }, { name: 'slider2', required: true },
|
|
1030
|
-
],
|
|
1031
|
-
},
|
|
1032
|
-
optionsBuilder: (c, v) => {
|
|
1033
|
-
centerThumbPivot(v.object.slider1);
|
|
1034
|
-
centerThumbPivot(v.object.slider2);
|
|
1035
|
-
return {
|
|
1036
|
-
bg: v.object.bg, fill: v.object.fill,
|
|
1037
|
-
slider1: v.object.slider1, slider2: v.object.slider2,
|
|
1038
|
-
min: c.min, max: c.max, value1: c.value1, value2: c.value2, showValue: c.showValue,
|
|
1039
|
-
};
|
|
1040
|
-
},
|
|
1041
|
-
signalMap: (c) => ({
|
|
1042
|
-
update: (v1, v2) => { c.value1 = v1; c.value2 = v2; c.updateCount += 1; return { value1: v1, value2: v2 }; },
|
|
1043
|
-
change: (v1, v2) => { c.value1 = v1; c.value2 = v2; c.changeCount += 1; return { value1: v1, value2: v2 }; },
|
|
1044
|
-
}),
|
|
1045
|
-
syncOnChange: (inst, c) => {
|
|
1046
|
-
if (inst.value1 !== c.value1)
|
|
1047
|
-
inst.value1 = c.value1;
|
|
1048
|
-
if (inst.value2 !== c.value2)
|
|
1049
|
-
inst.value2 = c.value2;
|
|
1050
|
-
},
|
|
1051
|
-
};
|
|
1052
|
-
const PROGRESS_BAR_DEF = {
|
|
1053
|
-
name: 'ProgressBar',
|
|
1054
|
-
signalPrefix: 'progressbar',
|
|
1055
|
-
pixiClass: ui.ProgressBar,
|
|
1056
|
-
fields: {
|
|
1057
|
-
value: { kind: 'scalar', default: 0, inspector: { name: 'value', type: 'number' } },
|
|
1058
|
-
valueRange: { kind: 'tuple', tupleLength: 2, default: [0, 100], inspector: { name: 'valueRange', type: 'number', isArray: true } },
|
|
1059
|
-
bgView: { kind: 'opaque' },
|
|
1060
|
-
fillView: { kind: 'opaque' },
|
|
1061
|
-
nineSliceSprite: { kind: 'opaque' },
|
|
1062
|
-
fillPaddings: { kind: 'shallowMerge', default: {} },
|
|
1063
|
-
bindToStore: BIND_STORE,
|
|
1064
|
-
},
|
|
1065
|
-
views: {
|
|
1066
|
-
kind: 'object', folder: '__progress_views__',
|
|
1067
|
-
keys: [], // 用空 keys 让 resolveViewsBySchema 跳过(下面用 customViewResolver 替代)
|
|
1068
|
-
},
|
|
1069
|
-
// 因为 ProgressBar 的 view ref 在两个独立字段(bgView/fillView),不在同一个 folder 对象里
|
|
1070
|
-
// 这里用 inline resolution(在 system.ts handleGeneric 里有 fast path 处理 views=undefined 的情形,
|
|
1071
|
-
// optionsBuilder 接受 raw component 自己 resolve)
|
|
1072
|
-
optionsBuilder: (c, _v) => ({
|
|
1073
|
-
bg: c.__resolved_bg, fill: c.__resolved_fill,
|
|
1074
|
-
fillPaddings: c.fillPaddings,
|
|
1075
|
-
progress: computeProgressPct(c),
|
|
1076
|
-
}),
|
|
1077
|
-
syncOnChange: (inst, c) => {
|
|
1078
|
-
const pct = computeProgressPct(c);
|
|
1079
|
-
if (inst.progress !== pct)
|
|
1080
|
-
inst.progress = pct;
|
|
1081
|
-
},
|
|
1082
|
-
mixin: { getProgressPct: getProgressPctMixin },
|
|
1083
|
-
};
|
|
1084
|
-
function computeProgressPct(c) {
|
|
1085
|
-
const [min, max] = c.valueRange;
|
|
1086
|
-
if (max <= min)
|
|
1087
|
-
return 0;
|
|
1088
|
-
const clamped = Math.max(min, Math.min(max, c.value));
|
|
1089
|
-
return ((clamped - min) / (max - min)) * 100;
|
|
1090
|
-
}
|
|
1091
|
-
const CIRCULAR_PROGRESS_BAR_DEF = {
|
|
1092
|
-
name: 'CircularProgressBar',
|
|
1093
|
-
signalPrefix: 'circularprogressbar',
|
|
1094
|
-
pixiClass: ui.CircularProgressBar,
|
|
1095
|
-
fields: {
|
|
1096
|
-
value: { kind: 'scalar', default: 0, inspector: { name: 'value', type: 'number' } },
|
|
1097
|
-
valueRange: { kind: 'tuple', tupleLength: 2, default: [0, 100], inspector: { name: 'valueRange', type: 'number', isArray: true } },
|
|
1098
|
-
radius: { kind: 'scalar', default: 60, inspector: { name: 'radius', type: 'number' } },
|
|
1099
|
-
lineWidth: { kind: 'scalar', default: 12, inspector: { name: 'lineWidth', type: 'number' } },
|
|
1100
|
-
startAngle: { kind: 'scalar', default: -90, inspector: { name: 'startAngle', type: 'number', step: 5 } },
|
|
1101
|
-
backgroundColor: { kind: 'scalar', default: '#1f2937', inspector: { name: 'backgroundColor', type: 'color' } },
|
|
1102
|
-
fillColor: { kind: 'scalar', default: '#22c55e', inspector: { name: 'fillColor', type: 'color' } },
|
|
1103
|
-
backgroundAlpha: { kind: 'scalar', default: 1, inspector: { name: 'backgroundAlpha', type: 'number', step: 0.05 } },
|
|
1104
|
-
fillAlpha: { kind: 'scalar', default: 1, inspector: { name: 'fillAlpha', type: 'number', step: 0.05 } },
|
|
1105
|
-
bindToStore: BIND_STORE,
|
|
1106
|
-
},
|
|
1107
|
-
optionsBuilder: (c) => ({
|
|
1108
|
-
radius: c.radius, lineWidth: c.lineWidth,
|
|
1109
|
-
backgroundColor: c.backgroundColor, fillColor: c.fillColor,
|
|
1110
|
-
backgroundAlpha: c.backgroundAlpha, fillAlpha: c.fillAlpha,
|
|
1111
|
-
value: computeProgressPct(c),
|
|
1112
|
-
}),
|
|
1113
|
-
syncOnChange: (inst, c) => {
|
|
1114
|
-
const pct = computeProgressPct(c);
|
|
1115
|
-
if (inst.progress !== pct)
|
|
1116
|
-
inst.progress = pct;
|
|
1117
|
-
},
|
|
1118
|
-
mixin: { getProgressPct: getProgressPctMixin },
|
|
1119
|
-
};
|
|
1120
|
-
const INPUT_DEF = {
|
|
1121
|
-
name: 'Input',
|
|
1122
|
-
signalPrefix: 'input',
|
|
1123
|
-
pixiClass: ui.Input,
|
|
1124
|
-
fields: {
|
|
1125
|
-
enabled: ENABLED,
|
|
1126
|
-
value: { kind: 'scalar', default: '', inspector: { name: 'value', type: 'string' } },
|
|
1127
|
-
placeholder: { kind: 'scalar', default: '', inspector: { name: 'placeholder', type: 'string' } },
|
|
1128
|
-
maxLength: { kind: 'scalar', default: 100, inspector: { name: 'maxLength', type: 'number' } },
|
|
1129
|
-
secure: { kind: 'scalar', default: false, inspector: { name: 'secure', type: 'boolean' } },
|
|
1130
|
-
align: { kind: 'scalar', default: 'left', inspector: { name: 'align', type: 'string' } },
|
|
1131
|
-
padding: { kind: 'tuple', tupleLength: 4, default: [8, 8, 8, 8] },
|
|
1132
|
-
textStyle: { kind: 'shallowMerge', default: { fontFamily: 'Arial', fontSize: 16, fill: '#222' } },
|
|
1133
|
-
bgView: { kind: 'opaque' },
|
|
1134
|
-
nineSliceSprite: { kind: 'opaque' },
|
|
1135
|
-
cleanOnFocus: { kind: 'scalar', default: false, inspector: { name: 'cleanOnFocus', type: 'boolean' } },
|
|
1136
|
-
addMask: { kind: 'scalar', default: false, inspector: { name: 'addMask', type: 'boolean' } },
|
|
1137
|
-
bindToStore: BIND_STORE,
|
|
1138
|
-
},
|
|
1139
|
-
views: { kind: 'single', key: 'bgView', required: true },
|
|
1140
|
-
optionsBuilder: (c, v) => ({
|
|
1141
|
-
bg: v.single, textStyle: c.textStyle,
|
|
1142
|
-
placeholder: c.placeholder, value: c.value, maxLength: c.maxLength, secure: c.secure,
|
|
1143
|
-
align: c.align, padding: c.padding,
|
|
1144
|
-
cleanOnFocus: c.cleanOnFocus, nineSliceSprite: c.nineSliceSprite, addMask: c.addMask,
|
|
1145
|
-
}),
|
|
1146
|
-
signalMap: (c) => ({
|
|
1147
|
-
change: (text) => { c.value = text; c.changeCount += 1; return { value: text }; },
|
|
1148
|
-
enter: (text) => ({ value: text }),
|
|
1149
|
-
}),
|
|
1150
|
-
syncOnChange: (inst, c) => {
|
|
1151
|
-
if (inst.value !== c.value)
|
|
1152
|
-
inst.value = c.value;
|
|
1153
|
-
},
|
|
1154
|
-
};
|
|
1155
|
-
const LIST_DEF = {
|
|
1156
|
-
name: 'List',
|
|
1157
|
-
signalPrefix: 'list',
|
|
1158
|
-
pixiClass: ui.List,
|
|
1159
|
-
fields: {
|
|
1160
|
-
type: { kind: 'scalar', default: 'vertical', inspector: { name: 'type', type: 'string' } },
|
|
1161
|
-
elementsMargin: { kind: 'scalar', default: 0, inspector: { name: 'elementsMargin', type: 'number' } },
|
|
1162
|
-
padding: { kind: 'scalar' },
|
|
1163
|
-
vertPadding: { kind: 'scalar' },
|
|
1164
|
-
horPadding: { kind: 'scalar' },
|
|
1165
|
-
topPadding: { kind: 'scalar' },
|
|
1166
|
-
bottomPadding: { kind: 'scalar' },
|
|
1167
|
-
leftPadding: { kind: 'scalar' },
|
|
1168
|
-
rightPadding: { kind: 'scalar' },
|
|
1169
|
-
maxWidth: { kind: 'scalar' },
|
|
1170
|
-
maxHeight: { kind: 'scalar' },
|
|
1171
|
-
itemsChildName: { kind: 'scalar', default: 'items' },
|
|
1172
|
-
},
|
|
1173
|
-
// List 不走 ViewSchema,view 是 collectChildContainers 副作用,system.ts 处理
|
|
1174
|
-
optionsBuilder: (c, _v) => {
|
|
1175
|
-
var _a;
|
|
1176
|
-
return ({
|
|
1177
|
-
type: c.type,
|
|
1178
|
-
elementsMargin: c.elementsMargin,
|
|
1179
|
-
padding: c.padding,
|
|
1180
|
-
vertPadding: c.vertPadding, horPadding: c.horPadding,
|
|
1181
|
-
topPadding: c.topPadding, bottomPadding: c.bottomPadding,
|
|
1182
|
-
leftPadding: c.leftPadding, rightPadding: c.rightPadding,
|
|
1183
|
-
maxWidth: c.maxWidth, maxHeight: c.maxHeight,
|
|
1184
|
-
children: (_a = c.__resolved_items) !== null && _a !== void 0 ? _a : [],
|
|
1185
|
-
});
|
|
1186
|
-
},
|
|
1187
|
-
syncOnChange: (inst, c) => {
|
|
1188
|
-
if (inst.type !== c.type)
|
|
1189
|
-
inst.type = c.type;
|
|
1190
|
-
if (inst.elementsMargin !== c.elementsMargin)
|
|
1191
|
-
inst.elementsMargin = c.elementsMargin;
|
|
1192
|
-
if (typeof inst.arrangeChildren === 'function')
|
|
1193
|
-
inst.arrangeChildren();
|
|
1194
|
-
},
|
|
1195
|
-
};
|
|
1196
|
-
const SCROLL_BOX_DEF = {
|
|
1197
|
-
name: 'ScrollBox',
|
|
1198
|
-
signalPrefix: 'scrollbox',
|
|
1199
|
-
pixiClass: ui.ScrollBox,
|
|
1200
|
-
fields: {
|
|
1201
|
-
width: { kind: 'scalar', default: 320, inspector: { name: 'width', type: 'number' } },
|
|
1202
|
-
height: { kind: 'scalar', default: 240, inspector: { name: 'height', type: 'number' } },
|
|
1203
|
-
direction: { kind: 'scalar', default: 'vertical', inspector: { name: 'direction', type: 'string' } },
|
|
1204
|
-
contentChildName: { kind: 'scalar', default: 'content' },
|
|
1205
|
-
background: { kind: 'scalar', default: 0x111111, inspector: { name: 'background', type: 'color' } },
|
|
1206
|
-
radius: { kind: 'scalar', default: 0, inspector: { name: 'radius', type: 'number' } },
|
|
1207
|
-
elementsMargin: { kind: 'scalar', default: 0 },
|
|
1208
|
-
disableDynamicRendering: { kind: 'scalar', default: false },
|
|
1209
|
-
disableEasing: { kind: 'scalar', default: false, inspector: { name: 'disableEasing', type: 'boolean' } },
|
|
1210
|
-
padding: { kind: 'scalar', default: 0 },
|
|
1211
|
-
// legacy fields(spec 兼容)
|
|
1212
|
-
inertia: { kind: 'scalar', default: true },
|
|
1213
|
-
inertiaDecay: { kind: 'scalar', default: 0.92 },
|
|
1214
|
-
bounceFactor: { kind: 'scalar', default: 0.18 },
|
|
1215
|
-
},
|
|
1216
|
-
// legacy spec hooks(applyOffset / getBounds 默认空实现 in mixin)
|
|
1217
|
-
mixin: {
|
|
1218
|
-
applyOffset() { },
|
|
1219
|
-
getBounds() { return { minX: 0, maxX: 0, minY: 0, maxY: 0 }; },
|
|
1220
|
-
},
|
|
1221
|
-
optionsBuilder: (c, _v) => {
|
|
1222
|
-
var _a, _b;
|
|
1223
|
-
const typeMap = {
|
|
1224
|
-
horizontal: 'horizontal', vertical: 'vertical', both: 'bidirectional',
|
|
1225
|
-
};
|
|
1226
|
-
return {
|
|
1227
|
-
width: c.width, height: c.height,
|
|
1228
|
-
type: (_a = typeMap[c.direction]) !== null && _a !== void 0 ? _a : 'vertical',
|
|
1229
|
-
background: c.background, radius: c.radius,
|
|
1230
|
-
elementsMargin: c.elementsMargin, padding: c.padding,
|
|
1231
|
-
disableEasing: c.disableEasing, disableDynamicRendering: c.disableDynamicRendering,
|
|
1232
|
-
items: (_b = c.__resolved_items) !== null && _b !== void 0 ? _b : [],
|
|
1233
|
-
};
|
|
1234
|
-
},
|
|
1235
|
-
signalMap: () => ({
|
|
1236
|
-
scroll: (v) => ({ value: v }),
|
|
1237
|
-
}),
|
|
1238
|
-
syncOnChange: (inst, c) => {
|
|
1239
|
-
if (typeof inst.resize === 'function') {
|
|
1240
|
-
try {
|
|
1241
|
-
inst.resize(c.width, c.height);
|
|
1242
|
-
}
|
|
1243
|
-
catch (_) { }
|
|
1244
|
-
}
|
|
1245
|
-
},
|
|
1246
|
-
};
|
|
1247
|
-
const SELECT_DEF = {
|
|
1248
|
-
name: 'Select',
|
|
1249
|
-
signalPrefix: 'select',
|
|
1250
|
-
pixiClass: ui.Select,
|
|
1251
|
-
fields: {
|
|
1252
|
-
items: { kind: 'arrayCopy', default: [] },
|
|
1253
|
-
selectedIndex: { kind: 'scalar', default: -1, inspector: { name: 'selectedIndex', type: 'number' } },
|
|
1254
|
-
placeholder: { kind: 'scalar', default: '', inspector: { name: 'placeholder', type: 'string' } },
|
|
1255
|
-
closedView: { kind: 'opaque' },
|
|
1256
|
-
openView: { kind: 'opaque' },
|
|
1257
|
-
textStyle: { kind: 'shallowMerge', default: { fontFamily: 'Arial', fontSize: 14, fill: '#222' } },
|
|
1258
|
-
nineSliceSprite: { kind: 'opaque' },
|
|
1259
|
-
bindToStore: BIND_STORE,
|
|
1260
|
-
},
|
|
1261
|
-
views: {
|
|
1262
|
-
kind: 'object', folder: '__select_views__', keys: [],
|
|
1263
|
-
},
|
|
1264
|
-
optionsBuilder: (c) => {
|
|
1265
|
-
var _a;
|
|
1266
|
-
return ({
|
|
1267
|
-
closedBG: c.__resolved_closedView,
|
|
1268
|
-
openBG: c.__resolved_openView,
|
|
1269
|
-
textStyle: c.textStyle,
|
|
1270
|
-
items: {
|
|
1271
|
-
items: ((_a = c.items) !== null && _a !== void 0 ? _a : []).map((it) => it.text),
|
|
1272
|
-
backgroundColor: 0x000000, hoverColor: 0x666666,
|
|
1273
|
-
width: 200, height: 30, textStyle: c.textStyle, radius: 4,
|
|
1274
|
-
},
|
|
1275
|
-
selected: c.selectedIndex >= 0 ? c.selectedIndex : undefined,
|
|
1276
|
-
nineSliceSprite: c.nineSliceSprite,
|
|
1277
|
-
});
|
|
1278
|
-
},
|
|
1279
|
-
signalMap: (c) => ({
|
|
1280
|
-
select: (value, text) => {
|
|
1281
|
-
c.selectedIndex = value;
|
|
1282
|
-
c.selectCount += 1;
|
|
1283
|
-
return { index: value, text };
|
|
1284
|
-
},
|
|
1285
|
-
}),
|
|
1286
|
-
};
|
|
1287
|
-
const DIALOG_DEF = {
|
|
1288
|
-
name: 'Dialog',
|
|
1289
|
-
signalPrefix: 'dialog',
|
|
1290
|
-
pixiClass: ui.Dialog,
|
|
1291
|
-
fields: {
|
|
1292
|
-
open: { kind: 'scalar', default: false, inspector: { name: 'open', type: 'boolean' } },
|
|
1293
|
-
title: { kind: 'scalar', inspector: { name: 'title', type: 'string' } },
|
|
1294
|
-
width: { kind: 'scalar', inspector: { name: 'width', type: 'number' } },
|
|
1295
|
-
height: { kind: 'scalar', inspector: { name: 'height', type: 'number' } },
|
|
1296
|
-
padding: { kind: 'scalar', inspector: { name: 'padding', type: 'number' } },
|
|
1297
|
-
backdropColor: { kind: 'scalar', default: 0x000000, inspector: { name: 'backdropColor', type: 'color' } },
|
|
1298
|
-
backdropAlpha: { kind: 'scalar', default: 0.5, inspector: { name: 'backdropAlpha', type: 'number', step: 0.05 } },
|
|
1299
|
-
backdropView: { kind: 'opaque' },
|
|
1300
|
-
backgroundView: { kind: 'opaque' },
|
|
1301
|
-
closeOnBackdropClick: { kind: 'scalar', default: true, inspector: { name: 'closeOnBackdropClick', type: 'boolean' } },
|
|
1302
|
-
contentChildName: { kind: 'scalar', default: 'content', inspector: { name: 'contentChildName', type: 'string' } },
|
|
1303
|
-
nineSliceSprite: { kind: 'opaque' },
|
|
1304
|
-
bindToStore: BIND_STORE,
|
|
1305
|
-
},
|
|
1306
|
-
optionsBuilder: (c) => {
|
|
1307
|
-
var _a;
|
|
1308
|
-
return ({
|
|
1309
|
-
backdrop: c.__resolved_backdropView,
|
|
1310
|
-
backdropColor: backdropColorToNumber(c.backdropColor),
|
|
1311
|
-
backdropAlpha: c.backdropAlpha,
|
|
1312
|
-
background: (_a = c.__resolved_backgroundView) !== null && _a !== void 0 ? _a : new pixi_js.Container(),
|
|
1313
|
-
title: c.title,
|
|
1314
|
-
width: c.width, height: c.height, padding: c.padding,
|
|
1315
|
-
closeOnBackdropClick: c.closeOnBackdropClick,
|
|
1316
|
-
nineSliceSprite: c.nineSliceSprite,
|
|
1317
|
-
});
|
|
1318
|
-
},
|
|
1319
|
-
postCreate: (inst, c) => {
|
|
1320
|
-
if (c.open && typeof inst.open === 'function')
|
|
1321
|
-
inst.open();
|
|
1322
|
-
},
|
|
1323
|
-
signalMap: (c) => ({
|
|
1324
|
-
close: () => { c.open = false; return {}; },
|
|
1325
|
-
select: (idx, text) => ({ index: idx, text }),
|
|
1326
|
-
}),
|
|
1327
|
-
syncOnChange: (inst, c) => {
|
|
1328
|
-
if (c.open && !inst.isOpen && typeof inst.open === 'function')
|
|
1329
|
-
inst.open();
|
|
1330
|
-
else if (!c.open && inst.isOpen && typeof inst.close === 'function')
|
|
1331
|
-
inst.close();
|
|
1332
|
-
},
|
|
1333
|
-
};
|
|
1334
|
-
const MASKED_FRAME_DEF = {
|
|
1335
|
-
name: 'MaskedFrame',
|
|
1336
|
-
signalPrefix: 'maskedframe',
|
|
1337
|
-
pixiClass: ui.MaskedFrame,
|
|
1338
|
-
fields: {
|
|
1339
|
-
targetView: { kind: 'opaque' },
|
|
1340
|
-
maskView: { kind: 'opaque' },
|
|
1341
|
-
borderView: { kind: 'opaque' },
|
|
1342
|
-
},
|
|
1343
|
-
optionsBuilder: (c) => ({
|
|
1344
|
-
target: c.__resolved_targetView,
|
|
1345
|
-
mask: c.__resolved_maskView,
|
|
1346
|
-
borderWidth: 0,
|
|
1347
|
-
borderColor: 0,
|
|
1348
|
-
}),
|
|
1349
|
-
};
|
|
1350
|
-
// ============================================================
|
|
1351
|
-
// Generate 14 Component classes
|
|
1352
|
-
// ============================================================
|
|
1353
|
-
const Button = defineUiComponent(BUTTON_DEF);
|
|
1354
|
-
const FancyButton = defineUiComponent(FANCY_BUTTON_DEF);
|
|
1355
|
-
const CheckBox = defineUiComponent(CHECKBOX_DEF);
|
|
1356
|
-
const Switcher = defineUiComponent(SWITCHER_DEF);
|
|
1357
|
-
const Slider = defineUiComponent(SLIDER_DEF);
|
|
1358
|
-
const DoubleSlider = defineUiComponent(DOUBLE_SLIDER_DEF);
|
|
1359
|
-
const ProgressBar = defineUiComponent(PROGRESS_BAR_DEF);
|
|
1360
|
-
const CircularProgressBar = defineUiComponent(CIRCULAR_PROGRESS_BAR_DEF);
|
|
1361
|
-
const Input = defineUiComponent(INPUT_DEF);
|
|
1362
|
-
const List = defineUiComponent(LIST_DEF);
|
|
1363
|
-
const ScrollBox = defineUiComponent(SCROLL_BOX_DEF);
|
|
1364
|
-
const Select = defineUiComponent(SELECT_DEF);
|
|
1365
|
-
const Dialog = defineUiComponent(DIALOG_DEF);
|
|
1366
|
-
const MaskedFrame = defineUiComponent(MASKED_FRAME_DEF);
|
|
1367
|
-
// 暴露 metadata 表给 system.ts(generic handler 路由)
|
|
1368
|
-
const COMPONENT_DEFINITIONS = {
|
|
1369
|
-
Button: BUTTON_DEF,
|
|
1370
|
-
FancyButton: FANCY_BUTTON_DEF,
|
|
1371
|
-
CheckBox: CHECKBOX_DEF,
|
|
1372
|
-
Switcher: SWITCHER_DEF,
|
|
1373
|
-
Slider: SLIDER_DEF,
|
|
1374
|
-
DoubleSlider: DOUBLE_SLIDER_DEF,
|
|
1375
|
-
ProgressBar: PROGRESS_BAR_DEF,
|
|
1376
|
-
CircularProgressBar: CIRCULAR_PROGRESS_BAR_DEF,
|
|
1377
|
-
Input: INPUT_DEF,
|
|
1378
|
-
List: LIST_DEF,
|
|
1379
|
-
ScrollBox: SCROLL_BOX_DEF,
|
|
1380
|
-
Select: SELECT_DEF,
|
|
1381
|
-
Dialog: DIALOG_DEF,
|
|
1382
|
-
MaskedFrame: MASKED_FRAME_DEF,
|
|
1383
|
-
};
|
|
1384
|
-
|
|
1385
|
-
/**
|
|
1386
|
-
* RadioGroup — 独立保留(不进入 components.ts factory)。
|
|
1387
|
-
*
|
|
1388
|
-
* 不能塌缩的原因:
|
|
1389
|
-
* 1. boundCheckBoxes 是 runtime collection,System 在 ADD 阶段 queueMicrotask 后填充
|
|
1390
|
-
* 2. applySelection() 反向遍历同步 child CheckBox.checked,跨实体协调
|
|
1391
|
-
* 3. system.ts handleRadioGroup 必须从 checkBoxInstances map 捞已构造的 PixiCheckBox 实例
|
|
1392
|
-
*/
|
|
1393
|
-
class RadioGroup extends eva_js.Component {
|
|
1394
|
-
constructor() {
|
|
1395
|
-
super(...arguments);
|
|
1396
|
-
this.componentName = 'RadioGroup';
|
|
1397
|
-
this.direction = 'vertical';
|
|
1398
|
-
this.elementsMargin = 4;
|
|
1399
|
-
/** runtime: 已绑定的子 CheckBox(System 填充) */
|
|
1400
|
-
this.boundCheckBoxes = [];
|
|
1401
|
-
}
|
|
1402
|
-
static getInspectorMetadata() {
|
|
1403
|
-
return {
|
|
1404
|
-
name: RadioGroup.componentName, type: 'object', isArray: false, isFolder: true,
|
|
1405
|
-
children: [
|
|
1406
|
-
{ name: 'selectedId', type: 'string', isArray: false },
|
|
1407
|
-
{ name: 'direction', type: 'string', isArray: false },
|
|
1408
|
-
{ name: 'elementsMargin', type: 'number', isArray: false },
|
|
1409
|
-
{ name: 'childNames', type: 'string', isArray: true },
|
|
1410
|
-
],
|
|
1411
|
-
};
|
|
1412
|
-
}
|
|
1413
|
-
init(p) { if (p)
|
|
1414
|
-
this.applyParams(p); }
|
|
1415
|
-
applyDeclarativeProps(next, _prev) { this.applyParams(next); }
|
|
1416
|
-
applySelection() {
|
|
1417
|
-
for (const { name, cb } of this.boundCheckBoxes) {
|
|
1418
|
-
const isSelected = name === this.selectedId;
|
|
1419
|
-
if (cb.checked !== isSelected)
|
|
1420
|
-
cb.checked = isSelected;
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
|
-
applyParams(p) {
|
|
1424
|
-
if (typeof p.selectedId === 'string')
|
|
1425
|
-
this.selectedId = p.selectedId;
|
|
1426
|
-
if (Array.isArray(p.childNames))
|
|
1427
|
-
this.childNames = p.childNames.slice();
|
|
1428
|
-
if (p.direction)
|
|
1429
|
-
this.direction = p.direction;
|
|
1430
|
-
if (typeof p.elementsMargin === 'number')
|
|
1431
|
-
this.elementsMargin = p.elementsMargin;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
RadioGroup.componentName = 'RadioGroup';
|
|
1435
|
-
|
|
1436
|
-
/******************************************************************************
|
|
1437
|
-
Copyright (c) Microsoft Corporation.
|
|
1438
|
-
|
|
1439
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
1440
|
-
purpose with or without fee is hereby granted.
|
|
1441
|
-
|
|
1442
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
1443
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
1444
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
1445
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
1446
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
1447
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
1448
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
1449
|
-
***************************************************************************** */
|
|
1450
|
-
|
|
1451
|
-
function __decorate(decorators, target, key, desc) {
|
|
1452
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1453
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1454
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1455
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
1459
|
-
var e = new Error(message);
|
|
1460
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
1461
|
-
};
|
|
1462
|
-
|
|
1463
|
-
/**
|
|
1464
|
-
* 把 @pixi/ui 实例的多个 Signal 桥接到 Eva 事件系统。
|
|
1465
|
-
*
|
|
1466
|
-
* 用法:
|
|
1467
|
-
* const offs = bridgeSignals(instance, { go, prefix: 'fancybutton' }, {
|
|
1468
|
-
* press: (e) => ({ pointerId: e?.pointerId }),
|
|
1469
|
-
* hover: () => ({}),
|
|
1470
|
-
* });
|
|
1471
|
-
* // REMOVE 时 offs.forEach(o => o())
|
|
1472
|
-
*/
|
|
1473
|
-
function bridgeSignals(instance, options, signalMap) {
|
|
1474
|
-
const offs = [];
|
|
1475
|
-
const { go, prefix, emitToMx = true } = options;
|
|
1476
|
-
for (const [signalName, projector] of Object.entries(signalMap)) {
|
|
1477
|
-
const signal = pickSignal(instance, signalName);
|
|
1478
|
-
if (!signal)
|
|
1479
|
-
continue;
|
|
1480
|
-
const conn = signal.connect((...args) => {
|
|
1481
|
-
var _a, _b, _c, _d, _e;
|
|
1482
|
-
const payloadFromArgs = projector === true ? args[0] : safeProject(projector, args);
|
|
1483
|
-
const payload = typeof payloadFromArgs === 'object' && payloadFromArgs !== null
|
|
1484
|
-
? Object.assign({ entityName: go.name }, payloadFromArgs) : { entityName: go.name, value: payloadFromArgs };
|
|
1485
|
-
// 1) GameObject local emit
|
|
1486
|
-
try {
|
|
1487
|
-
(_b = (_a = go).emit) === null || _b === void 0 ? void 0 : _b.call(_a, signalName, payload);
|
|
1488
|
-
}
|
|
1489
|
-
catch (_) { }
|
|
1490
|
-
// 2) component-prefixed local emit (`fancybutton:press`),与 Eva 现有惯例一致
|
|
1491
|
-
try {
|
|
1492
|
-
(_d = (_c = go).emit) === null || _d === void 0 ? void 0 : _d.call(_c, `${prefix}:${signalName}`, payload);
|
|
1493
|
-
}
|
|
1494
|
-
catch (_) { }
|
|
1495
|
-
// 3) global mx bus
|
|
1496
|
-
if (emitToMx) {
|
|
1497
|
-
const mx = (typeof globalThis !== 'undefined' ? globalThis.mx : undefined);
|
|
1498
|
-
if ((_e = mx === null || mx === void 0 ? void 0 : mx.event) === null || _e === void 0 ? void 0 : _e.emit) {
|
|
1499
|
-
try {
|
|
1500
|
-
mx.event.emit(`${prefix}:${signalName}`, payload);
|
|
1501
|
-
}
|
|
1502
|
-
catch (_) { }
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
1506
|
-
offs.push(() => { try {
|
|
1507
|
-
conn.disconnect();
|
|
1508
|
-
}
|
|
1509
|
-
catch (_) { } });
|
|
1510
|
-
}
|
|
1511
|
-
return offs;
|
|
1512
|
-
}
|
|
1513
|
-
/**
|
|
1514
|
-
* @pixi/ui Signal 命名:onPress / onUp / onDown / onHover / onChange / onSelect / onCheck / onUpdate / onScroll / onClose / onProximityChange
|
|
1515
|
-
*
|
|
1516
|
-
* 我们 signalMap 的 key 用语义动词(press/change/select/...),通过加 `on` 前缀
|
|
1517
|
-
* 与首字母大写映射到实际属性名。
|
|
1518
|
-
*/
|
|
1519
|
-
function pickSignal(instance, signalName) {
|
|
1520
|
-
const candidates = [
|
|
1521
|
-
`on${signalName.charAt(0).toUpperCase()}${signalName.slice(1)}`,
|
|
1522
|
-
`on${signalName.charAt(0).toUpperCase()}${signalName.slice(1)}Change`, // proximity -> onProximityChange
|
|
1523
|
-
];
|
|
1524
|
-
for (const cand of candidates) {
|
|
1525
|
-
const sig = instance === null || instance === void 0 ? void 0 : instance[cand];
|
|
1526
|
-
if (sig && typeof sig.connect === 'function')
|
|
1527
|
-
return sig;
|
|
1528
|
-
}
|
|
1529
|
-
return null;
|
|
1530
|
-
}
|
|
1531
|
-
function safeProject(projector, args) {
|
|
1532
|
-
var _a;
|
|
1533
|
-
try {
|
|
1534
|
-
return (_a = projector(...args)) !== null && _a !== void 0 ? _a : {};
|
|
1535
|
-
}
|
|
1536
|
-
catch (_) {
|
|
1537
|
-
return {};
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
/**
|
|
1542
|
-
* UISystem - generic handler driven by COMPONENT_DEFINITIONS metadata.
|
|
1543
|
-
*
|
|
1544
|
-
* 16 个 ECS 组件(UI 自渲染 + 14 个 @pixi/ui factory wrapper + RadioGroup 独立)由本 system 统一驱动。
|
|
1545
|
-
* 不再为每个组件写独立 handler 方法 - 14 个 @pixi/ui 组件走 handleGeneric,RadioGroup 单独处理。
|
|
1546
|
-
*/
|
|
1547
|
-
// observer schema:把 COMPONENT_DEFINITIONS.fields 转成 componentObserver decorator 参数
|
|
1548
|
-
function buildObserverSchema() {
|
|
1549
|
-
const out = {
|
|
1550
|
-
UI: [{ prop: ['shapes'], deep: true }],
|
|
1551
|
-
RadioGroup: [
|
|
1552
|
-
{ prop: ['selectedId'], deep: false },
|
|
1553
|
-
{ prop: ['direction'], deep: false },
|
|
1554
|
-
{ prop: ['elementsMargin'], deep: false },
|
|
1555
|
-
],
|
|
1556
|
-
};
|
|
1557
|
-
for (const [name, def] of Object.entries(COMPONENT_DEFINITIONS)) {
|
|
1558
|
-
const props = [];
|
|
1559
|
-
for (const [field, spec] of Object.entries(def.fields)) {
|
|
1560
|
-
const deep = spec.kind === 'shallowMerge' || spec.kind === 'tuple' || spec.kind === 'arrayCopy';
|
|
1561
|
-
props.push({ prop: [field], deep });
|
|
1562
|
-
}
|
|
1563
|
-
out[name] = props;
|
|
1564
|
-
}
|
|
1565
|
-
return out;
|
|
1566
|
-
}
|
|
1567
|
-
let UISystem = class UISystem extends eva_js.System {
|
|
1568
|
-
constructor() {
|
|
1569
|
-
super(...arguments);
|
|
1570
|
-
this.name = 'UISystem';
|
|
1571
|
-
/** componentName -> instance map(generic 共享一份) */
|
|
1572
|
-
this.instances = new Map();
|
|
1573
|
-
this.signalCleanups = new Map();
|
|
1574
|
-
this.radioGroupInstances = new Map();
|
|
1575
|
-
}
|
|
1576
|
-
/** 给 RadioGroup 反查 PixiCheckBox 实例用 */
|
|
1577
|
-
get checkBoxInstances() {
|
|
1578
|
-
let m = this.instances.get('CheckBox');
|
|
1579
|
-
if (!m) {
|
|
1580
|
-
m = new Map();
|
|
1581
|
-
this.instances.set('CheckBox', m);
|
|
1582
|
-
}
|
|
1583
|
-
return m;
|
|
1584
|
-
}
|
|
1585
|
-
// 每帧:把 componentObserver 队列里的事件派发到 componentChanged。
|
|
1586
|
-
// System 基类没默认实现 update,@eva/plugin-renderer 的 Renderer 基类才有,
|
|
1587
|
-
// 这里 UISystem 直接 extends System,必须自己实现这个 dispatch 循环。
|
|
1588
|
-
update(_e) {
|
|
1589
|
-
var _a, _b, _c;
|
|
1590
|
-
const changes = (_c = (_b = (_a = this.componentObserver) === null || _a === void 0 ? void 0 : _a.clear) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : [];
|
|
1591
|
-
for (const changed of changes) {
|
|
1592
|
-
this.componentChanged(changed);
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
componentChanged(changed) {
|
|
1596
|
-
const name = changed.componentName;
|
|
1597
|
-
if (name === 'UI')
|
|
1598
|
-
return this.handleUI(changed);
|
|
1599
|
-
if (name === 'RadioGroup')
|
|
1600
|
-
return this.handleRadioGroup(changed);
|
|
1601
|
-
const def = COMPONENT_DEFINITIONS[name];
|
|
1602
|
-
if (def)
|
|
1603
|
-
return this.handleGeneric(def, changed);
|
|
1604
|
-
}
|
|
1605
|
-
// ============================================================
|
|
1606
|
-
// UI(自渲染,无 @pixi/ui 实例)
|
|
1607
|
-
// ============================================================
|
|
1608
|
-
handleUI(changed) {
|
|
1609
|
-
if (changed.type === eva_js.OBSERVER_TYPE.ADD || changed.type === eva_js.OBSERVER_TYPE.CHANGE) {
|
|
1610
|
-
const ui = changed.component;
|
|
1611
|
-
if (typeof ui.redraw === 'function')
|
|
1612
|
-
ui.redraw();
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
// ============================================================
|
|
1616
|
-
// Generic handler(14 个 @pixi/ui wrapper)
|
|
1617
|
-
// ============================================================
|
|
1618
|
-
handleGeneric(def, changed) {
|
|
1619
|
-
var _a;
|
|
1620
|
-
const c = changed.component;
|
|
1621
|
-
const go = c.gameObject;
|
|
1622
|
-
if (!go)
|
|
1623
|
-
return;
|
|
1624
|
-
const instMap = this.getInstanceMap(def.name);
|
|
1625
|
-
if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
|
|
1626
|
-
whenContainerReady(this.gameRef(go), go, () => this.attachGeneric(def, c, go, instMap));
|
|
1627
|
-
}
|
|
1628
|
-
else if (changed.type === eva_js.OBSERVER_TYPE.CHANGE) {
|
|
1629
|
-
const inst = instMap.get(go.id);
|
|
1630
|
-
if (!inst) {
|
|
1631
|
-
// view 还没就绪导致 attach 失败,等下一帧重试
|
|
1632
|
-
whenContainerReady(this.gameRef(go), go, () => this.attachGeneric(def, c, go, instMap));
|
|
1633
|
-
return;
|
|
1634
|
-
}
|
|
1635
|
-
(_a = def.syncOnChange) === null || _a === void 0 ? void 0 : _a.call(def, inst, c);
|
|
1636
|
-
}
|
|
1637
|
-
else if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
|
|
1638
|
-
this.detachGeneric(go.id, instMap, go);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
attachGeneric(def, c, go, instMap) {
|
|
1642
|
-
var _a, _b, _c, _d, _f, _g, _h;
|
|
1643
|
-
if (instMap.has(go.id))
|
|
1644
|
-
return;
|
|
1645
|
-
const game = this.gameRef(go);
|
|
1646
|
-
// 1) resolve views
|
|
1647
|
-
const views = resolveViewsBySchema(game, go, c, def.views);
|
|
1648
|
-
if (def.views && views === null)
|
|
1649
|
-
return; // 必填 view 缺失 → abort
|
|
1650
|
-
// 2) 特殊组件 inline view resolve(ProgressBar / Select / Dialog / MaskedFrame)
|
|
1651
|
-
// 它们的 view ref 不在统一 folder,通过自定义字段名,这里给 wrapper component 挂 __resolved_*
|
|
1652
|
-
inlineResolveSpecialViews(def.name, c, game, go);
|
|
1653
|
-
if (!validateInlineResolved(def.name, c))
|
|
1654
|
-
return;
|
|
1655
|
-
// 3) collectChildContainers(List / ScrollBox)— 把子 entity 的 PIXI Container 收集为 items
|
|
1656
|
-
if (def.name === 'List' || def.name === 'ScrollBox') {
|
|
1657
|
-
const childName = def.name === 'List' ? c.itemsChildName : c.contentChildName;
|
|
1658
|
-
c.__resolved_items = collectChildContainers(game, go, childName);
|
|
1659
|
-
}
|
|
1660
|
-
// 4) 构造 PIXI 实例
|
|
1661
|
-
const built = def.optionsBuilder(c, views !== null && views !== void 0 ? views : {});
|
|
1662
|
-
const inst = def.positional
|
|
1663
|
-
? new def.pixiClass(...built)
|
|
1664
|
-
: new def.pixiClass(built);
|
|
1665
|
-
// 5) postCreate(enabled / selected tint 等)
|
|
1666
|
-
(_a = def.postCreate) === null || _a === void 0 ? void 0 : _a.call(def, inst, c);
|
|
1667
|
-
// 6) 注册 + attach
|
|
1668
|
-
instMap.set(go.id, inst);
|
|
1669
|
-
attachToGameObject(game, go, inst);
|
|
1670
|
-
// 7) onAttachedExtra(Dialog 挂 contentChild)
|
|
1671
|
-
if (def.name === 'Dialog') {
|
|
1672
|
-
const contentChild = findChildEntity(go, (_b = c.contentChildName) !== null && _b !== void 0 ? _b : 'content');
|
|
1673
|
-
if (contentChild) {
|
|
1674
|
-
const cc = getEvaContainer(game, contentChild);
|
|
1675
|
-
if (cc) {
|
|
1676
|
-
try {
|
|
1677
|
-
(_d = (_c = inst).addChild) === null || _d === void 0 ? void 0 : _d.call(_c, cc);
|
|
1678
|
-
}
|
|
1679
|
-
catch (_) { }
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
// MaskedFrame 挂 borderView 作为 child
|
|
1684
|
-
if (def.name === 'MaskedFrame' && c.borderView) {
|
|
1685
|
-
const border = resolveViewRef(game, go, c.borderView);
|
|
1686
|
-
if (border) {
|
|
1687
|
-
try {
|
|
1688
|
-
(_g = (_f = inst).addChild) === null || _g === void 0 ? void 0 : _g.call(_f, border);
|
|
1689
|
-
}
|
|
1690
|
-
catch (_) { }
|
|
1691
|
-
}
|
|
1692
|
-
}
|
|
1693
|
-
// 8) signal 桥接
|
|
1694
|
-
if (def.signalMap) {
|
|
1695
|
-
const offs = bridgeSignals(inst, { go, prefix: def.signalPrefix }, def.signalMap(c));
|
|
1696
|
-
this.signalCleanups.set(go.id, offs);
|
|
1697
|
-
}
|
|
1698
|
-
(_h = def.onAttachedExtra) === null || _h === void 0 ? void 0 : _h.call(def, inst, c, go, game);
|
|
1699
|
-
}
|
|
1700
|
-
detachGeneric(id, instMap, go) {
|
|
1701
|
-
const inst = instMap.get(id);
|
|
1702
|
-
detachFromGameObject(this.gameRef(go), go, inst);
|
|
1703
|
-
instMap.delete(id);
|
|
1704
|
-
const offs = this.signalCleanups.get(id);
|
|
1705
|
-
if (offs) {
|
|
1706
|
-
offs.forEach((o) => o());
|
|
1707
|
-
this.signalCleanups.delete(id);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
// ============================================================
|
|
1711
|
-
// RadioGroup(独立 handler — 跨实体协调)
|
|
1712
|
-
// ============================================================
|
|
1713
|
-
handleRadioGroup(changed) {
|
|
1714
|
-
const c = changed.component;
|
|
1715
|
-
const go = c.gameObject;
|
|
1716
|
-
if (!go)
|
|
1717
|
-
return;
|
|
1718
|
-
if (changed.type === eva_js.OBSERVER_TYPE.ADD) {
|
|
1719
|
-
whenContainerReady(this.gameRef(go), go, () => queueMicrotask(() => this.attachRadioGroup(c)));
|
|
1720
|
-
}
|
|
1721
|
-
else if (changed.type === eva_js.OBSERVER_TYPE.CHANGE) {
|
|
1722
|
-
c.applySelection();
|
|
1723
|
-
}
|
|
1724
|
-
else if (changed.type === eva_js.OBSERVER_TYPE.REMOVE) {
|
|
1725
|
-
const inst = this.radioGroupInstances.get(go.id);
|
|
1726
|
-
detachFromGameObject(this.gameRef(go), go, inst);
|
|
1727
|
-
this.radioGroupInstances.delete(go.id);
|
|
1728
|
-
const offs = this.signalCleanups.get(go.id);
|
|
1729
|
-
if (offs) {
|
|
1730
|
-
offs.forEach((o) => o());
|
|
1731
|
-
this.signalCleanups.delete(go.id);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
attachRadioGroup(c) {
|
|
1736
|
-
var _a, _b, _c, _d, _f;
|
|
1737
|
-
const go = c.gameObject;
|
|
1738
|
-
const game = this.gameRef(go);
|
|
1739
|
-
if (!go || this.radioGroupInstances.has(go.id))
|
|
1740
|
-
return;
|
|
1741
|
-
c.boundCheckBoxes = [];
|
|
1742
|
-
const items = [];
|
|
1743
|
-
const childNames = [];
|
|
1744
|
-
const tfChildren = ((_a = go.transform) === null || _a === void 0 ? void 0 : _a.children) || [];
|
|
1745
|
-
for (const tf of tfChildren) {
|
|
1746
|
-
const child = tf === null || tf === void 0 ? void 0 : tf.gameObject;
|
|
1747
|
-
if (!child)
|
|
1748
|
-
continue;
|
|
1749
|
-
if (c.childNames && !c.childNames.includes(child.name))
|
|
1750
|
-
continue;
|
|
1751
|
-
const cbWrapper = (_b = child.getComponent) === null || _b === void 0 ? void 0 : _b.call(child, 'CheckBox');
|
|
1752
|
-
if (!cbWrapper)
|
|
1753
|
-
continue;
|
|
1754
|
-
const inst = this.checkBoxInstances.get(child.id);
|
|
1755
|
-
if (!inst)
|
|
1756
|
-
continue;
|
|
1757
|
-
try {
|
|
1758
|
-
(_d = (_c = inst.parent) === null || _c === void 0 ? void 0 : _c.removeChild) === null || _d === void 0 ? void 0 : _d.call(_c, inst);
|
|
1759
|
-
}
|
|
1760
|
-
catch (_) { }
|
|
1761
|
-
items.push(inst);
|
|
1762
|
-
childNames.push(child.name);
|
|
1763
|
-
c.boundCheckBoxes.push({ name: child.name, cb: cbWrapper });
|
|
1764
|
-
}
|
|
1765
|
-
if (items.length === 0)
|
|
1766
|
-
return;
|
|
1767
|
-
const typeMap = {
|
|
1768
|
-
horizontal: 'horizontal', vertical: 'vertical', bidirectional: 'bidirectional',
|
|
1769
|
-
};
|
|
1770
|
-
const initialIndex = c.selectedId ? Math.max(0, childNames.indexOf(c.selectedId)) : 0;
|
|
1771
|
-
const inst = new ui.RadioGroup({
|
|
1772
|
-
items, type: (_f = typeMap[c.direction]) !== null && _f !== void 0 ? _f : 'vertical',
|
|
1773
|
-
elementsMargin: c.elementsMargin, selectedItem: initialIndex,
|
|
1774
|
-
});
|
|
1775
|
-
this.radioGroupInstances.set(go.id, inst);
|
|
1776
|
-
attachToGameObject(game, go, inst);
|
|
1777
|
-
const offs = bridgeSignals(inst, { go, prefix: 'radiogroup' }, {
|
|
1778
|
-
change: (idx, val) => {
|
|
1779
|
-
var _a;
|
|
1780
|
-
const childName = (_a = childNames[idx]) !== null && _a !== void 0 ? _a : val;
|
|
1781
|
-
c.selectedId = childName;
|
|
1782
|
-
return { selectedId: c.selectedId, index: idx };
|
|
1783
|
-
},
|
|
1784
|
-
});
|
|
1785
|
-
this.signalCleanups.set(go.id, offs);
|
|
1786
|
-
}
|
|
1787
|
-
// ============================================================
|
|
1788
|
-
// Helpers
|
|
1789
|
-
// ============================================================
|
|
1790
|
-
getInstanceMap(name) {
|
|
1791
|
-
let m = this.instances.get(name);
|
|
1792
|
-
if (!m) {
|
|
1793
|
-
m = new Map();
|
|
1794
|
-
this.instances.set(name, m);
|
|
1795
|
-
}
|
|
1796
|
-
return m;
|
|
1797
|
-
}
|
|
1798
|
-
gameRef(go) {
|
|
1799
|
-
var _a, _b, _c;
|
|
1800
|
-
// 1) System 上的 game(由 Eva 在系统注册后注入)
|
|
1801
|
-
const sysGame = this.game;
|
|
1802
|
-
if (sysGame)
|
|
1803
|
-
return sysGame;
|
|
1804
|
-
// 2) Fallback: GameObject 反查 game(用 Eva 的 GameObject -> scene -> game 链)
|
|
1805
|
-
if (go) {
|
|
1806
|
-
return (_c = (_a = go.game) !== null && _a !== void 0 ? _a : (_b = go.scene) === null || _b === void 0 ? void 0 : _b.game) !== null && _c !== void 0 ? _c : null;
|
|
1807
|
-
}
|
|
1808
|
-
return null;
|
|
1809
|
-
}
|
|
1810
|
-
};
|
|
1811
|
-
UISystem.systemName = 'UISystem';
|
|
1812
|
-
UISystem = __decorate([
|
|
1813
|
-
eva_js.decorators.componentObserver(buildObserverSchema())
|
|
1814
|
-
], UISystem);
|
|
1815
|
-
var UISystem$1 = UISystem;
|
|
1816
|
-
// ============================================================
|
|
1817
|
-
// Special inline view resolution + child collection
|
|
1818
|
-
// ============================================================
|
|
1819
|
-
/**
|
|
1820
|
-
* 4 个 wrapper 的 view ref 不放在统一 folder,需要 inline resolve 后挂 __resolved_*
|
|
1821
|
-
* 让 optionsBuilder 通过 c.__resolved_xxx 访问。
|
|
1822
|
-
*
|
|
1823
|
-
* - ProgressBar:bgView/fillView
|
|
1824
|
-
* - Select:closedView/openView
|
|
1825
|
-
* - Dialog:backdropView/backgroundView
|
|
1826
|
-
* - MaskedFrame:targetView/maskView
|
|
1827
|
-
*/
|
|
1828
|
-
function inlineResolveSpecialViews(name, c, game, go) {
|
|
1829
|
-
switch (name) {
|
|
1830
|
-
case 'ProgressBar':
|
|
1831
|
-
c.__resolved_bg = resolveViewRef(game, go, c.bgView);
|
|
1832
|
-
c.__resolved_fill = resolveViewRef(game, go, c.fillView);
|
|
1833
|
-
break;
|
|
1834
|
-
case 'Select':
|
|
1835
|
-
c.__resolved_closedView = resolveViewRef(game, go, c.closedView);
|
|
1836
|
-
c.__resolved_openView = resolveViewRef(game, go, c.openView);
|
|
1837
|
-
break;
|
|
1838
|
-
case 'Dialog':
|
|
1839
|
-
c.__resolved_backdropView = resolveViewRef(game, go, c.backdropView);
|
|
1840
|
-
c.__resolved_backgroundView = resolveViewRef(game, go, c.backgroundView);
|
|
1841
|
-
break;
|
|
1842
|
-
case 'MaskedFrame':
|
|
1843
|
-
c.__resolved_targetView = resolveViewRef(game, go, c.targetView);
|
|
1844
|
-
c.__resolved_maskView = resolveViewRef(game, go, c.maskView);
|
|
1845
|
-
break;
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
/** 验证 inline-resolved view 是否齐备(必填项不能 null) */
|
|
1849
|
-
function validateInlineResolved(name, c) {
|
|
1850
|
-
switch (name) {
|
|
1851
|
-
case 'ProgressBar':
|
|
1852
|
-
return !!(c.__resolved_bg && c.__resolved_fill);
|
|
1853
|
-
case 'Select':
|
|
1854
|
-
return !!(c.__resolved_closedView && c.__resolved_openView);
|
|
1855
|
-
case 'Dialog':
|
|
1856
|
-
// backgroundView 可 fallback PixiContainer,backdropView 也可 null
|
|
1857
|
-
return true;
|
|
1858
|
-
case 'MaskedFrame':
|
|
1859
|
-
return !!(c.__resolved_targetView && c.__resolved_maskView);
|
|
1860
|
-
default:
|
|
1861
|
-
return true;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
|
-
function collectChildContainers(game, go, childName) {
|
|
1865
|
-
var _a, _b, _c;
|
|
1866
|
-
const result = [];
|
|
1867
|
-
const contentChild = findChildEntity(go, childName);
|
|
1868
|
-
if (!contentChild)
|
|
1869
|
-
return result;
|
|
1870
|
-
const tfChildren = ((_a = contentChild.transform) === null || _a === void 0 ? void 0 : _a.children) || [];
|
|
1871
|
-
for (const tf of tfChildren) {
|
|
1872
|
-
const child = tf === null || tf === void 0 ? void 0 : tf.gameObject;
|
|
1873
|
-
if (!child)
|
|
1874
|
-
continue;
|
|
1875
|
-
const childContainer = getEvaContainer(game, child);
|
|
1876
|
-
if (childContainer) {
|
|
1877
|
-
try {
|
|
1878
|
-
(_c = (_b = childContainer.parent) === null || _b === void 0 ? void 0 : _b.removeChild) === null || _c === void 0 ? void 0 : _c.call(_b, childContainer);
|
|
1879
|
-
}
|
|
1880
|
-
catch (_) { }
|
|
1881
|
-
result.push(childContainer);
|
|
1882
|
-
}
|
|
1883
|
-
}
|
|
1884
|
-
return result;
|
|
1885
|
-
}
|
|
1886
|
-
function findChildEntity(go, name) {
|
|
1887
|
-
var _a, _b;
|
|
1888
|
-
if (!go)
|
|
1889
|
-
return null;
|
|
1890
|
-
if (typeof go.findChildByName === 'function') {
|
|
1891
|
-
try {
|
|
1892
|
-
const c = go.findChildByName(name);
|
|
1893
|
-
if (c)
|
|
1894
|
-
return c;
|
|
1895
|
-
}
|
|
1896
|
-
catch (_) { }
|
|
1897
|
-
}
|
|
1898
|
-
const children = ((_a = go.transform) === null || _a === void 0 ? void 0 : _a.children) || [];
|
|
1899
|
-
for (const tf of children) {
|
|
1900
|
-
if (((_b = tf === null || tf === void 0 ? void 0 : tf.gameObject) === null || _b === void 0 ? void 0 : _b.name) === name)
|
|
1901
|
-
return tf.gameObject;
|
|
1902
|
-
}
|
|
1903
|
-
return null;
|
|
1904
|
-
}
|
|
1905
|
-
|
|
1906
|
-
exports.Button = Button;
|
|
1907
|
-
exports.COMPONENT_DEFINITIONS = COMPONENT_DEFINITIONS;
|
|
1908
|
-
exports.CheckBox = CheckBox;
|
|
1909
|
-
exports.CircularProgressBar = CircularProgressBar;
|
|
1910
|
-
exports.Dialog = Dialog;
|
|
1911
|
-
exports.DoubleSlider = DoubleSlider;
|
|
1912
|
-
exports.FancyButton = FancyButton;
|
|
1913
|
-
exports.Input = Input;
|
|
1914
|
-
exports.List = List;
|
|
1915
|
-
exports.MaskedFrame = MaskedFrame;
|
|
1916
|
-
exports.PixiUiComponent = PixiUiComponent;
|
|
1917
|
-
exports.PluginUiSystem = UISystem$1;
|
|
1918
|
-
exports.ProgressBar = ProgressBar;
|
|
1919
|
-
exports.RadioGroup = RadioGroup;
|
|
1920
|
-
exports.ScrollBox = ScrollBox;
|
|
1921
|
-
exports.Select = Select;
|
|
1922
|
-
exports.Slider = Slider;
|
|
1923
|
-
exports.Switcher = Switcher;
|
|
1924
|
-
exports.UI = UI;
|
|
1925
|
-
exports.UISystem = UISystem$1;
|
|
1926
|
-
exports.defineUiComponent = defineUiComponent;
|