@xiacg/exia-fgui 1.0.2

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.
@@ -0,0 +1,2245 @@
1
+ 'use strict';
2
+
3
+ var fairyguiCc = require('fairygui-cc');
4
+ var exiaCore = require('@xiacg/exia-core');
5
+ var cc = require('cc');
6
+
7
+ /******************************************************************************
8
+ Copyright (c) Microsoft Corporation.
9
+
10
+ Permission to use, copy, modify, and/or distribute this software for any
11
+ purpose with or without fee is hereby granted.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
+ PERFORMANCE OF THIS SOFTWARE.
20
+ ***************************************************************************** */
21
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
22
+
23
+
24
+ function __decorate(decorators, target, key, desc) {
25
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
26
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
27
+ 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;
28
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
29
+ }
30
+
31
+ function __awaiter(thisArg, _arguments, P, generator) {
32
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
33
+ return new (P || (P = Promise))(function (resolve, reject) {
34
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
35
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
36
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
37
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
38
+ });
39
+ }
40
+
41
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
42
+ var e = new Error(message);
43
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
44
+ };
45
+
46
+ /**
47
+ * @Description: 窗口的一些类型配置
48
+ */
49
+ /** 是否开启调试模式 */
50
+ /** 窗口显示时,对其他窗口的隐藏处理类型 */
51
+ exports.WindowType = void 0;
52
+ (function (WindowType) {
53
+ /** 不做任何处理 */
54
+ WindowType[WindowType["Normal"] = 0] = "Normal";
55
+ /** 关闭所有 */
56
+ WindowType[WindowType["CloseAll"] = 1] = "CloseAll";
57
+ /** 关闭上一个 */
58
+ WindowType[WindowType["CloseOne"] = 2] = "CloseOne";
59
+ /** 隐藏所有 */
60
+ WindowType[WindowType["HideAll"] = 4] = "HideAll";
61
+ /** 隐藏上一个 */
62
+ WindowType[WindowType["HideOne"] = 8] = "HideOne";
63
+ })(exports.WindowType || (exports.WindowType = {}));
64
+ /** 窗口适配类型,默认全屏 */
65
+ exports.AdapterType = void 0;
66
+ (function (AdapterType) {
67
+ /** 全屏适配 */
68
+ AdapterType[AdapterType["Full"] = 0] = "Full";
69
+ /** 空出刘海 */
70
+ AdapterType[AdapterType["Bang"] = 1] = "Bang";
71
+ /** 固定的 不适配 */
72
+ AdapterType[AdapterType["Fixed"] = 2] = "Fixed";
73
+ })(exports.AdapterType || (exports.AdapterType = {}));
74
+ /** 定义装饰器元数据的key */
75
+ var MetadataKey;
76
+ (function (MetadataKey) {
77
+ /** 属性 */
78
+ MetadataKey["prop"] = "__uipropmeta__";
79
+ /** 回调 */
80
+ MetadataKey["callback"] = "__uicbmeta__";
81
+ /** 控制器 */
82
+ MetadataKey["control"] = "__uicontrolmeta__";
83
+ /** 动画 */
84
+ MetadataKey["transition"] = "__uitransitionmeta__";
85
+ /** 原始名称 */
86
+ MetadataKey["originalName"] = "__UI_ORIGINAL_NAME__";
87
+ })(MetadataKey || (MetadataKey = {}));
88
+
89
+ /**
90
+ * @Description: 属性辅助类
91
+ */
92
+ /** @internal */
93
+ class PropsHelper {
94
+ /** @internal */
95
+ static setConfig(config) {
96
+ this._config = config;
97
+ }
98
+ /** 序列化属性 @internal */
99
+ static serializeProps(component, packageName, componentName) {
100
+ if (!this._config) {
101
+ return;
102
+ }
103
+ const config = this._config[packageName];
104
+ if (!config) {
105
+ return;
106
+ }
107
+ componentName = componentName || component.name;
108
+ const propsInfo = config[componentName];
109
+ if (!propsInfo) {
110
+ return;
111
+ }
112
+ // 设置属性
113
+ const props = propsInfo.props;
114
+ this.serializationPropsNode(component, props);
115
+ // 设置回调
116
+ const callbacks = propsInfo.callbacks;
117
+ this.serializationCallbacksNode(component, callbacks);
118
+ // 设置控制器
119
+ const controls = propsInfo.controls;
120
+ this.serializationControlsNode(component, controls);
121
+ // 设置动画
122
+ const transitions = propsInfo.transitions;
123
+ this.serializationTransitionsNode(component, transitions);
124
+ }
125
+ /** 给界面中定义的属性赋值 @internal */
126
+ static serializationPropsNode(component, props) {
127
+ const propsCount = props.length;
128
+ // [name1, len, ...props1, name2, len, ...props2, ...]
129
+ let index = 0;
130
+ while (index < propsCount) {
131
+ const propName = props[index++];
132
+ const endIndex = index + props[index];
133
+ let uinode = component;
134
+ while (++index <= endIndex) {
135
+ uinode = uinode.getChildAt(props[index]);
136
+ if (!uinode) {
137
+ console.warn(`无法对UI类(${component.name})属性(${propName})赋值,请检查节点配置是否正确`);
138
+ break;
139
+ }
140
+ }
141
+ component[propName] = uinode == component ? null : uinode;
142
+ }
143
+ }
144
+ /** 给界面中定义的回调赋值 @internal */
145
+ static serializationCallbacksNode(component, callbacks) {
146
+ const propsCount = callbacks.length;
147
+ // [name1, len, ...props1, name2, len, ...props2, ...]
148
+ let index = 0;
149
+ while (index < propsCount) {
150
+ const propName = callbacks[index++];
151
+ const endIndex = index + callbacks[index];
152
+ let uinode = component;
153
+ while (++index <= endIndex) {
154
+ uinode = uinode.getChildAt(callbacks[index]);
155
+ if (!uinode) {
156
+ console.warn(`无法对UI类(${component.name})的(${propName})设置回调,请检查节点配置是否正确`);
157
+ break;
158
+ }
159
+ }
160
+ if (uinode != component) {
161
+ uinode.onClick(component[propName], component);
162
+ }
163
+ }
164
+ }
165
+ /** 给界面中定义的控制器赋值 @internal */
166
+ static serializationControlsNode(component, controls) {
167
+ const controlsCount = controls.length;
168
+ let index = 0;
169
+ while (index < controlsCount) {
170
+ const propName = controls[index];
171
+ const controlName = controls[index + 1];
172
+ const controller = component.getController(controlName);
173
+ if (!controller) {
174
+ console.warn(`无法对UI类(${component.name})的(${propName})设置控制器,请检查配置是否正确`);
175
+ break;
176
+ }
177
+ component[propName] = controller;
178
+ index += 2;
179
+ }
180
+ }
181
+ /** 给界面中定义的动画赋值 @internal */
182
+ static serializationTransitionsNode(component, transitions) {
183
+ const transitionsCount = transitions.length;
184
+ let index = 0;
185
+ while (index < transitionsCount) {
186
+ const propName = transitions[index];
187
+ const transitionName = transitions[index + 1];
188
+ const transition = component.getTransition(transitionName);
189
+ if (!transition) {
190
+ console.warn(`无法对UI类(${component.name})的(${propName})设置动画,请检查配置是否正确`);
191
+ break;
192
+ }
193
+ component[propName] = transition;
194
+ index += 2;
195
+ }
196
+ }
197
+ }
198
+ /** @internal */
199
+ PropsHelper._config = {};
200
+
201
+ /**
202
+ * @Description: 信息池 注册的窗口、header、自定义组件的信息
203
+ */
204
+ /** @internal */
205
+ class InfoPool {
206
+ /**
207
+ * 添加窗口信息
208
+ * @param ctor 类的构造函数
209
+ * @param group 窗口组名
210
+ * @param pkg 包名
211
+ * @param name 窗口名
212
+ * @param bundleName bundle名
213
+ * @internal
214
+ */
215
+ static add(ctor, group, pkg, name, pkgs) {
216
+ if (this.has(name)) {
217
+ console.warn(`窗口【${name}】已注册,跳过,请检查是否重复注册`);
218
+ return;
219
+ }
220
+ exiaCore.debug(`窗口注册 窗口名:${name} 包名:${pkg} 组名:${group}`);
221
+ this._windowInfos.set(name, {
222
+ ctor: ctor,
223
+ group: group,
224
+ pkgName: pkg,
225
+ name: name,
226
+ });
227
+ // 窗口组件扩展
228
+ fairyguiCc.UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
229
+ this.addWindowPkg(name, pkg);
230
+ if (pkgs.length > 0) {
231
+ for (const pkg of pkgs) {
232
+ this.addWindowPkg(name, pkg);
233
+ }
234
+ }
235
+ }
236
+ /**
237
+ * 注册窗口header信息
238
+ * @param ctor 类的构造函数
239
+ * @param pkg 包名
240
+ * @param name 窗口名
241
+ * @param bundleName bundle名
242
+ * @internal
243
+ */
244
+ static addHeader(ctor, pkg, name) {
245
+ if (this.hasHeader(name)) {
246
+ console.warn(`header【${name}】已注册,跳过,请检查是否重复注册`);
247
+ return;
248
+ }
249
+ exiaCore.debug(`header注册 header名:${name} 包名:${pkg}`);
250
+ this._headerInfos.set(name, {
251
+ ctor: ctor,
252
+ pkgName: pkg,
253
+ });
254
+ // 窗口header扩展
255
+ fairyguiCc.UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
256
+ }
257
+ /**
258
+ * 注册自定义组件信息
259
+ * @param ctor 组件构造函数
260
+ * @param pkg 包名
261
+ * @param name 组件名
262
+ * @internal
263
+ */
264
+ static addComponent(ctor, pkg, name) {
265
+ const componentKey = `${pkg}/${name}`;
266
+ if (this._customComponents.has(componentKey)) {
267
+ console.debug(`自定义组件【${name}】已注册,跳过,请检查是否重复注册`);
268
+ return;
269
+ }
270
+ exiaCore.debug(`自定义组件注册 组件名:${name} 包名:${pkg}`);
271
+ this._customComponents.add(componentKey);
272
+ this.registerComponent(ctor, pkg, name);
273
+ }
274
+ /**
275
+ * 是否存在窗口信息
276
+ * @param name 窗口名
277
+ * @returns 是否存在
278
+ * @internal
279
+ */
280
+ static has(name) {
281
+ return this._windowInfos.has(name);
282
+ }
283
+ /**
284
+ * 获取窗口信息
285
+ * @param name 窗口名
286
+ * @returns 窗口信息
287
+ * @internal
288
+ */
289
+ static get(name) {
290
+ if (!this.has(name)) {
291
+ throw new Error(`窗口【${name}】未注册,请使用 _uidecorator.uiclass 注册窗口`);
292
+ }
293
+ return this._windowInfos.get(name);
294
+ }
295
+ /**
296
+ * 是否存在窗口header信息
297
+ * @param name 窗口header名
298
+ * @returns 是否存在
299
+ * @internal
300
+ */
301
+ static hasHeader(name) {
302
+ return this._headerInfos.has(name);
303
+ }
304
+ /**
305
+ * 获取窗口header信息
306
+ * @param name 窗口header名
307
+ * @returns 窗口header信息
308
+ * @internal
309
+ */
310
+ static getHeader(name) {
311
+ if (!this.hasHeader(name)) {
312
+ throw new Error(`窗口header【${name}】未注册,请使用 _uidecorator.uiheader 注册窗口header`);
313
+ }
314
+ return this._headerInfos.get(name);
315
+ }
316
+ /**
317
+ * 设置UI包所在的bundle名
318
+ * @param pkg 包名
319
+ * @param bundleName bundle名
320
+ * @internal
321
+ */
322
+ static addBundleName(pkg, bundleName) {
323
+ if (this._customPackageBundle.has(pkg)) {
324
+ console.warn(`UI包【${pkg}】已设置过包名`);
325
+ return;
326
+ }
327
+ this._customPackageBundle.set(pkg, bundleName);
328
+ }
329
+ /**
330
+ * 获取UI包所在的bundle名
331
+ * @param pkg 包名
332
+ * @returns bundle名
333
+ * @internal
334
+ */
335
+ static getBundleName(pkg) {
336
+ return this._customPackageBundle.get(pkg) || "resources";
337
+ }
338
+ /**
339
+ * UI包所在的自定义路径
340
+ * @param pkg 包名
341
+ * @param path 路径
342
+ * @internal
343
+ */
344
+ static addPackagePath(pkg, path) {
345
+ if (this._customPackagePath.has(pkg)) {
346
+ console.warn(`UI包【${pkg}】已设置过自定义路径`);
347
+ return;
348
+ }
349
+ this._customPackagePath.set(pkg, path);
350
+ }
351
+ /**
352
+ * 获取UI包所在的路径
353
+ * @param pkg 包名
354
+ * @returns 路径
355
+ * @internal
356
+ */
357
+ static getPackagePath(pkg) {
358
+ return `${this._customPackagePath.get(pkg) || "ui"}/${pkg}`;
359
+ }
360
+ /**
361
+ * 添加窗口需要的包名
362
+ * @param windowName 窗口名
363
+ * @param pkg 包名
364
+ * @internal
365
+ */
366
+ static addWindowPkg(windowName, pkg) {
367
+ this._dirty = true;
368
+ if (!this._windowPkgs.has(windowName)) {
369
+ this._windowPkgs.set(windowName, [pkg]);
370
+ }
371
+ else {
372
+ this._windowPkgs.get(windowName).push(pkg);
373
+ }
374
+ }
375
+ /**
376
+ * 获取窗口需要的包名列表
377
+ * @param windowName 窗口名
378
+ * @returns 包名列表
379
+ * @internal
380
+ */
381
+ static getWindowPkg(windowName) {
382
+ if (this._dirty) {
383
+ this.refreshWindowPackages();
384
+ this._dirty = false;
385
+ }
386
+ return this._windowPkgs.get(windowName) || [];
387
+ }
388
+ /**
389
+ * 添加手动管理资源加载 和 卸载的包名
390
+ * @param pkgName 包名
391
+ * @internal
392
+ */
393
+ static addManualPackage(pkgName) {
394
+ this._dirty = true;
395
+ this._manualPackages.add(pkgName);
396
+ }
397
+ /**
398
+ * 注册自定义组件信息
399
+ * @param info
400
+ * @internal
401
+ */
402
+ static registerComponent(ctor, pkg, name) {
403
+ // 自定义组件扩展
404
+ const onConstruct = function () {
405
+ PropsHelper.serializeProps(this, pkg, name);
406
+ this.onInit && this.onInit();
407
+ };
408
+ ctor.prototype.onConstruct = onConstruct;
409
+ const oldDispose = ctor.prototype.dispose;
410
+ ctor.prototype.dispose = function () {
411
+ this.onClose && this.onClose();
412
+ oldDispose.call(this);
413
+ };
414
+ // 自定义组件扩展
415
+ fairyguiCc.UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
416
+ }
417
+ /** 刷新窗口需要的包名信息 */
418
+ static refreshWindowPackages() {
419
+ for (const packages of this._windowPkgs.values()) {
420
+ let len = packages.length;
421
+ for (let index = len - 1; index >= 0; index--) {
422
+ const name = packages[index];
423
+ if (this._manualPackages.has(name)) {
424
+ packages.splice(index, 1);
425
+ }
426
+ }
427
+ }
428
+ }
429
+ }
430
+ /** @internal */
431
+ InfoPool._windowInfos = new Map(); // 窗口信息池 窗口名 -> 窗口信息
432
+ /** @internal */
433
+ InfoPool._headerInfos = new Map(); // 窗口header信息池 窗口header名 -> header信息
434
+ /** @internal */
435
+ InfoPool._customComponents = new Set(); // 自定义组件信息池 自定义组件名 -> 组件信息
436
+ /** @internal */
437
+ InfoPool._customPackageBundle = new Map(); // UI包所在的bundle名 1对1 默认: resources
438
+ /** @internal */
439
+ InfoPool._customPackagePath = new Map(); // 自定义UI包所在的路径 1对1
440
+ /** @internal */
441
+ InfoPool._windowPkgs = new Map(); // 窗口名对应的包名列表 窗口名 -> 包名列表
442
+ /** @internal */
443
+ InfoPool._manualPackages = new Set(); // 需要手动管理的资源包名
444
+ /** @internal */
445
+ InfoPool._dirty = true;
446
+
447
+ /** @internal */
448
+ class ResLoader {
449
+ /**
450
+ * 设置UI包加载相关回调函数
451
+ * @internal
452
+ */
453
+ static setCallbacks(callbacks) {
454
+ this._showWaitWindow = callbacks.showWaitWindow;
455
+ this._hideWaitWindow = callbacks.hideWaitWindow;
456
+ this._onLoadFail = callbacks.fail;
457
+ }
458
+ /** @internal */
459
+ static setAutoRelease(auto) {
460
+ this.autoRelease = auto;
461
+ }
462
+ /**
463
+ * 增加等待窗的引用计数
464
+ * @internal
465
+ */
466
+ static addWaitRef() {
467
+ var _a;
468
+ if (this.waitRef++ === 0) {
469
+ (_a = this._showWaitWindow) === null || _a === void 0 ? void 0 : _a.call(this);
470
+ }
471
+ }
472
+ /**
473
+ * 减少等待窗的引用计数
474
+ * @internal
475
+ */
476
+ static decWaitRef() {
477
+ var _a;
478
+ // 修复:防止waitRef变为负数
479
+ this.waitRef = Math.max(0, this.waitRef - 1);
480
+ if (this.waitRef === 0) {
481
+ (_a = this._hideWaitWindow) === null || _a === void 0 ? void 0 : _a.call(this);
482
+ }
483
+ }
484
+ /** @internal */
485
+ static getRef(pkg) {
486
+ return this.pkgRefs.get(pkg) || 0;
487
+ }
488
+ /** @internal */
489
+ static addRef(pkg) {
490
+ this.pkgRefs.set(pkg, this.getRef(pkg) + 1);
491
+ }
492
+ /** @internal */
493
+ static subRef(pkg) {
494
+ let ref = this.getRef(pkg) - 1;
495
+ this.pkgRefs.set(pkg, ref);
496
+ return ref;
497
+ }
498
+ /**
499
+ * 加载窗口需要的包
500
+ * @param windowName 窗口名
501
+ */
502
+ static loadWindowRes(windowName) {
503
+ // 获取窗口需要的资源包
504
+ let packageNames = InfoPool.getWindowPkg(windowName);
505
+ if (packageNames.length <= 0) {
506
+ return Promise.resolve();
507
+ }
508
+ return this.loadUIPackages(packageNames, windowName);
509
+ }
510
+ /**
511
+ * 卸载窗口需要的包
512
+ * @param windowName 窗口名
513
+ */
514
+ static unloadWindowRes(windowName) {
515
+ // 获取窗口需要的资源包
516
+ let packageNames = InfoPool.getWindowPkg(windowName);
517
+ if (packageNames.length <= 0) {
518
+ return;
519
+ }
520
+ this.unloadUIPackages(packageNames);
521
+ }
522
+ /**
523
+ * 根据传入的UIPackage名称集合 加载多个UI包资源
524
+ * @param packages 包名列表
525
+ * @param windowName 窗口名(用于失败回调)
526
+ * @internal
527
+ */
528
+ static loadUIPackages(packages, windowName) {
529
+ return __awaiter(this, void 0, void 0, function* () {
530
+ // 修复:防止并发加载相同的包
531
+ // 检查是否有包正在加载,如果有则等待其完成
532
+ const waitPromises = [];
533
+ for (const pkg of packages) {
534
+ const loadingPromise = this.loadingPromises.get(pkg);
535
+ if (loadingPromise) {
536
+ waitPromises.push(loadingPromise);
537
+ }
538
+ }
539
+ // 等待所有正在加载的包完成
540
+ if (waitPromises.length > 0) {
541
+ yield Promise.all(waitPromises);
542
+ }
543
+ // 先找出来所有需要加载的包名
544
+ let list = packages.filter((pkg) => this.getRef(pkg) <= 0);
545
+ if (list.length <= 0) {
546
+ // 增加引用计数
547
+ packages.forEach((pkg) => this.addRef(pkg));
548
+ return;
549
+ }
550
+ // 一定有需要加载的资源
551
+ this.addWaitRef();
552
+ // 记录成功加载的包,用于失败时回滚
553
+ const loadedPackages = [];
554
+ // 创建加载Promise并记录
555
+ const loadPromise = (() => __awaiter(this, void 0, void 0, function* () {
556
+ try {
557
+ // 获取包对应的bundle名
558
+ let bundleNames = list.map((pkg) => InfoPool.getBundleName(pkg));
559
+ // 加载bundle
560
+ yield this.loadBundles(bundleNames, windowName);
561
+ // 顺序加载每个UI包,每加载成功一个就记录
562
+ for (const pkg of list) {
563
+ yield this.loadSingleUIPackage(pkg, windowName);
564
+ loadedPackages.push(pkg);
565
+ }
566
+ // 所有包加载成功后,减少等待窗引用计数
567
+ this.decWaitRef();
568
+ // 增加包资源的引用计数
569
+ packages.forEach((pkg) => this.addRef(pkg));
570
+ }
571
+ catch (err) {
572
+ // 减少等待窗的引用计数
573
+ this.decWaitRef();
574
+ // 回滚:卸载已经加载成功的包
575
+ loadedPackages.forEach((pkg) => {
576
+ fairyguiCc.UIPackage.removePackage(pkg);
577
+ });
578
+ throw err;
579
+ }
580
+ finally {
581
+ // 清理加载状态
582
+ list.forEach((pkg) => this.loadingPromises.delete(pkg));
583
+ }
584
+ }))();
585
+ // 记录正在加载的包
586
+ list.forEach((pkg) => this.loadingPromises.set(pkg, loadPromise));
587
+ yield loadPromise;
588
+ });
589
+ }
590
+ /**
591
+ * 加载多个bundle(顺序加载)
592
+ * @param bundleNames bundle名集合
593
+ * @param windowName 窗口名(用于失败回调)
594
+ * @internal
595
+ */
596
+ static loadBundles(bundleNames, windowName) {
597
+ return __awaiter(this, void 0, void 0, function* () {
598
+ let unloadedBundleNames = bundleNames.filter((bundleName) => bundleName !== "resources" && !cc.assetManager.getBundle(bundleName));
599
+ if (unloadedBundleNames.length <= 0) {
600
+ return;
601
+ }
602
+ // 顺序加载每个bundle
603
+ for (const bundleName of unloadedBundleNames) {
604
+ yield new Promise((resolve, reject) => {
605
+ cc.assetManager.loadBundle(bundleName, (err, bundle) => {
606
+ if (err) {
607
+ // 调用失败回调
608
+ if (this._onLoadFail) {
609
+ this._onLoadFail(windowName, 1, bundleName);
610
+ }
611
+ reject(new Error(`bundle【${bundleName}】加载失败`));
612
+ }
613
+ else {
614
+ resolve();
615
+ }
616
+ });
617
+ });
618
+ }
619
+ });
620
+ }
621
+ /**
622
+ * 加载单个 UI 包
623
+ * @param pkg 包名
624
+ * @param windowName 窗口名(用于失败回调)
625
+ * @internal
626
+ */
627
+ static loadSingleUIPackage(pkg, windowName) {
628
+ return new Promise((resolve, reject) => {
629
+ let bundleName = InfoPool.getBundleName(pkg);
630
+ let bundle = bundleName === "resources"
631
+ ? cc.resources
632
+ : cc.assetManager.getBundle(bundleName);
633
+ fairyguiCc.UIPackage.loadPackage(bundle, InfoPool.getPackagePath(pkg), (err) => {
634
+ if (err) {
635
+ // 调用失败回调
636
+ if (windowName && this._onLoadFail) {
637
+ this._onLoadFail(windowName, 2, pkg);
638
+ }
639
+ reject(new Error(`UI包【${pkg}】加载失败`));
640
+ }
641
+ else {
642
+ resolve();
643
+ }
644
+ });
645
+ });
646
+ }
647
+ /**
648
+ * 根据传入的UIPackage名称集合 卸载多个UI包资源
649
+ * @param pkgNames UIPackage名称集合
650
+ * @internal
651
+ */
652
+ static unloadUIPackages(packages) {
653
+ for (const pkg of packages) {
654
+ if (this.subRef(pkg) === 0 && this.autoRelease) {
655
+ fairyguiCc.UIPackage.removePackage(pkg);
656
+ }
657
+ }
658
+ }
659
+ /**
660
+ * 释放不再使用中的自动加载的UI资源
661
+ * 释放所有引用计数 <= 0 的UI资源
662
+ * @internal
663
+ */
664
+ static releaseUnusedRes() {
665
+ let keys = Array.from(this.pkgRefs.keys());
666
+ for (const key of keys) {
667
+ if (this.getRef(key) <= 0) {
668
+ fairyguiCc.UIPackage.removePackage(key);
669
+ this.pkgRefs.delete(key);
670
+ }
671
+ }
672
+ }
673
+ }
674
+ /**
675
+ * 等待窗口的引用计数
676
+ * 每次加载开始时 +1 每次加载完成时 -1
677
+ * @internal
678
+ */
679
+ ResLoader.waitRef = 0;
680
+ /** 包的引用计数 包名 -> 引用计数 */
681
+ ResLoader.pkgRefs = new Map();
682
+ /** 包的加载状态 包名 -> 加载中的Promise,用于防止并发加载 */
683
+ ResLoader.loadingPromises = new Map();
684
+ /**
685
+ * 自动释放UI资源
686
+ * @internal
687
+ */
688
+ ResLoader.autoRelease = true;
689
+ /** UI包加载回调 - 显示加载等待窗 @internal */
690
+ ResLoader._showWaitWindow = null;
691
+ /** UI包加载回调 - 隐藏加载等待窗 @internal */
692
+ ResLoader._hideWaitWindow = null;
693
+ /** UI包加载回调 - 打开窗口时UI包加载失败 @internal */
694
+ ResLoader._onLoadFail = null;
695
+
696
+ /**
697
+ * @Description: 窗口管理类
698
+ */
699
+ class WindowManager {
700
+ /** @internal */
701
+ static get bgAlpha() {
702
+ return this._bgAlpha;
703
+ }
704
+ /** @internal */
705
+ static set bgAlpha(value) {
706
+ this._bgAlpha = value;
707
+ }
708
+ /**
709
+ * 屏幕大小改变时 调用所有窗口的screenResize方法 (内部方法)
710
+ * @internal
711
+ */
712
+ static onScreenResize() {
713
+ // 半透明遮罩适配
714
+ if (this._alphaGraph) {
715
+ this._alphaGraph.setPosition(exiaCore.Screen.ScreenWidth * 0.5, exiaCore.Screen.ScreenHeight * 0.5);
716
+ this._alphaGraph.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
717
+ }
718
+ // 所有窗口适配
719
+ this._windows.forEach((window) => {
720
+ window._adapted();
721
+ });
722
+ // 所有header适配
723
+ HeaderManager.onScreenResize();
724
+ }
725
+ /**
726
+ * 添加手动管理资源加载 和 卸载的包名
727
+ * @param pkgName 包名
728
+ */
729
+ static addManualPackage(pkgName) {
730
+ InfoPool.addManualPackage(pkgName);
731
+ }
732
+ /**
733
+ * 提供一种特殊需求 用来手动设置包所在的 bundle名 以及包在bundle中的路径
734
+ * @param name 窗口名称
735
+ * @param bundleName bundle名 默认: resources
736
+ * @param path 包在bundle中的路径 默认: ui目录
737
+ */
738
+ static setPackageInfo(pkgName, bundleName = "resources", path = "ui") {
739
+ if (bundleName !== "resources") {
740
+ InfoPool.addBundleName(pkgName, bundleName);
741
+ }
742
+ if (path !== "ui") {
743
+ InfoPool.addPackagePath(pkgName, path);
744
+ }
745
+ }
746
+ /**
747
+ * 用于手动设置UI导出数据
748
+ * @param config UI导出数据
749
+ */
750
+ static setUIConfig(config) {
751
+ PropsHelper.setConfig(config);
752
+ }
753
+ /**
754
+ * 设置UI包加载相关回调函数
755
+ * @param callbacks 包含加载回调的对象
756
+ * @param callbacks.showWaitWindow 显示加载等待窗的回调
757
+ * @param callbacks.hideWaitWindow 隐藏加载等待窗的回调
758
+ * @param callbacks.fail 打开窗口时资源加载失败的回调 code( 1:bundle加载失败 2:包加载失败 )
759
+ */
760
+ static setPackageCallbacks(callbacks) {
761
+ ResLoader.setCallbacks(callbacks);
762
+ }
763
+ /**
764
+ * 向窗口管理器添加一个窗口组 如果窗口组名称已存在,则抛出错误. (内部方法)
765
+ * @param group 要添加的窗口组
766
+ * @internal
767
+ */
768
+ static addWindowGroup(group) {
769
+ if (this._groups.has(group.name)) {
770
+ throw new Error(`窗口组【${group.name}】已存在`);
771
+ }
772
+ this._groups.set(group.name, group);
773
+ this._groupNames.push(group.name);
774
+ }
775
+ /**
776
+ * 设置半透明遮罩
777
+ * @param alphaGraph 半透明遮罩
778
+ * @internal
779
+ */
780
+ static setAlphaGraph(alphaGraph) {
781
+ this._alphaGraph = alphaGraph;
782
+ }
783
+ /**
784
+ * 异步打开一个窗口 (如果UI包的资源未加载, 会自动加载 可以配合 WindowManager.setPackageCallbacks一起使用)
785
+ * @param 窗口类
786
+ * @param userdata 用户数据
787
+ */
788
+ static showWindow(window, userdata) {
789
+ // 优先使用装饰器设置的静态属性,避免代码混淆后 constructor.name 变化
790
+ const name = window[MetadataKey.originalName];
791
+ if (!name) {
792
+ throw new Error(`窗口【${window.name}】未注册,请使用 _uidecorator.uiclass 注册窗口`);
793
+ }
794
+ return this.showWindowByName(name, userdata);
795
+ }
796
+ /**
797
+ * 通过窗口名称打开一个窗口
798
+ * @param name 窗口名称
799
+ * @param userdata 用户数据
800
+ * @internal
801
+ */
802
+ static showWindowByName(name, userdata) {
803
+ // 找到他所属的窗口组
804
+ const info = InfoPool.get(name);
805
+ const group = this.getWindowGroup(info.group);
806
+ return group.showWindow(info, userdata);
807
+ }
808
+ /**
809
+ * 关闭一个窗口
810
+ * @param ctor 窗口类
811
+ */
812
+ static closeWindow(window) {
813
+ // 取到窗口的名称,优先使用装饰器设置的静态属性
814
+ const name = window[MetadataKey.originalName];
815
+ this.closeWindowByName(name);
816
+ }
817
+ /**
818
+ * 通过窗口名称关闭一个窗口
819
+ * @param name 窗口名称
820
+ */
821
+ static closeWindowByName(name) {
822
+ if (!this.hasWindow(name)) {
823
+ console.warn(`窗口不存在 ${name} 不需要关闭`);
824
+ return;
825
+ }
826
+ const info = InfoPool.get(name);
827
+ const group = this.getWindowGroup(info.group);
828
+ group.removeWindow(name);
829
+ // 调整半透明遮罩
830
+ this.adjustAlphaGraph();
831
+ // 找到最上层的窗口 调用toTop方法
832
+ let topWindow = this.getTopWindow();
833
+ if (topWindow && !topWindow.isTop()) {
834
+ topWindow._toTop();
835
+ }
836
+ }
837
+ /**
838
+ * 是否存在窗口
839
+ * @param name 窗口名称
840
+ */
841
+ static hasWindow(name) {
842
+ return this._windows.has(name);
843
+ }
844
+ /**
845
+ * 添加窗口
846
+ * @param name 窗口名称
847
+ * @param window 要添加的窗口对象,需实现 IWindow 接口。
848
+ * @internal
849
+ */
850
+ static addWindow(name, window) {
851
+ this._windows.set(name, window);
852
+ }
853
+ /**
854
+ * 移除窗口
855
+ * @param name 窗口名称
856
+ * @internal
857
+ */
858
+ static removeWindow(name) {
859
+ this._windows.delete(name);
860
+ }
861
+ /**
862
+ * 根据窗口名称获取窗口实例。
863
+ * @template T 窗口类型,必须继承自IWindow接口。
864
+ * @param name 窗口名称
865
+ * @returns 如果找到窗口,则返回对应类型的窗口实例;否则返回null。
866
+ */
867
+ static getWindow(name) {
868
+ return this._windows.get(name);
869
+ }
870
+ /**
871
+ * 获取当前最顶层的窗口实例。
872
+ * 默认会忽略掉忽略查询的窗口组
873
+ * @returns {T | null} - 返回最顶层的窗口实例,如果没有找到则返回 null。
874
+ */
875
+ static getTopWindow(isAll = true) {
876
+ const names = this._groupNames;
877
+ for (let i = names.length - 1; i >= 0; i--) {
878
+ const group = this.getWindowGroup(names[i]);
879
+ if (group.isIgnore && !isAll) {
880
+ continue;
881
+ }
882
+ if (group.size === 0) {
883
+ continue;
884
+ }
885
+ return group.getTopWindow();
886
+ }
887
+ return null;
888
+ }
889
+ /**
890
+ * 获取所有窗口组的名称列表(按层级顺序)
891
+ * @returns 窗口组的名称列表
892
+ */
893
+ static getGroupNames() {
894
+ return this._groupNames;
895
+ }
896
+ /**
897
+ * 根据给定的组名获取窗口组。如果组不存在,则抛出错误。
898
+ * @param name 窗口组名称
899
+ * @returns 返回找到的窗口组。
900
+ */
901
+ static getWindowGroup(name) {
902
+ if (this._groups.has(name)) {
903
+ return this._groups.get(name);
904
+ }
905
+ throw new Error(`窗口组【${name}】不存在`);
906
+ }
907
+ /**
908
+ * 关闭所有窗口
909
+ * @param ignores 不关闭的窗口
910
+ */
911
+ static closeAllWindow(ignores = []) {
912
+ let len = this._groupNames.length;
913
+ for (let i = len - 1; i >= 0; i--) {
914
+ let group = this.getWindowGroup(this._groupNames[i]);
915
+ group.closeAllWindow(ignores);
916
+ }
917
+ // 找到最上层的窗口 调用toTop方法
918
+ let topWindow = this.getTopWindow();
919
+ if (topWindow && !topWindow.isTop()) {
920
+ topWindow._toTop();
921
+ }
922
+ }
923
+ /**
924
+ * 调整半透明遮罩的显示层级
925
+ * 从上到下(从所有窗口组)查找第一个bgAlpha不为0的窗口,将遮罩放到该窗口下方
926
+ * @internal
927
+ */
928
+ static adjustAlphaGraph() {
929
+ let topWindow = null;
930
+ // 从后往前遍历窗口组(后面的窗口组层级更高)
931
+ for (let i = this._groupNames.length - 1; i >= 0; i--) {
932
+ const group = this._groups.get(this._groupNames[i]);
933
+ if (group.size === 0) {
934
+ continue;
935
+ }
936
+ // 在当前窗口组中从上到下查找第一个bgAlpha不为0的窗口
937
+ for (let j = group.windowNames.length - 1; j >= 0; j--) {
938
+ const name = group.windowNames[j];
939
+ const win = WindowManager.getWindow(name);
940
+ if (win && win.bgAlpha > 0) {
941
+ topWindow = win;
942
+ break;
943
+ }
944
+ }
945
+ if (topWindow) {
946
+ break;
947
+ }
948
+ }
949
+ // 如果找到了需要遮罩的窗口
950
+ if (topWindow && topWindow.parent) {
951
+ // 获取窗口组的根节点
952
+ const parent = topWindow.parent;
953
+ // 将遮罩设置到目标窗口的下方
954
+ const windowIndex = parent.getChildIndex(topWindow);
955
+ let gIndex = 0;
956
+ // 确保遮罩在目标窗口组的根节点下
957
+ if (this._alphaGraph.parent !== parent) {
958
+ this._alphaGraph.removeFromParent();
959
+ parent.addChild(this._alphaGraph);
960
+ gIndex = parent.numChildren - 1;
961
+ }
962
+ else {
963
+ gIndex = parent.getChildIndex(this._alphaGraph);
964
+ }
965
+ let newIndex = gIndex >= windowIndex ? windowIndex : windowIndex - 1;
966
+ parent.setChildIndex(this._alphaGraph, newIndex);
967
+ // 显示遮罩
968
+ this._alphaGraph.visible = true;
969
+ // 半透明遮罩绘制
970
+ this._bgColor.a = topWindow.bgAlpha * 255;
971
+ this._alphaGraph.clearGraphics();
972
+ this._alphaGraph.drawRect(0, this._bgColor, this._bgColor);
973
+ }
974
+ else {
975
+ // 没有找到需要遮罩的窗口,隐藏遮罩
976
+ this._alphaGraph.visible = false;
977
+ }
978
+ }
979
+ /**
980
+ * 释放不再使用中的自动加载的UI资源
981
+ * 针对在 UIModule 中设置了不自动释放资源的场景
982
+ */
983
+ static releaseUnusedRes() {
984
+ ResLoader.releaseUnusedRes();
985
+ }
986
+ }
987
+ WindowManager._bgAlpha = 0.75;
988
+ WindowManager._bgColor = new cc.Color(0, 0, 0, 0);
989
+ /** @internal */
990
+ WindowManager._alphaGraph = null; // 半透明的遮罩
991
+ /** @internal */
992
+ WindowManager._groups = new Map(); // 窗口组
993
+ /** @internal */
994
+ WindowManager._groupNames = []; // 窗口组的名称列表
995
+ /** @internal */
996
+ WindowManager._windows = new Map(); // 所有窗口的引用
997
+
998
+ /**
999
+ * @Description: header(资源栏)管理类
1000
+ */
1001
+ class HeaderManager {
1002
+ /**
1003
+ * 屏幕适配
1004
+ * @internal
1005
+ */
1006
+ static onScreenResize() {
1007
+ for (const header of this._headers.values()) {
1008
+ header._adapted();
1009
+ }
1010
+ }
1011
+ /**
1012
+ * 为窗口请求一个header
1013
+ * @param windowName 窗口名
1014
+ * @param headerInfo header信息
1015
+ */
1016
+ static requestHeader(windowName, headerInfo) {
1017
+ // 保存header的信息
1018
+ if (!headerInfo) {
1019
+ return;
1020
+ }
1021
+ this._headerInfos.set(windowName, headerInfo);
1022
+ const headerName = headerInfo.name;
1023
+ // 如果header不存在,创建它
1024
+ if (!this._headers.has(headerName)) {
1025
+ const header = this.createHeader(headerInfo);
1026
+ this._headers.set(headerName, header);
1027
+ this._refCounts.set(headerName, 0);
1028
+ this._headerWindowsMap.set(headerName, new Set());
1029
+ }
1030
+ // 增加引用计数
1031
+ this._refCounts.set(headerName, (this._refCounts.get(headerName) || 0) + 1);
1032
+ // 记录窗口和header的关系
1033
+ const windowsSet = this._headerWindowsMap.get(headerName);
1034
+ if (windowsSet) {
1035
+ windowsSet.add(windowName);
1036
+ }
1037
+ }
1038
+ /**
1039
+ * 显示指定窗口的header
1040
+ * @param windowName 窗口名
1041
+ * @param fromHide 窗口是否从隐藏状态恢复显示
1042
+ */
1043
+ static showHeader(windowName) {
1044
+ if (!this.hasHeader(windowName)) {
1045
+ return;
1046
+ }
1047
+ const headerName = this.getHeaderName(windowName);
1048
+ const header = this.getHeader(headerName);
1049
+ // 更新最上层窗口(智能判断,只在必要时重新计算)
1050
+ this.updateTopWindow(headerName, windowName, true);
1051
+ // 只有当前窗口是最上层时才显示
1052
+ const currentTopWindow = this._cacheHeaderTopWindow.get(headerName);
1053
+ if (currentTopWindow === windowName) {
1054
+ header._show(this.getHeaderUserData(windowName));
1055
+ }
1056
+ }
1057
+ /**
1058
+ * 隐藏指定窗口的header
1059
+ * @param windowName 窗口名
1060
+ */
1061
+ static hideHeader(windowName) {
1062
+ if (!this.hasHeader(windowName)) {
1063
+ return;
1064
+ }
1065
+ const headerName = this.getHeaderName(windowName);
1066
+ const header = this.getHeader(headerName);
1067
+ // 更新最上层窗口(会自动处理切换逻辑)
1068
+ this.updateTopWindow(headerName, windowName, false);
1069
+ // 如果切换到了新的窗口,传递新的userdata0
1070
+ if (this._cacheHeaderTopWindow.has(headerName)) {
1071
+ const newTopWindowName = this._cacheHeaderTopWindow.get(headerName);
1072
+ header._show(this.getHeaderUserData(newTopWindowName));
1073
+ }
1074
+ else if (header.isShowing()) {
1075
+ header._hide();
1076
+ }
1077
+ }
1078
+ /**
1079
+ * 释放窗口的header(减少引用计数)
1080
+ * @param windowName 窗口名
1081
+ */
1082
+ static releaseHeader(windowName) {
1083
+ if (!this.hasHeader(windowName)) {
1084
+ return;
1085
+ }
1086
+ const headerName = this.getHeaderName(windowName);
1087
+ // 减少引用计数
1088
+ const refCount = (this._refCounts.get(headerName) || 1) - 1;
1089
+ // 移除映射关系
1090
+ const windowsSet = this._headerWindowsMap.get(headerName);
1091
+ if (windowsSet) {
1092
+ windowsSet.delete(windowName);
1093
+ }
1094
+ // 清除窗口的header信息
1095
+ this._headerInfos.delete(windowName);
1096
+ const header = this.getHeader(headerName);
1097
+ // 如果引用计数为0,销毁header
1098
+ if (refCount === 0) {
1099
+ header._close();
1100
+ this._headers.delete(headerName);
1101
+ this._refCounts.delete(headerName);
1102
+ this._headerWindowsMap.delete(headerName);
1103
+ this._cacheHeaderTopWindow.delete(headerName);
1104
+ }
1105
+ else {
1106
+ // 更新引用计数
1107
+ this._refCounts.set(headerName, refCount);
1108
+ // 查找新的最上层窗口 并显示
1109
+ const newTopWindowName = this.findTopWindowForHeader(headerName, windowName);
1110
+ if (newTopWindowName) {
1111
+ this._cacheHeaderTopWindow.set(headerName, newTopWindowName);
1112
+ this.adjustHeaderPosition(headerName, newTopWindowName);
1113
+ header._show(this.getHeaderUserData(newTopWindowName));
1114
+ }
1115
+ else {
1116
+ // 没找到需要显示的 隐藏掉
1117
+ this._cacheHeaderTopWindow.delete(headerName);
1118
+ header.isShowing() && header._hide();
1119
+ }
1120
+ }
1121
+ }
1122
+ /**
1123
+ * 获取窗口关联的header
1124
+ * @param windowName 窗口名
1125
+ * @returns header实例,如果没有则返回null
1126
+ */
1127
+ static getHeaderByWindow(windowName) {
1128
+ if (!this.hasHeader(windowName)) {
1129
+ return null;
1130
+ }
1131
+ const headerName = this.getHeaderName(windowName);
1132
+ return this._headers.get(headerName) || null;
1133
+ }
1134
+ /**
1135
+ * 刷新窗口的header
1136
+ * 如果新的header和旧的header不是同一个,先释放旧的,再创建新的
1137
+ * 如果是同一个,更新userdata并重新显示
1138
+ * @param windowName 窗口名
1139
+ * @param newHeaderInfo 新的header信息
1140
+ */
1141
+ static refreshWindowHeader(windowName, newHeaderInfo) {
1142
+ const oldHeaderName = this.getHeaderName(windowName);
1143
+ const newHeaderName = newHeaderInfo === null || newHeaderInfo === void 0 ? void 0 : newHeaderInfo.name;
1144
+ // 情况1:新旧 header 名称相同,只需要更新 userdata 并重新显示
1145
+ if (oldHeaderName === newHeaderName) {
1146
+ if (newHeaderInfo) {
1147
+ // 更新保存的 userdata
1148
+ this._headerInfos.set(windowName, newHeaderInfo);
1149
+ // 重新显示 header(如果当前窗口是最上层)
1150
+ const currentTopWindow = this._cacheHeaderTopWindow.get(newHeaderName);
1151
+ if (currentTopWindow === windowName) {
1152
+ const header = this.getHeader(newHeaderName);
1153
+ header._show(newHeaderInfo.userdata);
1154
+ }
1155
+ }
1156
+ return;
1157
+ }
1158
+ // 情况2:header 名称不同,先释放旧的,再创建新的
1159
+ // 释放旧的 header
1160
+ if (oldHeaderName) {
1161
+ this.releaseHeader(windowName);
1162
+ }
1163
+ // 请求新的 header 并显示
1164
+ if (newHeaderInfo) {
1165
+ this.requestHeader(windowName, newHeaderInfo);
1166
+ this.showHeader(windowName);
1167
+ }
1168
+ }
1169
+ static createHeader(headerInfo) {
1170
+ // 创建header实例
1171
+ const info = InfoPool.getHeader(headerInfo.name);
1172
+ const header = fairyguiCc.UIPackage.createObject(info.pkgName, headerInfo.name);
1173
+ header.name = headerInfo.name;
1174
+ PropsHelper.serializeProps(header, info.pkgName);
1175
+ header._init();
1176
+ header._adapted();
1177
+ return header;
1178
+ }
1179
+ /**
1180
+ * 获取窗口记录的header userdata
1181
+ * @internal
1182
+ */
1183
+ static getHeaderUserData(windowName) {
1184
+ var _a;
1185
+ return (_a = this._headerInfos.get(windowName)) === null || _a === void 0 ? void 0 : _a.userdata;
1186
+ }
1187
+ /**
1188
+ * 根据窗口名字获取header名称
1189
+ * @internal
1190
+ */
1191
+ static getHeaderName(windowName) {
1192
+ var _a;
1193
+ return (_a = this._headerInfos.get(windowName)) === null || _a === void 0 ? void 0 : _a.name;
1194
+ }
1195
+ /**
1196
+ * 窗口上是否存在header
1197
+ * @internal
1198
+ */
1199
+ static hasHeader(windowName) {
1200
+ return this._headerInfos.has(windowName);
1201
+ }
1202
+ /** 通过名称直接获取header实例 */
1203
+ static getHeader(name) {
1204
+ return this._headers.get(name);
1205
+ }
1206
+ /**
1207
+ * 更新header的最上层窗口(智能判断是否需要重新计算)
1208
+ * @internal
1209
+ */
1210
+ static updateTopWindow(headerName, changedWindowName, isShowing) {
1211
+ // 记录的最上层窗口名
1212
+ const topWindowName = this._cacheHeaderTopWindow.get(headerName);
1213
+ if (isShowing) {
1214
+ // 情况1:新窗口显示,且它在更高层级
1215
+ if (!topWindowName ||
1216
+ this.isWindowAbove(changedWindowName, topWindowName)) {
1217
+ // 新窗口更靠上, 调整header位置并缓存
1218
+ this._cacheHeaderTopWindow.set(headerName, changedWindowName);
1219
+ this.adjustHeaderPosition(headerName, changedWindowName);
1220
+ return;
1221
+ }
1222
+ }
1223
+ else if (topWindowName === changedWindowName) {
1224
+ // 最上层的窗口需要隐藏, 查找新的最上层窗口
1225
+ const newTopWindowName = this.findTopWindowForHeader(headerName, changedWindowName);
1226
+ if (newTopWindowName) {
1227
+ this._cacheHeaderTopWindow.set(headerName, newTopWindowName);
1228
+ this.adjustHeaderPosition(headerName, newTopWindowName);
1229
+ }
1230
+ else {
1231
+ this._cacheHeaderTopWindow.delete(headerName);
1232
+ const header = this.getHeader(headerName);
1233
+ if (header && header.isShowing()) {
1234
+ header._hide();
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ /**
1240
+ * 判断窗口A是否在窗口B的上层 (如果是同一个窗口,返回false)
1241
+ * @internal
1242
+ */
1243
+ static isWindowAbove(windowA, windowB) {
1244
+ if (windowA === windowB) {
1245
+ return true;
1246
+ }
1247
+ const infoA = InfoPool.get(windowA);
1248
+ const infoB = InfoPool.get(windowB);
1249
+ // 先比较窗口组
1250
+ const groupNames = WindowManager.getGroupNames();
1251
+ const groupIndexA = groupNames.indexOf(infoA.group);
1252
+ const groupIndexB = groupNames.indexOf(infoB.group);
1253
+ if (groupIndexA !== groupIndexB) {
1254
+ return groupIndexA > groupIndexB;
1255
+ }
1256
+ // 同一个组,比较窗口在组内的位置
1257
+ const group = WindowManager.getWindowGroup(infoA.group);
1258
+ const indexA = group.windowNames.indexOf(windowA);
1259
+ const indexB = group.windowNames.indexOf(windowB);
1260
+ return indexA > indexB;
1261
+ }
1262
+ /**
1263
+ * 为header查找新的最上层窗口(排除指定窗口)
1264
+ * @internal
1265
+ */
1266
+ static findTopWindowForHeader(headerName, excludeWindow) {
1267
+ const windowNames = this._headerWindowsMap.get(headerName);
1268
+ if (!windowNames || windowNames.size === 0) {
1269
+ return null;
1270
+ }
1271
+ // 从上到下遍历窗口组
1272
+ const groupNames = WindowManager.getGroupNames();
1273
+ for (let i = groupNames.length - 1; i >= 0; i--) {
1274
+ // 从上到下遍历组内窗口
1275
+ const group = WindowManager.getWindowGroup(groupNames[i]);
1276
+ for (let j = group.windowNames.length - 1; j >= 0; j--) {
1277
+ const wName = group.windowNames[j];
1278
+ if (wName === excludeWindow) {
1279
+ continue;
1280
+ }
1281
+ if (!windowNames.has(wName)) {
1282
+ continue;
1283
+ }
1284
+ const win = WindowManager.getWindow(wName);
1285
+ if (win && win.isShowing()) {
1286
+ return wName; // 找到就返回,不继续遍历
1287
+ }
1288
+ }
1289
+ }
1290
+ return null;
1291
+ }
1292
+ /**
1293
+ * 调整header到指定窗口的位置
1294
+ * @internal
1295
+ */
1296
+ static adjustHeaderPosition(headerName, windowName) {
1297
+ const header = this._headers.get(headerName);
1298
+ const window = WindowManager.getWindow(windowName);
1299
+ const info = InfoPool.get(windowName);
1300
+ const group = WindowManager.getWindowGroup(info.group);
1301
+ const parent = group.root;
1302
+ // 移动header到对应的group
1303
+ if (header.parent !== parent) {
1304
+ header.removeFromParent();
1305
+ parent.addChild(header);
1306
+ }
1307
+ // 调整层级:找到该组中最上层的显示窗口
1308
+ let maxWindowIndex = parent.getChildIndex(window);
1309
+ for (let i = group.windowNames.length - 1; i >= 0; i--) {
1310
+ const win = WindowManager.getWindow(group.windowNames[i]);
1311
+ if (win && win.isShowing()) {
1312
+ maxWindowIndex = Math.max(maxWindowIndex, parent.getChildIndex(win));
1313
+ }
1314
+ }
1315
+ parent.setChildIndex(header, maxWindowIndex + 1);
1316
+ }
1317
+ }
1318
+ /** @internal */
1319
+ HeaderManager._headers = new Map(); // header名 > header实例
1320
+ /** @internal */
1321
+ HeaderManager._refCounts = new Map(); // header名 > 引用计数
1322
+ /** @internal */
1323
+ HeaderManager._headerWindowsMap = new Map(); // header名 > 窗口名列表
1324
+ /** @internal */
1325
+ HeaderManager._headerInfos = new Map(); // 窗口名 > header的数据
1326
+ /** @internal */
1327
+ HeaderManager._cacheHeaderTopWindow = new Map(); // header名 > 缓存的当前显示该header的最上层窗口名
1328
+
1329
+ /**
1330
+ * @Description: 窗口组 (在同一个窗口容器的上的窗口)
1331
+ */
1332
+ class WindowGroup {
1333
+ /**
1334
+ * 获取窗口组的名称。
1335
+ * @returns {string} 窗口组的名称。
1336
+ */
1337
+ get name() {
1338
+ return this._name;
1339
+ }
1340
+ /** 获取窗口组的根节点 */
1341
+ get root() {
1342
+ return this._root;
1343
+ }
1344
+ /**
1345
+ * 获取当前窗口组中窗口的数量。
1346
+ * @returns 窗口数量
1347
+ */
1348
+ get size() {
1349
+ return this._windowNames.length;
1350
+ }
1351
+ /** 获取窗口组中窗口的名称列表 */
1352
+ get windowNames() {
1353
+ return this._windowNames;
1354
+ }
1355
+ /**
1356
+ * 获取是否忽略查询的状态。
1357
+ * @returns {boolean} 如果忽略查询,则返回 true,否则返回 false。
1358
+ */
1359
+ get isIgnore() {
1360
+ return this._ignore;
1361
+ }
1362
+ /**
1363
+ * 实例化
1364
+ * @param name 组名
1365
+ * @param root 窗口组的根节点 一个fgui的组件
1366
+ * @param ignoreQuery 是否忽略顶部窗口查询
1367
+ * @param swallowTouch 是否吞掉触摸事件
1368
+ * @param bgAlpha 半透明遮罩的透明度
1369
+ * @internal
1370
+ */
1371
+ constructor(name, root, ignoreQuery, swallowTouch) {
1372
+ /** @internal */
1373
+ this._name = ""; // 窗口组的名字
1374
+ /** @internal */
1375
+ this._ignore = false; // 忽略查询
1376
+ /** @internal */
1377
+ this._swallowTouch = false; // 吞噬触摸事件
1378
+ /** @internal */
1379
+ this._windowNames = []; // 窗口名列表 顺序为窗口显示的层级 (最后一个显示在最上层)
1380
+ this._name = name;
1381
+ this._root = root;
1382
+ this._ignore = ignoreQuery;
1383
+ this._swallowTouch = swallowTouch;
1384
+ this._windowNames = [];
1385
+ }
1386
+ /**
1387
+ * 显示一个窗口
1388
+ * @param info 窗口信息
1389
+ * @param userdata
1390
+ * @internal
1391
+ */
1392
+ showWindow(info, userdata) {
1393
+ return __awaiter(this, void 0, void 0, function* () {
1394
+ let lastTopWindow = WindowManager.getTopWindow();
1395
+ if (WindowManager.hasWindow(info.name)) {
1396
+ const window = WindowManager.getWindow(info.name);
1397
+ this.showAdjustment(window, userdata);
1398
+ if (lastTopWindow && lastTopWindow.name !== window.name) {
1399
+ lastTopWindow._toBottom();
1400
+ window._toTop();
1401
+ }
1402
+ return window;
1403
+ }
1404
+ else {
1405
+ try {
1406
+ yield ResLoader.loadWindowRes(info.name);
1407
+ const window = this.createWindow(info.pkgName, info.name);
1408
+ this.showAdjustment(window, userdata);
1409
+ if (lastTopWindow && lastTopWindow.name !== window.name) {
1410
+ lastTopWindow._toBottom();
1411
+ }
1412
+ return window;
1413
+ }
1414
+ catch (err) {
1415
+ throw new Error(`窗口【${info.name}】打开失败: ${err.message}`);
1416
+ }
1417
+ }
1418
+ });
1419
+ }
1420
+ /**
1421
+ * show一个界面后的调整
1422
+ * @param window
1423
+ * @internal
1424
+ */
1425
+ showAdjustment(window, userdata) {
1426
+ // 如果窗口不在最上层 则调整层级
1427
+ this.moveWindowToTop(window);
1428
+ // 显示窗口
1429
+ window._show(userdata);
1430
+ // 尝试显示header
1431
+ HeaderManager.showHeader(window.name);
1432
+ // 调整半透明遮罩
1433
+ WindowManager.adjustAlphaGraph();
1434
+ }
1435
+ /**
1436
+ * 将指定名称的窗口移动到窗口组的最顶层。
1437
+ * @param name 窗口的名称。
1438
+ * @internal
1439
+ */
1440
+ moveWindowToTop(window) {
1441
+ if (window.name !== this._windowNames[this.size - 1]) {
1442
+ const index = this._windowNames.indexOf(window.name);
1443
+ if (index < 0) {
1444
+ console.error(`[BUG] 窗口【${window.name}】不在数组中,数据结构已损坏`);
1445
+ return;
1446
+ }
1447
+ this._windowNames.splice(index, 1);
1448
+ // 放到数组的末尾
1449
+ this._windowNames.push(window.name);
1450
+ }
1451
+ // 根据窗口的type, 处理上一个窗口的关闭状态
1452
+ this._processWindowCloseStatus(window);
1453
+ // 调整窗口的显示层级
1454
+ window.setDepth(this._root.numChildren - 1);
1455
+ // 处理窗口显示和隐藏状态
1456
+ this.processWindowHideStatus(this.size - 1);
1457
+ }
1458
+ /**
1459
+ * 根据窗口名创建窗口 并添加到显示节点
1460
+ * @param windowName 窗口名
1461
+ * @internal
1462
+ */
1463
+ createWindow(pkg, name) {
1464
+ let window = fairyguiCc.UIPackage.createObject(pkg, name);
1465
+ window.name = name;
1466
+ PropsHelper.serializeProps(window, pkg);
1467
+ window._init(this._swallowTouch);
1468
+ window._adapted();
1469
+ // 添加到显示节点
1470
+ this._root.addChild(window);
1471
+ // 窗口组之前没有窗口, 显示窗口组节点
1472
+ if (this.size === 0) {
1473
+ this._root.visible = true;
1474
+ }
1475
+ this._windowNames.push(name);
1476
+ WindowManager.addWindow(name, window);
1477
+ // 处理header(只请求,不立即添加到节点,由adjustHeaderDepth动态管理)
1478
+ HeaderManager.requestHeader(name, window.getHeaderInfo());
1479
+ return window;
1480
+ }
1481
+ /**
1482
+ * 处理index下层窗口的隐藏状态的私有方法。递归调用
1483
+ * @param index - 窗口索引
1484
+ */
1485
+ processWindowHideStatus(startIndex) {
1486
+ let curWindow = WindowManager.getWindow(this._windowNames[startIndex]);
1487
+ // 如果当前是当前组中的最后一个窗口并且当前窗口是隐藏状态 则恢复隐藏
1488
+ if (curWindow && startIndex == this.size - 1 && !curWindow.isShowing()) {
1489
+ curWindow._showFromHide();
1490
+ // 恢复显示header
1491
+ HeaderManager.showHeader(curWindow.name);
1492
+ }
1493
+ // 已经是最后一个了
1494
+ if (startIndex <= 0) {
1495
+ return;
1496
+ }
1497
+ // 遍历到倒数第二个窗口停止
1498
+ for (let index = startIndex; index > 0; index--) {
1499
+ let window = WindowManager.getWindow(this._windowNames[index]);
1500
+ if (!window) {
1501
+ console.error(`[BUG] 窗口【${this._windowNames[index]}】不存在,数据结构已损坏`);
1502
+ return;
1503
+ }
1504
+ if (window.type === exports.WindowType.HideAll) {
1505
+ // 隐藏所有
1506
+ for (let i = index - 1; i >= 0; i--) {
1507
+ let name = this._windowNames[i];
1508
+ const window = WindowManager.getWindow(name);
1509
+ if (window && window.isShowing()) {
1510
+ window._hide();
1511
+ // 隐藏header
1512
+ HeaderManager.hideHeader(name);
1513
+ }
1514
+ }
1515
+ break;
1516
+ }
1517
+ else if (window.type === exports.WindowType.HideOne) {
1518
+ // 隐藏前一个窗口
1519
+ let prevWindowName = this._windowNames[index - 1];
1520
+ let prevWindow = WindowManager.getWindow(prevWindowName);
1521
+ if (prevWindow && prevWindow.isShowing()) {
1522
+ prevWindow._hide();
1523
+ // 隐藏header
1524
+ HeaderManager.hideHeader(prevWindowName);
1525
+ }
1526
+ }
1527
+ else {
1528
+ // 如果前一个窗口是隐藏状态 需要恢复显示
1529
+ let prevWindowName = this._windowNames[index - 1];
1530
+ let prevWindow = WindowManager.getWindow(prevWindowName);
1531
+ if (prevWindow && !prevWindow.isShowing()) {
1532
+ prevWindow._showFromHide();
1533
+ // 恢复显示header(使用记录的userdata)
1534
+ HeaderManager.showHeader(prevWindowName);
1535
+ }
1536
+ }
1537
+ }
1538
+ }
1539
+ /**
1540
+ * 根据传入窗口的关闭类型, 处理上一个窗口或者所有窗口的关闭
1541
+ * @param window 新创建的窗口
1542
+ * @internal
1543
+ */
1544
+ _processWindowCloseStatus(window) {
1545
+ // 最后一个是新显示上来的窗口
1546
+ if (window.type === exports.WindowType.CloseOne) {
1547
+ // 关闭上一个窗口(倒数第二个,因为最后一个是当前窗口)
1548
+ // 修复:明确检查边界,避免数组越界
1549
+ if (this.size < 2) {
1550
+ return;
1551
+ }
1552
+ const name = this._windowNames[this.size - 2];
1553
+ this._windowNames.splice(this.size - 2, 1);
1554
+ const win = WindowManager.getWindow(name);
1555
+ if (!win) {
1556
+ console.error(`[BUG] 窗口【${name}】不存在,数据结构已损坏`);
1557
+ return;
1558
+ }
1559
+ // 释放header
1560
+ HeaderManager.releaseHeader(name);
1561
+ win._close();
1562
+ WindowManager.removeWindow(name);
1563
+ }
1564
+ else if (window.type === exports.WindowType.CloseAll) {
1565
+ // 关闭所有窗口 从后向前依次删除
1566
+ // 修复:添加异常保护,避免清理异常导致数据不一致
1567
+ for (let i = this.size - 2; i >= 0; i--) {
1568
+ const name = this._windowNames[i];
1569
+ const win = WindowManager.getWindow(name);
1570
+ if (!win) {
1571
+ console.error(`[BUG] 窗口【${name}】不存在,数据结构已损坏`);
1572
+ continue; // 继续处理其他窗口,而不是直接返回
1573
+ }
1574
+ try {
1575
+ // 释放header
1576
+ HeaderManager.releaseHeader(name);
1577
+ win._close();
1578
+ WindowManager.removeWindow(name);
1579
+ }
1580
+ catch (err) {
1581
+ console.error(`关闭窗口【${name}】时发生异常:`, err);
1582
+ // 即使出错也继续处理其他窗口
1583
+ }
1584
+ }
1585
+ // 清理数组,只保留最后一个(当前窗口)
1586
+ this._windowNames.splice(0, this.size - 1);
1587
+ }
1588
+ }
1589
+ /**
1590
+ * 移除指定名称的窗口。
1591
+ * @param name 窗口的名称。
1592
+ * @internal
1593
+ */
1594
+ removeWindow(name) {
1595
+ let window = WindowManager.getWindow(name);
1596
+ if (!window) {
1597
+ console.error(`[BUG] 窗口【${name}】不存在,数据结构已损坏`);
1598
+ return;
1599
+ }
1600
+ // 释放header引用
1601
+ HeaderManager.releaseHeader(name);
1602
+ window._close();
1603
+ // 先删除窗口组中记录的窗口名称
1604
+ let index = this._windowNames.lastIndexOf(name);
1605
+ if (index < 0) {
1606
+ console.error(`[BUG] 窗口【${name}】不在数组中,数据结构已损坏`);
1607
+ return;
1608
+ }
1609
+ this._windowNames.splice(index, 1);
1610
+ // 删除WindowManager中记录的窗口
1611
+ WindowManager.removeWindow(name);
1612
+ // 释放资源
1613
+ ResLoader.unloadWindowRes(name);
1614
+ if (this.size == 0) {
1615
+ this._root.visible = false;
1616
+ }
1617
+ else {
1618
+ this.processWindowHideStatus(this.size - 1);
1619
+ }
1620
+ }
1621
+ hasWindow(name) {
1622
+ return this._windowNames.indexOf(name) >= 0;
1623
+ }
1624
+ /**
1625
+ * 获取窗口组顶部窗口实例
1626
+ * @returns {IWindow} 顶部窗口实例
1627
+ */
1628
+ getTopWindow() {
1629
+ if (this.size > 0) {
1630
+ return WindowManager.getWindow(this._windowNames[this.size - 1]);
1631
+ }
1632
+ console.warn(`窗口组【${this._name}】中不存在窗口`);
1633
+ return null;
1634
+ }
1635
+ /**
1636
+ * 关闭窗口组中的所有窗口
1637
+ * @param ignores 不关闭的窗口名
1638
+ * @internal
1639
+ */
1640
+ closeAllWindow(ignores = []) {
1641
+ let len = this.size - 1;
1642
+ for (let i = len; i >= 0; i--) {
1643
+ let name = this._windowNames[i];
1644
+ // 如果当前窗口在ignores列表中,跳过
1645
+ if (ignores.some((ignore) => ignore.name === name)) {
1646
+ continue;
1647
+ }
1648
+ const window = WindowManager.getWindow(name);
1649
+ if (!window) {
1650
+ console.error(`[BUG] 窗口【${name}】不存在,数据结构已损坏`);
1651
+ return;
1652
+ }
1653
+ // 释放header
1654
+ HeaderManager.releaseHeader(name);
1655
+ window._close();
1656
+ WindowManager.removeWindow(name);
1657
+ // 从数组中删除
1658
+ this._windowNames.splice(i, 1);
1659
+ }
1660
+ // 统一处理窗口的显示状态
1661
+ if (this.size == 0) {
1662
+ this._root.visible = false;
1663
+ }
1664
+ else {
1665
+ this.processWindowHideStatus(this.size - 1);
1666
+ }
1667
+ }
1668
+ }
1669
+
1670
+ /**
1671
+ * @Description: UI 装饰器
1672
+ */
1673
+ /**
1674
+ * 获取对象属性
1675
+ * @param obj 对象
1676
+ * @param key 属性名
1677
+ * @returns 属性值
1678
+ */
1679
+ function getObjectProp(obj, key) {
1680
+ if (obj.hasOwnProperty(key)) {
1681
+ return obj[key];
1682
+ }
1683
+ return (obj[key] = Object.assign({}, obj[key]));
1684
+ }
1685
+ exports._uidecorator = void 0;
1686
+ (function (_uidecorator) {
1687
+ /** @internal */
1688
+ const uiclassMap = new Map(); // 窗口注册信息
1689
+ /** @internal */
1690
+ const uicomponentMap = new Map(); // 组件注册信息
1691
+ /** @internal */
1692
+ const uiheaderMap = new Map(); // header注册信息
1693
+ /** 获取窗口注册信息 */
1694
+ function getWindowMaps() {
1695
+ return uiclassMap;
1696
+ }
1697
+ _uidecorator.getWindowMaps = getWindowMaps;
1698
+ /** 获取组件注册信息 */
1699
+ function getComponentMaps() {
1700
+ return uicomponentMap;
1701
+ }
1702
+ _uidecorator.getComponentMaps = getComponentMaps;
1703
+ /** 获取header注册信息 */
1704
+ function getHeaderMaps() {
1705
+ return uiheaderMap;
1706
+ }
1707
+ _uidecorator.getHeaderMaps = getHeaderMaps;
1708
+ /**
1709
+ * 窗口装饰器
1710
+ * @param {string} groupName 窗口组名称
1711
+ * @param {string} pkgName fgui包名
1712
+ * @param {string} name 窗口名 (与fgui中的组件名一一对应)
1713
+ * @param {string[] | string} inlinePkgs 内联的包名 当前界面需要引用其他包中的资源时使用 引用多个包用数组 引用单个包用字符串
1714
+ *
1715
+ * @example @uiclass("窗口组", "UI包名", "MyWindow", ["包名1", "包名2"])
1716
+ * @example @uiclass("窗口组", "UI包名", "MyWindow", "包名1")
1717
+ */
1718
+ function uiclass(groupName, pkgName, name, inlinePkgs) {
1719
+ /** target 类的构造函数 */
1720
+ return function (ctor) {
1721
+ // 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
1722
+ const originalCtor = ctor;
1723
+ // 给构造函数添加静态属性,存储窗口名称(避免混淆后 constructor.name 变化)
1724
+ ctor[MetadataKey.originalName] = name;
1725
+ uiclassMap.set(originalCtor, {
1726
+ ctor: ctor, // 存储实际的构造函数(可能被包装过)
1727
+ props: ctor[MetadataKey.prop] || null,
1728
+ callbacks: ctor[MetadataKey.callback] || null,
1729
+ controls: ctor[MetadataKey.control] || null,
1730
+ transitions: ctor[MetadataKey.transition] || null,
1731
+ res: {
1732
+ group: groupName,
1733
+ pkg: pkgName,
1734
+ name: name,
1735
+ },
1736
+ });
1737
+ let pkgs = [];
1738
+ if (Array.isArray(inlinePkgs)) {
1739
+ pkgs = inlinePkgs;
1740
+ }
1741
+ else if (typeof inlinePkgs === "string") {
1742
+ pkgs = [inlinePkgs];
1743
+ }
1744
+ InfoPool.add(ctor, groupName, pkgName, name, pkgs);
1745
+ return ctor;
1746
+ };
1747
+ }
1748
+ _uidecorator.uiclass = uiclass;
1749
+ /**
1750
+ * UI组件装饰器
1751
+ * @param {string} pkg 包名
1752
+ * @param {string} name 组件名
1753
+ */
1754
+ function uicom(pkg, name) {
1755
+ return function (ctor) {
1756
+ // 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
1757
+ const originalCtor = ctor;
1758
+ // log(`pkg:【${pkg}】 uicom prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
1759
+ ctor[MetadataKey.originalName] = name;
1760
+ uicomponentMap.set(originalCtor, {
1761
+ ctor: ctor, // 存储实际的构造函数(可能被包装过)
1762
+ props: ctor[MetadataKey.prop] || null,
1763
+ callbacks: ctor[MetadataKey.callback] || null,
1764
+ controls: ctor[MetadataKey.control] || null,
1765
+ transitions: ctor[MetadataKey.transition] || null,
1766
+ res: {
1767
+ pkg: pkg,
1768
+ name: name,
1769
+ },
1770
+ });
1771
+ InfoPool.addComponent(ctor, pkg, name);
1772
+ return ctor;
1773
+ };
1774
+ }
1775
+ _uidecorator.uicom = uicom;
1776
+ /**
1777
+ * UI header装饰器
1778
+ * @param {string} pkg 包名
1779
+ * @param {string} name 组件名
1780
+ */
1781
+ function uiheader(pkg, name) {
1782
+ return function (ctor) {
1783
+ // 检查是否有原始构造函数引用(由其他装饰器如 @dataclass 提供)
1784
+ const originalCtor = ctor;
1785
+ // log(`pkg:【${pkg}】 uiheader prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
1786
+ ctor[MetadataKey.originalName] = name;
1787
+ uiheaderMap.set(originalCtor, {
1788
+ ctor: ctor, // 存储实际的构造函数(可能被包装过)
1789
+ props: ctor[MetadataKey.prop] || null,
1790
+ callbacks: ctor[MetadataKey.callback] || null,
1791
+ controls: ctor[MetadataKey.control] || null,
1792
+ transitions: ctor[MetadataKey.transition] || null,
1793
+ res: {
1794
+ pkg: pkg,
1795
+ name: name,
1796
+ },
1797
+ });
1798
+ InfoPool.addHeader(ctor, pkg, name);
1799
+ return ctor;
1800
+ };
1801
+ }
1802
+ _uidecorator.uiheader = uiheader;
1803
+ /**
1804
+ * UI属性装饰器
1805
+ * @param {Object} target 实例成员的类的原型
1806
+ * @param {string} name 属性名
1807
+ *
1808
+ * example: @uiprop node: GObject
1809
+ */
1810
+ function uiprop(target, name) {
1811
+ // debug("属性装饰器:", target.constructor, name);
1812
+ getObjectProp(target.constructor, MetadataKey.prop)[name] = 1;
1813
+ }
1814
+ _uidecorator.uiprop = uiprop;
1815
+ /**
1816
+ * UI控制器装饰器
1817
+ * @param {Object} target 实例成员的类的原型
1818
+ * @param {string} name 属性名
1819
+ *
1820
+ * example: @uicontrol node: GObject
1821
+ */
1822
+ function uicontrol(target, name) {
1823
+ // debug("属性装饰器:", target.constructor, name);
1824
+ getObjectProp(target.constructor, MetadataKey.control)[name] = 1;
1825
+ }
1826
+ _uidecorator.uicontrol = uicontrol;
1827
+ /**
1828
+ * UI动画装饰器
1829
+ * @param {Object} target 实例成员的类的原型
1830
+ * @param {string} name 属性名
1831
+ *
1832
+ * example: @uitransition node: GObject
1833
+ */
1834
+ function uitransition(target, name) {
1835
+ // debug("属性装饰器:", target.constructor, name);
1836
+ getObjectProp(target.constructor, MetadataKey.transition)[name] = 1;
1837
+ }
1838
+ _uidecorator.uitransition = uitransition;
1839
+ /**
1840
+ * 方法装饰器 (给点击事件用)
1841
+ * @param {Object} target 实例成员的类的原型
1842
+ * @param {string} name 方法名
1843
+ */
1844
+ function uiclick(target, name, descriptor) {
1845
+ // debug("方法装饰器:", target.constructor, name, descriptor);
1846
+ getObjectProp(target.constructor, MetadataKey.callback)[name] =
1847
+ descriptor.value;
1848
+ }
1849
+ _uidecorator.uiclick = uiclick;
1850
+ })(exports._uidecorator || (exports._uidecorator = {}));
1851
+ const _global = (globalThis || window || global);
1852
+ _global["getKunpoRegisterWindowMaps"] = function () {
1853
+ return exports._uidecorator.getWindowMaps();
1854
+ };
1855
+ _global["getKunpoRegisterComponentMaps"] = function () {
1856
+ return exports._uidecorator.getComponentMaps();
1857
+ };
1858
+ _global["getKunpoRegisterHeaderMaps"] = function () {
1859
+ return exports._uidecorator.getHeaderMaps();
1860
+ };
1861
+
1862
+ /**
1863
+ * @Description: 窗口顶边栏
1864
+ * 窗口顶边资源栏 同组中只会有一个显示
1865
+ */
1866
+ class Header extends fairyguiCc.GComponent {
1867
+ constructor() {
1868
+ super(...arguments);
1869
+ /** 窗口适配类型 */
1870
+ this.adapterType = exports.AdapterType.Full;
1871
+ }
1872
+ onAdapted() { }
1873
+ onClose() { }
1874
+ onHide() { }
1875
+ onShowFromHide() { }
1876
+ /**
1877
+ * 是否显示中
1878
+ */
1879
+ isShowing() {
1880
+ return this.visible;
1881
+ }
1882
+ /**
1883
+ * 初始化 (内部方法)
1884
+ * @internal
1885
+ */
1886
+ _init() {
1887
+ this.opaque = false;
1888
+ this.onInit();
1889
+ }
1890
+ /**
1891
+ * 关闭 (内部方法)
1892
+ * @internal
1893
+ */
1894
+ _close() {
1895
+ this.onClose();
1896
+ this.dispose();
1897
+ }
1898
+ /**
1899
+ * 窗口适配
1900
+ * @internal
1901
+ */
1902
+ _adapted() {
1903
+ this.setPosition(exiaCore.Screen.ScreenWidth * 0.5, exiaCore.Screen.ScreenHeight * 0.5);
1904
+ this.setPivot(0.5, 0.5, true);
1905
+ switch (this.adapterType) {
1906
+ case exports.AdapterType.Full:
1907
+ this.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
1908
+ break;
1909
+ case exports.AdapterType.Bang:
1910
+ this.setSize(exiaCore.Screen.SafeWidth, exiaCore.Screen.SafeHeight, true);
1911
+ break;
1912
+ }
1913
+ this.onAdapted();
1914
+ }
1915
+ /**
1916
+ * 显示
1917
+ * @param userdata 用户数据
1918
+ * @internal
1919
+ */
1920
+ _show(userdata) {
1921
+ this.visible = true;
1922
+ this.onShow(userdata);
1923
+ }
1924
+ /**
1925
+ * 隐藏
1926
+ * @internal
1927
+ */
1928
+ _hide() {
1929
+ this.visible = false;
1930
+ this.onHide();
1931
+ }
1932
+ }
1933
+
1934
+ /**
1935
+ * @Description: 窗口顶部资源栏信息
1936
+ */
1937
+ class HeaderInfo {
1938
+ /**
1939
+ * 创建 HeaderInfo (类型安全)
1940
+ * @param ctor Header类构造函数
1941
+ * @param userdata 自定义数据
1942
+ * @returns {HeaderInfo}
1943
+ */
1944
+ static create(ctor, userdata) {
1945
+ // 优先使用装饰器设置的静态属性,避免代码混淆后 constructor.name 变化
1946
+ const name = ctor[MetadataKey.originalName];
1947
+ if (!name) {
1948
+ throw new Error(`header【${ctor.name}】未注册,请使用 _uidecorator.uiheader 注册header`);
1949
+ }
1950
+ const info = new HeaderInfo();
1951
+ info.name = name;
1952
+ info.userdata = userdata;
1953
+ return info;
1954
+ }
1955
+ /**
1956
+ * 通过名称创建 HeaderInfo (非类型安全)
1957
+ * @param name header名称
1958
+ * @param userdata 自定义数据
1959
+ * @returns {HeaderInfo}
1960
+ */
1961
+ static createByName(name, userdata) {
1962
+ const info = new HeaderInfo();
1963
+ info.name = name;
1964
+ info.userdata = userdata;
1965
+ return info;
1966
+ }
1967
+ }
1968
+
1969
+ /**
1970
+ * @Description: 窗口基类和fgui组件对接
1971
+ */
1972
+ class WindowBase extends fairyguiCc.GComponent {
1973
+ constructor() {
1974
+ super(...arguments);
1975
+ /** 窗口类型 */
1976
+ this.type = exports.WindowType.Normal;
1977
+ /** 窗口适配类型 */
1978
+ this.adapterType = exports.AdapterType.Full;
1979
+ /** @internal */
1980
+ this._swallowNode = null; // 吞噬触摸的节点
1981
+ /** @internal */
1982
+ this._isTop = true;
1983
+ }
1984
+ /**
1985
+ * 初始化方法 (框架内部使用)
1986
+ * @param swallowTouch 是否吞噬触摸事件
1987
+ * @param bgAlpha 底部遮罩的透明度
1988
+ * @internal
1989
+ */
1990
+ _init(swallowTouch) {
1991
+ // 窗口本身可能留有安全区的边, 所以需要一个全屏的节点来吞噬触摸事件
1992
+ let bgNode = new fairyguiCc.GComponent();
1993
+ bgNode.name = "swallow";
1994
+ bgNode.setPivot(0.5, 0.5, true);
1995
+ this.addChild(bgNode);
1996
+ bgNode.parent.setChildIndex(bgNode, 0); // 调整显示层级
1997
+ bgNode.onClick(this.onEmptyAreaClick, this); // 空白区域点击事件
1998
+ bgNode.opaque = swallowTouch;
1999
+ this._swallowNode = bgNode;
2000
+ // 窗口自身也要设置是否吞噬触摸
2001
+ this.opaque = swallowTouch;
2002
+ this._isTop = true;
2003
+ this.bgAlpha = WindowManager.bgAlpha;
2004
+ this.onInit();
2005
+ }
2006
+ /**
2007
+ * 适配窗口
2008
+ * @internal
2009
+ */
2010
+ _adapted() {
2011
+ this.setPosition(exiaCore.Screen.ScreenWidth * 0.5, exiaCore.Screen.ScreenHeight * 0.5);
2012
+ this.setPivot(0.5, 0.5, true);
2013
+ switch (this.adapterType) {
2014
+ case exports.AdapterType.Full:
2015
+ this.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
2016
+ break;
2017
+ case exports.AdapterType.Bang:
2018
+ this.setSize(exiaCore.Screen.SafeWidth, exiaCore.Screen.SafeHeight, true);
2019
+ break;
2020
+ }
2021
+ // 屏幕的宽高
2022
+ this._swallowNode.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
2023
+ // 位置放在窗口的中心
2024
+ this._swallowNode.setPosition(this.width * 0.5, this.height * 0.5);
2025
+ this.onAdapted();
2026
+ }
2027
+ /**
2028
+ * 窗口关闭
2029
+ * @internal
2030
+ */
2031
+ _close() {
2032
+ this.onClose();
2033
+ this.dispose();
2034
+ }
2035
+ /**
2036
+ * 显示窗口 (框架内部使用)
2037
+ * @param userdata 用户自定义数据
2038
+ * @internal
2039
+ */
2040
+ _show(userdata) {
2041
+ this.visible = true;
2042
+ this.onShow(userdata);
2043
+ }
2044
+ /**
2045
+ * 隐藏窗口 (框架内部使用)
2046
+ * @internal
2047
+ */
2048
+ _hide() {
2049
+ this.visible = false;
2050
+ this.onHide();
2051
+ }
2052
+ /**
2053
+ * 从隐藏状态恢复显示
2054
+ * @internal
2055
+ */
2056
+ _showFromHide() {
2057
+ this.visible = true;
2058
+ this.onShowFromHide();
2059
+ }
2060
+ /**
2061
+ * 除忽略的窗口组外, 显示到最上层时
2062
+ * @internal
2063
+ */
2064
+ _toTop() {
2065
+ this._isTop = true;
2066
+ this.onToTop();
2067
+ }
2068
+ /**
2069
+ * 除忽略的窗口组外, 被上层窗口覆盖时
2070
+ * @internal
2071
+ */
2072
+ _toBottom() {
2073
+ this._isTop = false;
2074
+ this.onToBottom();
2075
+ }
2076
+ /**
2077
+ * 设置窗口深度
2078
+ * @param depth 深度
2079
+ * @internal
2080
+ */
2081
+ setDepth(depth) {
2082
+ this.parent.setChildIndex(this, depth);
2083
+ }
2084
+ isShowing() {
2085
+ return this.visible;
2086
+ }
2087
+ /** 是否在最上层显示 (除忽略的窗口组外, 显示到最上层时) */
2088
+ isTop() {
2089
+ return this._isTop;
2090
+ }
2091
+ /** @internal */
2092
+ screenResize() {
2093
+ this._adapted();
2094
+ }
2095
+ /**
2096
+ * 刷新顶部资源栏
2097
+ * 调用这个方法会重新创建 或者 刷新header
2098
+ * 用来在同一个界面显示不同的header
2099
+ */
2100
+ refreshHeader() {
2101
+ HeaderManager.refreshWindowHeader(this.name, this.getHeaderInfo());
2102
+ }
2103
+ /**
2104
+ * 用于在界面中关闭自己
2105
+ */
2106
+ removeSelf() {
2107
+ WindowManager.closeWindowByName(this.name);
2108
+ }
2109
+ }
2110
+
2111
+ class Window extends WindowBase {
2112
+ onAdapted() { }
2113
+ onHide() { }
2114
+ onShowFromHide() { }
2115
+ onToTop() { }
2116
+ onToBottom() { }
2117
+ /**
2118
+ * 空白区域点击事件处理函数。
2119
+ * 当用户点击窗口的空白区域时触发此方法。
2120
+ */
2121
+ onEmptyAreaClick() { }
2122
+ /**
2123
+ * 获取窗口顶部资源栏数据 默认返回空数组
2124
+ * @returns {HeaderInfo}
2125
+ */
2126
+ getHeaderInfo() {
2127
+ return null;
2128
+ }
2129
+ }
2130
+
2131
+ const { ccclass: ccclass$1, property: property$1, menu: menu$1 } = cc._decorator;
2132
+ let CocosWindowContainer = class CocosWindowContainer extends cc.Component {
2133
+ constructor() {
2134
+ super(...arguments);
2135
+ this.ignoreQuery = false;
2136
+ this.swallowTouch = false;
2137
+ }
2138
+ /**
2139
+ * 初始化窗口容器
2140
+ * @internal
2141
+ */
2142
+ init() {
2143
+ let name = this.node.name;
2144
+ exiaCore.debug(`\tUIContainer name:${name} 忽略顶部窗口查询:${this.ignoreQuery} 吞噬触摸事件:${this.swallowTouch}`);
2145
+ const root = new fairyguiCc.GComponent();
2146
+ root.name = name;
2147
+ root.node.name = name;
2148
+ root.visible = false;
2149
+ root.opaque = this.swallowTouch;
2150
+ root.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
2151
+ fairyguiCc.GRoot.inst.addChild(root);
2152
+ WindowManager.addWindowGroup(new WindowGroup(name, root, this.ignoreQuery, this.swallowTouch));
2153
+ }
2154
+ };
2155
+ __decorate([
2156
+ property$1({
2157
+ displayName: "忽略顶部窗口查询",
2158
+ tooltip: "当通过窗口管理器获取顶部窗口时,是否忽略查询",
2159
+ })
2160
+ ], CocosWindowContainer.prototype, "ignoreQuery", void 0);
2161
+ __decorate([
2162
+ property$1({
2163
+ displayName: "吞噬触摸事件",
2164
+ tooltip: "窗口组是否会吞噬触摸事件,防止层级下的窗口接收触摸事件",
2165
+ })
2166
+ ], CocosWindowContainer.prototype, "swallowTouch", void 0);
2167
+ CocosWindowContainer = __decorate([
2168
+ ccclass$1("CocosWindowContainer"),
2169
+ menu$1("exia/UIContainer")
2170
+ ], CocosWindowContainer);
2171
+
2172
+ const { ccclass, menu, property } = cc._decorator;
2173
+ exports.UIModule = class UIModule extends exiaCore.Module {
2174
+ constructor() {
2175
+ super(...arguments);
2176
+ this.ui_config = null;
2177
+ this.bgAlpha = 0.75;
2178
+ this.autoReleaseUIRes = true;
2179
+ /** 模块名称 */
2180
+ this.moduleName = "UI模块";
2181
+ }
2182
+ onInit() {
2183
+ this.ui_config &&
2184
+ PropsHelper.setConfig(this.ui_config.json);
2185
+ ResLoader.setAutoRelease(this.autoReleaseUIRes);
2186
+ // 设置底部遮罩的默认透明度
2187
+ WindowManager.bgAlpha = this.bgAlpha;
2188
+ /** 初始化窗口管理系统 */
2189
+ fairyguiCc.GRoot.create();
2190
+ exiaCore.debug("初始化 WindowContainers");
2191
+ const alphaGraph = new fairyguiCc.GGraph();
2192
+ alphaGraph.touchable = false;
2193
+ alphaGraph.name = "bgAlpha";
2194
+ alphaGraph.setPosition(exiaCore.Screen.ScreenWidth * 0.5, exiaCore.Screen.ScreenHeight * 0.5);
2195
+ alphaGraph.setSize(exiaCore.Screen.ScreenWidth, exiaCore.Screen.ScreenHeight, true);
2196
+ alphaGraph.setPivot(0.5, 0.5, true);
2197
+ alphaGraph.visible = false;
2198
+ fairyguiCc.GRoot.inst.addChild(alphaGraph);
2199
+ WindowManager.setAlphaGraph(alphaGraph);
2200
+ for (const container of this.getComponentsInChildren(CocosWindowContainer)) {
2201
+ container.init();
2202
+ }
2203
+ this.node.destroyAllChildren();
2204
+ exiaCore.Adapter.instance.addResizeListener(this.onScreenResize.bind(this));
2205
+ }
2206
+ /**
2207
+ * 屏幕大小改变时被调用
2208
+ * @internal
2209
+ */
2210
+ onScreenResize(...args) {
2211
+ WindowManager.onScreenResize();
2212
+ }
2213
+ };
2214
+ __decorate([
2215
+ property({
2216
+ type: cc.JsonAsset,
2217
+ displayName: "配置文件",
2218
+ tooltip: "编辑器导出的配置文件",
2219
+ })
2220
+ ], exports.UIModule.prototype, "ui_config", void 0);
2221
+ __decorate([
2222
+ property({
2223
+ displayName: "底部遮罩透明度",
2224
+ tooltip: "半透明遮罩的默认透明度",
2225
+ min: 0,
2226
+ max: 1,
2227
+ step: 0.01,
2228
+ })
2229
+ ], exports.UIModule.prototype, "bgAlpha", void 0);
2230
+ __decorate([
2231
+ property({
2232
+ displayName: "自动释放UI资源",
2233
+ tooltip: "界面关闭时自动释放加载的资源",
2234
+ })
2235
+ ], exports.UIModule.prototype, "autoReleaseUIRes", void 0);
2236
+ exports.UIModule = __decorate([
2237
+ ccclass("UIModule"),
2238
+ menu("exia/UIIModule")
2239
+ ], exports.UIModule);
2240
+
2241
+ exports.Header = Header;
2242
+ exports.HeaderInfo = HeaderInfo;
2243
+ exports.Window = Window;
2244
+ exports.WindowGroup = WindowGroup;
2245
+ exports.WindowManager = WindowManager;