first
This commit is contained in:
1617
extensions/app/assets/manager/ui/UIManager.ts
Normal file
1617
extensions/app/assets/manager/ui/UIManager.ts
Normal file
@@ -0,0 +1,1617 @@
|
||||
import { Asset, AssetManager, Camera, Canvas, Component, Event, Layers, Node, Prefab, RenderTexture, ResolutionPolicy, Scene, SceneAsset, Settings, UITransform, Widget, _decorator, director, instantiate, isValid, js, screen, settings, size, view } from 'cc';
|
||||
import { DEV } from 'cc/env';
|
||||
import { IMiniViewName, IViewName } from '../../../../../assets/app-builtin/app-admin/executor';
|
||||
import Core from '../../Core';
|
||||
import BaseManager from '../../base/BaseManager';
|
||||
import BaseView, { IHideParamOnHide, IShade, IShowParamAttr, IShowParamOnHide, IShowParamOnShow, IViewType, ViewType } from '../../base/BaseView';
|
||||
import UIMgrLoading from './comp/UIMgrLoading';
|
||||
import UIMgrShade from './comp/UIMgrShade';
|
||||
import UIMgrToast from './comp/UIMgrToast';
|
||||
import UIMgrZOrder from './comp/UIMgrZOrder';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
interface IShowParams<T, IShow = any, IShowReturn = any, IHideReturn = any> {
|
||||
/**UI名 */
|
||||
name: T,
|
||||
/**
|
||||
* 数据
|
||||
* - 被onShow接收
|
||||
*/
|
||||
data?: IShow,
|
||||
/**
|
||||
* 是否将UI显示在最上
|
||||
* - 默认true
|
||||
*/
|
||||
top?: boolean,
|
||||
/**
|
||||
* 队列模式,一个UI关闭后,是否展示下一个UI
|
||||
* - join: 排队
|
||||
* - jump: 插队(到首位)
|
||||
*/
|
||||
queue?: 'join' | 'jump',
|
||||
/**静默 默认false(不显示加载loading,也不屏蔽触摸) */
|
||||
silent?: boolean,
|
||||
/**UI触发onShow后 */
|
||||
onShow?: IShowParamOnShow<IShowReturn>,
|
||||
/**UI触发onHide后 */
|
||||
onHide?: IShowParamOnHide<IHideReturn>,
|
||||
/**当code的值为ErrorCode.LogicError时,如果返回true,则自动重试 */
|
||||
onError?: (result: string, code: ErrorCode) => true | void,
|
||||
/**
|
||||
* @private
|
||||
* @deprecated
|
||||
*/
|
||||
attr?: IShowParamAttr,
|
||||
}
|
||||
|
||||
interface IShowAsyncParams<T, IShow = any, IShowReturn = any> {
|
||||
/**UI名 */
|
||||
name: T,
|
||||
/**
|
||||
* 数据
|
||||
* - 被onShow接收
|
||||
*/
|
||||
data?: IShow,
|
||||
/**
|
||||
* 是否将UI显示在最上
|
||||
* - 默认true
|
||||
*/
|
||||
top?: boolean,
|
||||
/**
|
||||
* 队列模式,一个UI关闭后,是否展示下一个UI
|
||||
* - join: 排队
|
||||
* - jump: 插队(到首位)
|
||||
*/
|
||||
queue?: 'join' | 'jump',
|
||||
/**静默 默认false(不显示加载loading,也不屏蔽触摸) */
|
||||
silent?: boolean,
|
||||
/**UI触发onShow后 */
|
||||
onShow?: IShowParamOnShow<IShowReturn>,
|
||||
/**当code的值为ErrorCode.LogicError时,如果返回true,则自动重试 */
|
||||
onError?: (result: string, code: ErrorCode) => true | void,
|
||||
/**
|
||||
* @private
|
||||
* @deprecated
|
||||
*/
|
||||
attr?: IShowParamAttr,
|
||||
}
|
||||
|
||||
interface IHideParams<T, IHide = any, IHideReturn = any> {
|
||||
name: T,
|
||||
data?: IHide,
|
||||
onHide?: IHideParamOnHide<IHideReturn>
|
||||
}
|
||||
|
||||
const UIScene = 'UIScene';
|
||||
const UserInterfacePath = 'UserInterface';
|
||||
const ViewTypes = [ViewType.Page, ViewType.Paper, ViewType.Pop, ViewType.Top];
|
||||
|
||||
const BlockEvents = [
|
||||
Node.EventType.TOUCH_START, Node.EventType.TOUCH_MOVE, Node.EventType.TOUCH_END, Node.EventType.TOUCH_CANCEL,
|
||||
Node.EventType.MOUSE_DOWN, Node.EventType.MOUSE_MOVE, Node.EventType.MOUSE_UP,
|
||||
Node.EventType.MOUSE_ENTER, Node.EventType.MOUSE_LEAVE, Node.EventType.MOUSE_WHEEL
|
||||
];
|
||||
|
||||
/**
|
||||
* 错误码
|
||||
*/
|
||||
enum ErrorCode {
|
||||
/**加载失败 */
|
||||
LoadError,
|
||||
/**beforeShow返回错误 */
|
||||
LogicError,
|
||||
/**UI无效(UI的isViewValid返回false) */
|
||||
InvalidError,
|
||||
}
|
||||
|
||||
/**
|
||||
* 界面名字枚举
|
||||
*/
|
||||
const ViewName: { [key in IViewName]: key } = new Proxy({} as any, {
|
||||
get: function (target, key) {
|
||||
if (target[key]) return target[key];
|
||||
target[key] = key;
|
||||
return key;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 子界面名字枚举
|
||||
*/
|
||||
const MiniViewName: { [key in IMiniViewName]: key } = new Proxy({} as any, {
|
||||
get: function (target, key) {
|
||||
if (target[key]) return target[key];
|
||||
target[key] = key;
|
||||
return key;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 将驼峰命名转成串式命名
|
||||
* @param str 驼峰字符串
|
||||
* @returns
|
||||
*/
|
||||
function stringCaseNegate(str: string) {
|
||||
return str.replace(/[A-Z]/g, (searchStr, startIndex) => {
|
||||
if (startIndex === 0) {
|
||||
return searchStr.toLowerCase();
|
||||
} else {
|
||||
return '-' + searchStr.toLowerCase();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ccclass('UIManager')
|
||||
export default class UIManager<UIName extends string, MiniName extends string> extends BaseManager {
|
||||
/**静态设置 */
|
||||
static setting: {
|
||||
/**预加载列表,会在UI初始化阶段进行 */
|
||||
preload?: (IViewName | IMiniViewName | Array<IViewName | IMiniViewName>)[],
|
||||
/**默认UI,框架初始化完毕后会自动加载 */
|
||||
defaultUI?: IViewName,
|
||||
/**给默认UI传递的数据 */
|
||||
defaultData?: any,
|
||||
/**弹窗背景遮罩的参数 */
|
||||
shade?: IShade,
|
||||
/**
|
||||
* 是否自动适配分辨率策略
|
||||
* - 开启后会弃用当前的适配策略,并根据实际设备分辨率与设计分辨率的比值,计算出新的适配策略(宽适配或高适配),保证游戏区域不会被裁减只会扩边
|
||||
* - 当实际设备分辨率「高/宽」>= 设计分辨率「高/宽」时,为宽适配
|
||||
* - 当实际设备分辨率「高/宽」< 设计分辨率「高/宽」时,为高适配
|
||||
*/
|
||||
autoFit?: boolean,
|
||||
} = {};
|
||||
|
||||
/**错误码 */
|
||||
static ErrorCode = ErrorCode;
|
||||
|
||||
/**界面名字枚举 */
|
||||
static ViewName = ViewName;
|
||||
|
||||
/**子界面名字枚举 */
|
||||
static MiniViewName = MiniViewName;
|
||||
|
||||
@property({
|
||||
type: Prefab,
|
||||
tooltip: '位置: app://manager/ui/prefab/UIMgrLoading'
|
||||
})
|
||||
private loadingPre: Prefab = null;
|
||||
|
||||
@property({
|
||||
type: Prefab,
|
||||
tooltip: '位置: app://manager/ui/prefab/UIMgrShade'
|
||||
})
|
||||
private shadePre: Prefab = null;
|
||||
|
||||
@property({
|
||||
type: Prefab,
|
||||
tooltip: '位置: app://manager/ui/prefab/UIMgrToast'
|
||||
})
|
||||
private toastPre: Prefab = null;
|
||||
|
||||
// UI根节点
|
||||
private UserInterface: Node = null;
|
||||
|
||||
// 加载和遮罩节点
|
||||
private loading: Node = null;
|
||||
private shade: Node = null;
|
||||
private toast: Node = null;
|
||||
|
||||
private defaultUI: UIName = null;
|
||||
private defaultData: string = '';
|
||||
|
||||
private currScene: string = '';
|
||||
private currPage: BaseView = null;
|
||||
private currFocus: BaseView = null;
|
||||
|
||||
// 预制体缓存
|
||||
private prefabCache: { [name in string]: Prefab } = {};
|
||||
private sceneCache: { [name in string]: SceneAsset } = {};
|
||||
|
||||
// 全局触摸有效
|
||||
private touchEnabled: boolean = true;
|
||||
|
||||
// 记录触摸屏蔽
|
||||
private touchMaskMap = new Map<string, boolean>();
|
||||
// 记录展示加载
|
||||
private showLoadingMap = new Map<string, boolean>();
|
||||
|
||||
// 记录正在加载中的有效的ui
|
||||
private uiLoadingMap: Map<UIName, string[]> = new Map();
|
||||
// 记录正在展示中的有效的ui
|
||||
private uiShowingMap: Map<BaseView, UIName> = new Map();
|
||||
|
||||
private showQueue: IShowParams<UIName>[] = [];
|
||||
|
||||
/**UI根节点 */
|
||||
public get root() {
|
||||
return this.node.parent.parent;
|
||||
}
|
||||
|
||||
/**相机 */
|
||||
public get camera() {
|
||||
return this.canvas.cameraComponent;
|
||||
}
|
||||
|
||||
/**画布*/
|
||||
public get canvas() {
|
||||
return this.root.getComponent(Canvas);
|
||||
}
|
||||
|
||||
protected init(finish: Function) {
|
||||
const setting = UIManager.setting;
|
||||
|
||||
this.defaultUI = setting.defaultUI as UIName;
|
||||
this.defaultData = setting.defaultData;
|
||||
|
||||
super.init(finish);
|
||||
|
||||
// 预加载,符合AnyTask规则
|
||||
if (setting.preload?.length) {
|
||||
const task = Core.inst.lib.task.createAny();
|
||||
setting.preload.forEach((preload) => {
|
||||
if (preload instanceof Array) {
|
||||
task.add(preload.map(name => {
|
||||
return next => this.preload(name as any, next);
|
||||
}));
|
||||
} else {
|
||||
task.add(next => this.preload(preload as any, next));
|
||||
}
|
||||
});
|
||||
task.start();
|
||||
}
|
||||
}
|
||||
|
||||
protected onLoad() {
|
||||
this.UserInterface = this.root.getChildByName(UserInterfacePath);
|
||||
|
||||
this.root.getComponentsInChildren(Camera).forEach(camera => {
|
||||
// 避免camera.priority<0的情况,否则会造成渲染异常
|
||||
if (camera.priority < 0) camera.priority = 0;
|
||||
// 避免camera.far<=camera.near的情况,否则会造成渲染异常
|
||||
if (camera.far <= camera.near) camera.far = camera.near + 1;
|
||||
});
|
||||
director.addPersistRootNode(this.root);
|
||||
|
||||
this.createTypeRoot();
|
||||
|
||||
this.shade = instantiate(this.shadePre);
|
||||
this.shade.parent = this.UserInterface;
|
||||
this.shade.active = false;
|
||||
this.shade.getComponent(Widget).target = this.root;
|
||||
|
||||
this.loading = instantiate(this.loadingPre);
|
||||
this.loading.parent = this.node;
|
||||
this.loading.active = false;
|
||||
|
||||
// toast是后面加的,需要做容错
|
||||
if (this.toastPre) {
|
||||
this.toast = instantiate(this.toastPre);
|
||||
this.toast.parent = this.node;
|
||||
}
|
||||
|
||||
// 自动适配分辨率策略
|
||||
if (UIManager.setting.autoFit) {
|
||||
const designResolution = settings.querySettings(Settings.Category.SCREEN, 'designResolution') as { width: number, height: number, policy: number };
|
||||
const windowSize = size(screen.windowSize);
|
||||
let resolutionPolicy = designResolution.policy;
|
||||
const autoFitResolutionPolicy = function () {
|
||||
if (windowSize.width / windowSize.height > designResolution.width / designResolution.height) {
|
||||
if (resolutionPolicy === ResolutionPolicy.FIXED_HEIGHT) return;
|
||||
view.setResolutionPolicy(ResolutionPolicy.FIXED_HEIGHT);
|
||||
resolutionPolicy = ResolutionPolicy.FIXED_HEIGHT;
|
||||
} else {
|
||||
if (resolutionPolicy === ResolutionPolicy.FIXED_WIDTH) return;
|
||||
view.setResolutionPolicy(ResolutionPolicy.FIXED_WIDTH);
|
||||
resolutionPolicy = ResolutionPolicy.FIXED_WIDTH;
|
||||
}
|
||||
};
|
||||
autoFitResolutionPolicy();
|
||||
this.schedule(() => {
|
||||
if (windowSize.equals(screen.windowSize)) return;
|
||||
windowSize.set(screen.windowSize);
|
||||
autoFitResolutionPolicy();
|
||||
}, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
private createTypeRoot() {
|
||||
ViewTypes.forEach((type) => {
|
||||
const d2 = new Node(type);
|
||||
d2.layer = Layers.Enum.UI_2D;
|
||||
d2.addComponent(UIMgrZOrder);
|
||||
d2.parent = this.UserInterface;
|
||||
d2.addComponent(UITransform);
|
||||
const widget = d2.addComponent(Widget);
|
||||
widget.isAlignBottom = true;
|
||||
widget.isAlignLeft = true;
|
||||
widget.isAlignRight = true;
|
||||
widget.isAlignTop = true;
|
||||
widget.top = 0;
|
||||
widget.left = 0;
|
||||
widget.right = 0;
|
||||
widget.bottom = 0;
|
||||
widget.alignMode = Widget.AlignMode.ON_WINDOW_RESIZE;
|
||||
|
||||
if (DEV) {
|
||||
d2.on(Node.EventType.CHILD_ADDED, (child: Node) => {
|
||||
if (!child) return;
|
||||
if (child === this.shade) return;
|
||||
if (this.getBaseView(child)) return;
|
||||
this.warn(`${UserInterfacePath}/${type}下非必要请不要添加非UI节点:`, child?.name);
|
||||
}, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private addTouchMaskListener() {
|
||||
if (!this.touchEnabled) return;
|
||||
if (this.touchMaskMap.size > 0) return;
|
||||
|
||||
for (let i = 0; i < BlockEvents.length; i++) {
|
||||
this.root.on(BlockEvents[i], this.stopPropagation, this, true);
|
||||
}
|
||||
}
|
||||
|
||||
private removeTouchMaskListener() {
|
||||
if (!this.touchEnabled) return;
|
||||
if (this.touchMaskMap.size > 0) return;
|
||||
|
||||
for (let i = 0; i < BlockEvents.length; i++) {
|
||||
this.root.off(BlockEvents[i], this.stopPropagation, this, true);
|
||||
}
|
||||
}
|
||||
|
||||
private stopPropagation(event: Event) {
|
||||
if (!this.touchEnabled || this.touchMaskMap.size > 0) {
|
||||
event.propagationStopped = true;
|
||||
if (event.type !== Node.EventType.MOUSE_MOVE) {
|
||||
this.log('屏蔽触摸');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个节点上的BaseView组件, 获取不到返回null
|
||||
*/
|
||||
private getBaseView(node: Node): BaseView {
|
||||
if (!node) return null;
|
||||
return node.components.find(component => component instanceof BaseView) as BaseView;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在所有父节点中找到一个最近的view组件
|
||||
* @param target
|
||||
* @returns
|
||||
*/
|
||||
private getViewInParents(target: Node) {
|
||||
let node = target;
|
||||
let com: BaseView = null;
|
||||
|
||||
while (node.parent && !(node.parent instanceof Scene)) {
|
||||
com = this.getBaseView(node.parent);
|
||||
if (!com) {
|
||||
node = node.parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return com;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在子节点中找到一个最近的view组件
|
||||
* @param target
|
||||
* @returns
|
||||
*/
|
||||
private getViewInChildren(target: Node) {
|
||||
for (let index = 0; index < target.children.length; index++) {
|
||||
const node = target.children[index];
|
||||
const com = this.getBaseView(node);
|
||||
if (com) return com;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UI名字获取它的脚本类
|
||||
*/
|
||||
private getUIClass(name: string): typeof BaseView {
|
||||
return js.getClassByName(name) as (typeof BaseView);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UI名字获取UI路径
|
||||
* @param name ui名字
|
||||
* @returns
|
||||
*/
|
||||
private getUIPath(name: string) {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前缀
|
||||
* @param name ui名字
|
||||
*/
|
||||
private getUIPrefix(name: string): ViewType {
|
||||
for (let index = 0; index < ViewTypes.length; index++) {
|
||||
const typeName = ViewTypes[index];
|
||||
if (name.indexOf(typeName) === 0) {
|
||||
return typeName;
|
||||
}
|
||||
}
|
||||
this.error('getUIPrefix', `${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UI名字查询父节点
|
||||
* @param name ui名字
|
||||
*/
|
||||
private getUIParent(name: string): Node {
|
||||
if (this.currScene === name) {
|
||||
return director.getScene();
|
||||
}
|
||||
|
||||
const prefix = this.getUIPrefix(name);
|
||||
for (let index = 0; index < ViewTypes.length; index++) {
|
||||
const viewType = ViewTypes[index];
|
||||
if (viewType === prefix) {
|
||||
return this.UserInterface.getChildByName(viewType);
|
||||
}
|
||||
}
|
||||
|
||||
this.error('getUIParent', `找不到${name}对应的Parent`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UI名字获取场景内的节点
|
||||
* @param name ui名字
|
||||
*/
|
||||
private getUIInScene(name: string): Node;
|
||||
private getUIInScene(name: string, multiple: false): Node;
|
||||
private getUIInScene(name: string, multiple: true): Node[];
|
||||
private getUIInScene(name: string, multiple = false) {
|
||||
const parent = this.getUIParent(name);
|
||||
|
||||
if (multiple) {
|
||||
const result = parent.children.filter(node => node.name === name);
|
||||
if (result.length) return result.filter(node => isValid(node, true));
|
||||
} else {
|
||||
const result = parent.children.find(node => node.name === name);
|
||||
if (result) return isValid(result, true) ? result : null;
|
||||
}
|
||||
|
||||
return multiple ? [] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据UI名字获取展示中的节点
|
||||
* @param name ui名字
|
||||
*/
|
||||
private getUIInShowing(name: string): Node;
|
||||
private getUIInShowing(name: string, multiple: false): Node;
|
||||
private getUIInShowing(name: string, multiple: true): Node[];
|
||||
private getUIInShowing(name: string, multiple = false) {
|
||||
if (multiple) {
|
||||
const result: Node[] = [];
|
||||
this.uiShowingMap.forEach((_name, com) => {
|
||||
if (_name === name) result.push(com.node);
|
||||
});
|
||||
return result;
|
||||
} else {
|
||||
let result: Node = null;
|
||||
this.uiShowingMap.forEach((_name, com) => {
|
||||
if (!result && _name === name) result = com.node;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UI骨架Bundle名字
|
||||
* @deprecated 将会移除,请改为其它方案
|
||||
*/
|
||||
public getNativeBundleName(uiName: UIName | MiniName) {
|
||||
// 兼容旧版本
|
||||
const oldBundleName = `app-view_${uiName}`;
|
||||
const projectBundles = settings.querySettings(Settings.Category.ASSETS, 'projectBundles') as string[];
|
||||
if (projectBundles && projectBundles.indexOf(oldBundleName) >= 0) {
|
||||
return oldBundleName;
|
||||
}
|
||||
|
||||
return stringCaseNegate(uiName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取UI资源Bundle名字
|
||||
* @deprecated 将会移除,请改为其它方案
|
||||
*/
|
||||
public getResBundleName(uiName: UIName | MiniName) {
|
||||
// 兼容旧版本
|
||||
const oldBundleName = `app-view_${uiName}_Res`;
|
||||
const projectBundles = settings.querySettings(Settings.Category.ASSETS, 'projectBundles') as string[];
|
||||
if (projectBundles && projectBundles.indexOf(oldBundleName) >= 0) {
|
||||
return oldBundleName;
|
||||
}
|
||||
|
||||
return `${stringCaseNegate(uiName)}-res`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Bundle
|
||||
*/
|
||||
private initBundle(name: UIName | MiniName, onFinish: (result: [AssetManager.Bundle, AssetManager.Bundle]) => any) {
|
||||
Core.inst.lib.task.createASync<[AssetManager.Bundle, AssetManager.Bundle]>()
|
||||
.add((next) => {
|
||||
Core.inst.manager.loader.loadBundle({
|
||||
bundle: this.getNativeBundleName(name),
|
||||
onComplete: next
|
||||
});
|
||||
})
|
||||
.add((next) => {
|
||||
Core.inst.manager.loader.loadBundle({
|
||||
bundle: this.getResBundleName(name),
|
||||
onComplete: next
|
||||
});
|
||||
})
|
||||
.start(onFinish);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装UI
|
||||
*/
|
||||
private installUI(name: UIName | MiniName, complete?: (result: Prefab | SceneAsset | null) => any, progress?: (finish: number, total: number, item: AssetManager.RequestItem) => void) {
|
||||
if (this.sceneCache[name]) {
|
||||
complete && setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
complete(this.sceneCache[name]);
|
||||
});
|
||||
return;
|
||||
} else if (this.prefabCache[name]) {
|
||||
complete && setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
complete(this.prefabCache[name]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const task = Core.inst.lib.task.createSync<[[AssetManager.Bundle, AssetManager.Bundle], Prefab | SceneAsset]>()
|
||||
.add(next => {
|
||||
this.initBundle(name, next);
|
||||
})
|
||||
.add((next) => {
|
||||
// 失败
|
||||
const uiBundles = task.results[0];
|
||||
if (!uiBundles || !uiBundles[0] || !uiBundles[1]) return next(null);
|
||||
|
||||
const isScene = uiBundles[0].getSceneInfo(name);
|
||||
Core.inst.manager.loader.load({
|
||||
bundle: this.getNativeBundleName(name),
|
||||
path: this.getUIPath(name),
|
||||
type: isScene ? SceneAsset : Prefab,
|
||||
onProgress: progress,
|
||||
onComplete: next
|
||||
});
|
||||
})
|
||||
.start((results) => {
|
||||
if (!isValid(this)) return;
|
||||
// 验证缓存
|
||||
const cache = this.sceneCache[name] || this.prefabCache[name];
|
||||
if (cache) {
|
||||
return complete && complete(cache);
|
||||
}
|
||||
// 验证有效
|
||||
const asset = results[1];
|
||||
if (!asset) {
|
||||
return complete && complete(null);
|
||||
}
|
||||
// 添加引用计数
|
||||
asset.addRef();
|
||||
// 添加缓存
|
||||
if (asset instanceof Prefab) {
|
||||
this.prefabCache[name] = asset;
|
||||
} else {
|
||||
this.sceneCache[name] = asset;
|
||||
}
|
||||
this.log(`加载: ${name}`);
|
||||
return complete && complete(asset);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 卸载UI
|
||||
*/
|
||||
private uninstallUI(name: UIName | MiniName) {
|
||||
if (this.sceneCache[name]) {
|
||||
// 释放引用计数
|
||||
this.sceneCache[name].decRef();
|
||||
// 删除缓存
|
||||
delete this.sceneCache[name];
|
||||
} else if (this.prefabCache[name]) {
|
||||
// 释放引用计数
|
||||
this.prefabCache[name].decRef();
|
||||
// 删除缓存
|
||||
delete this.prefabCache[name];
|
||||
}
|
||||
|
||||
const resBundle = this.getResBundleName(name);
|
||||
const naBundle = this.getNativeBundleName(name);
|
||||
Core.inst.manager.loader.releaseAll(resBundle);
|
||||
Core.inst.manager.loader.releaseAll(naBundle);
|
||||
Core.inst.manager.loader.removeBundle(resBundle);
|
||||
Core.inst.manager.loader.removeBundle(naBundle);
|
||||
this.log(`卸载: ${name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载ui内部资源
|
||||
*/
|
||||
public loadRes<T extends typeof Asset>(target: Component, path: string, type: T, callback?: (item: InstanceType<T> | null) => any) {
|
||||
if (typeof target === 'string') {
|
||||
Core.inst.manager.loader.load({
|
||||
bundle: this.getResBundleName(target),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: callback
|
||||
});
|
||||
} else {
|
||||
const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene());
|
||||
if (view) {
|
||||
Core.inst.manager.loader.load({
|
||||
bundle: this.getResBundleName(view.viewName as UIName | MiniName),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: callback
|
||||
});
|
||||
} else {
|
||||
this.error('loadRes', target.name, path);
|
||||
callback && callback(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载ui内部资源
|
||||
*/
|
||||
public preloadRes<T extends typeof Asset>(target: Component | UIName | MiniName, path: string, type: T, complete?: (item: AssetManager.RequestItem[] | null) => any) {
|
||||
if (typeof target === 'string') {
|
||||
Core.inst.manager.loader.preload({
|
||||
bundle: this.getResBundleName(target),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: complete
|
||||
});
|
||||
} else {
|
||||
const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene());
|
||||
if (view) {
|
||||
Core.inst.manager.loader.preload({
|
||||
bundle: this.getResBundleName(view.viewName as UIName | MiniName),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: complete
|
||||
});
|
||||
} else {
|
||||
this.error('preloadRes', target.name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载ui内部资源
|
||||
*/
|
||||
public loadResDir<T extends typeof Asset>(target: Component, path: string, type: T, callback?: (items: InstanceType<T>[] | null) => any) {
|
||||
if (typeof target === 'string') {
|
||||
Core.inst.manager.loader.loadDir({
|
||||
bundle: this.getResBundleName(target),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: callback
|
||||
});
|
||||
} else {
|
||||
const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene());
|
||||
if (view) {
|
||||
Core.inst.manager.loader.loadDir({
|
||||
bundle: this.getResBundleName(view.viewName as UIName | MiniName),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: callback
|
||||
});
|
||||
} else {
|
||||
this.error('loadResDir', target.name, path);
|
||||
callback && callback([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载ui内部资源
|
||||
*/
|
||||
public preloadResDir<T extends typeof Asset>(target: Component | UIName | MiniName, path: string, type: T, complete?: (item: AssetManager.RequestItem[] | null) => any) {
|
||||
if (typeof target === 'string') {
|
||||
Core.inst.manager.loader.preloadDir({
|
||||
bundle: this.getResBundleName(target),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: complete
|
||||
});
|
||||
} else {
|
||||
const view = this.getBaseView(target.node) || this.getViewInParents(target.node) || this.getViewInChildren(director.getScene());
|
||||
if (view) {
|
||||
Core.inst.manager.loader.preloadDir({
|
||||
bundle: this.getResBundleName(view.viewName as UIName | MiniName),
|
||||
path: path,
|
||||
type: type,
|
||||
onComplete: complete
|
||||
});
|
||||
} else {
|
||||
this.error('preloadResDir', target.name, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预加载UI
|
||||
*/
|
||||
public preload(name: UIName | MiniName, complete?: (item: AssetManager.RequestItem[] | null) => any) {
|
||||
// 验证name是否为真
|
||||
if (!name) {
|
||||
this.error('preload', 'fail');
|
||||
complete && setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
complete(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.initBundle(name, ([naBundle]) => {
|
||||
const isScene = naBundle.getSceneInfo(name);
|
||||
Core.inst.manager.loader.preload({
|
||||
bundle: this.getNativeBundleName(name),
|
||||
path: this.getUIPath(name),
|
||||
type: isScene ? SceneAsset : Prefab,
|
||||
onComplete: complete
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载UI
|
||||
*/
|
||||
public load(name: UIName | MiniName): void;
|
||||
public load(name: UIName | MiniName, complete: (result: Prefab | SceneAsset | null) => any): void;
|
||||
public load(name: UIName | MiniName, progress: (finish: number, total: number, item: AssetManager.RequestItem) => void, complete: (result: Prefab | SceneAsset | null) => any): void;
|
||||
public load(name: UIName | MiniName, ...args: Function[]): void {
|
||||
const progress = (args[1] && args[0]) as (finish: number, total: number, item: AssetManager.RequestItem) => void;
|
||||
const complete = (args[1] || args[0]) as (result: any) => any;
|
||||
|
||||
// 验证name是否为真
|
||||
if (!name) {
|
||||
this.error('load', 'fail');
|
||||
complete && setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
complete(null);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 异步加载
|
||||
this.installUI(name, (result) => {
|
||||
if (!result) return complete && complete(null);
|
||||
return complete && complete(result);
|
||||
}, progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁UI,释放资源
|
||||
* - 直接销毁,不管是否是show状态
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public release(nameOrCom: UIName | MiniName | BaseView) {
|
||||
const uiName = typeof nameOrCom === 'string' ? nameOrCom : nameOrCom.viewName;
|
||||
|
||||
if (!uiName) {
|
||||
this.error('release', `${nameOrCom} fail`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 传入字符串是释放所有
|
||||
if (typeof nameOrCom === 'string') {
|
||||
this.getUIInScene(uiName, true).forEach((node) => {
|
||||
const com = this.getBaseView(node);
|
||||
if (!com) {
|
||||
this.error('release', `${uiName}不存在BaseView组件`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (com.isShow) {
|
||||
this.warn('release', `${uiName}正处于show状态, 此处将直接销毁`);
|
||||
}
|
||||
if (com === this.currPage) {
|
||||
this.currPage = null;
|
||||
}
|
||||
if (com === this.currFocus) {
|
||||
this.currFocus = null;
|
||||
}
|
||||
|
||||
this.uiShowingMap.delete(com);
|
||||
|
||||
if (node && isValid(node, true)) {
|
||||
node.parent = null;
|
||||
node.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
// 传入组件是释放单个
|
||||
else {
|
||||
if (nameOrCom.isShow) {
|
||||
this.warn('release', `${uiName}正处于show状态, 此处将直接销毁`);
|
||||
}
|
||||
if (nameOrCom === this.currPage) {
|
||||
this.currPage = null;
|
||||
}
|
||||
if (nameOrCom === this.currFocus) {
|
||||
this.currFocus = null;
|
||||
}
|
||||
|
||||
this.uiShowingMap.delete(nameOrCom);
|
||||
|
||||
const node = nameOrCom.node;
|
||||
if (node && isValid(node, true)) {
|
||||
node.parent = null;
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 当全部释放时才清除缓存
|
||||
const nodes = this.getUIInScene(uiName, true);
|
||||
if (nodes.length === 0 || nodes.every(node => !isValid(node, true))) {
|
||||
this.uninstallUI(uiName as UIName | MiniName);
|
||||
this.log(`释放资源: ${uiName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁全部UI,释放资源
|
||||
* - 直接销毁,不管是否是show状态
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public releaseAll(exclude?: UIName[]) {
|
||||
Object.keys(this.prefabCache).forEach((name: UIName) => {
|
||||
if (exclude && exclude.indexOf(name) !== -1) return;
|
||||
this.release(name);
|
||||
});
|
||||
Object.keys(this.sceneCache).forEach((name: UIName) => {
|
||||
if (exclude && exclude.indexOf(name) !== -1) return;
|
||||
this.release(name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查UI是否有效
|
||||
* - -1: 加载失败
|
||||
* - 0: UI无效
|
||||
* - 1: UI有效
|
||||
*/
|
||||
private checkUIValid(name: UIName | MiniName, data: any, callback: (valid: -1 | 0 | 1) => any) {
|
||||
this.installUI(name, (result) => {
|
||||
if (!result) return callback(-1);
|
||||
const View = this.getUIClass(name);
|
||||
if (!View) return callback(0);
|
||||
if (!View.isViewValid) return callback(1);
|
||||
View.isViewValid((valid: boolean) => {
|
||||
callback(valid ? 1 : 0);
|
||||
}, data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新阴影的层级及显示
|
||||
*/
|
||||
public refreshShade() {
|
||||
// 借助refreshShade实现onFocus、onLostFocus(onFocus不会被每次都触发,只有产生变化时才触发)
|
||||
let onFocus = false;
|
||||
// 倒序遍历uiRoots
|
||||
let uiRoots = this.UserInterface.children;
|
||||
for (let index = uiRoots.length - 1; index >= 0; index--) {
|
||||
const uiRoot = uiRoots[index];
|
||||
if (uiRoot !== this.shade && uiRoot !== this.loading) {
|
||||
// 倒序遍历uiRoot
|
||||
let children = uiRoot.children;
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
const node = children[i];
|
||||
if (node === this.shade) continue;
|
||||
|
||||
const com = this.getBaseView(node);
|
||||
if (!com) continue;
|
||||
|
||||
// 触发onFocus
|
||||
if (!onFocus && com.isCaptureFocus && com.isShow) {
|
||||
onFocus = true;
|
||||
if (this.currFocus !== com) {
|
||||
isValid(this.currFocus, true) && this.currFocus.constructor.prototype.focus.call(this.currFocus, false);
|
||||
this.currFocus = com;
|
||||
this.currFocus.constructor.prototype.focus.call(this.currFocus, true);
|
||||
}
|
||||
}
|
||||
// 添加遮罩
|
||||
if (com.isNeedShade && com.isShow) {
|
||||
const shadeSetting = Object.assign({}, UIManager.setting.shade, com.constructor.prototype.onShade.call(com));
|
||||
if (shadeSetting.blur) {
|
||||
this.shade.getComponent(UIMgrShade).init(0, 255, 255, 0, true);
|
||||
} else {
|
||||
this.shade.getComponent(UIMgrShade).init(
|
||||
typeof shadeSetting.delay !== 'number' ? 0 : shadeSetting.delay,
|
||||
typeof shadeSetting.begin !== 'number' ? 60 : shadeSetting.begin,
|
||||
typeof shadeSetting.end !== 'number' ? 180 : shadeSetting.end,
|
||||
typeof shadeSetting.speed !== 'number' ? 100 : shadeSetting.speed,
|
||||
false,
|
||||
);
|
||||
}
|
||||
this.shade.layer = node.layer;
|
||||
this.shade.parent = uiRoot;
|
||||
this.shade.active = true;
|
||||
// 以z坐标来代替2.x时代的zIndex
|
||||
this.shade.setPosition(this.shade.position.x, this.shade.position.y, node.position.z);
|
||||
|
||||
let shadeIndex = this.shade.getSiblingIndex();
|
||||
let nodeIndex = node.getSiblingIndex();
|
||||
if (shadeIndex > nodeIndex) {
|
||||
this.shade.setSiblingIndex(nodeIndex);
|
||||
} else {
|
||||
this.shade.setSiblingIndex(nodeIndex - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.shade.active = false;
|
||||
this.shade.getComponent(UIMgrShade).clear();
|
||||
if (!onFocus) {
|
||||
isValid(this.currFocus, true) && this.currFocus.constructor.prototype.focus.call(this.currFocus, false);
|
||||
this.currFocus = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析prefab
|
||||
private parsingPrefab(prefab: Prefab, name: string) {
|
||||
if (!prefab) return null;
|
||||
|
||||
const node = instantiate(prefab);
|
||||
|
||||
node.active = false;
|
||||
if (node.name !== name) {
|
||||
this.warn('parsingPrefab', `节点名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`);
|
||||
node.name = name;
|
||||
}
|
||||
|
||||
node.parent = this.getUIParent(name);
|
||||
node.getComponent(Widget)?.updateAlignment();
|
||||
return node;
|
||||
}
|
||||
|
||||
// 解析scene
|
||||
private parsingScene(asset: SceneAsset, name: string) {
|
||||
if (!asset || !asset.scene) return null;
|
||||
|
||||
if (asset.scene.name !== name) {
|
||||
this.warn('parsingScene', `场景名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`);
|
||||
asset.scene.name = name;
|
||||
}
|
||||
|
||||
const view = this.getViewInChildren(asset.scene);
|
||||
if (!view) {
|
||||
this.error('parsingScene', `解析场景时未查询到根节点存在BaseView: ${this.getUIPath(name)}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
view.node.active = false;
|
||||
if (view.node.name !== name) {
|
||||
this.warn('parsingScene', `节点名与UI名不一致, 已重置为UI名: ${this.getUIPath(name)}`);
|
||||
view.node.name = name;
|
||||
}
|
||||
return view.node;
|
||||
}
|
||||
|
||||
private addUILoadingUuid(name: UIName, loadingUuid?: string) {
|
||||
const uuid = loadingUuid || this.createUUID();
|
||||
if (!this.uiLoadingMap.has(name)) {
|
||||
this.uiLoadingMap.set(name, [uuid]);
|
||||
} else {
|
||||
this.uiLoadingMap.get(name).push(uuid);
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private removeUILoadingUuid(name: UIName, uuid: string) {
|
||||
if (!this.uiLoadingMap.has(name)) return false;
|
||||
const index = this.uiLoadingMap.get(name).indexOf(uuid);
|
||||
if (index === -1) return false;
|
||||
this.uiLoadingMap.get(name).splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建UI
|
||||
*/
|
||||
private createUI(name: UIName, silent: boolean, callback: (node: Node, scene?: Scene) => any) {
|
||||
// 生成一个UI加载的UUID
|
||||
const loadingUuid = silent ? '' : this.showLoading();
|
||||
const uiLoadingUuid = this.addUILoadingUuid(name, loadingUuid);
|
||||
|
||||
// 验证name
|
||||
if (!name) {
|
||||
setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
// 验证本次加载是否有效
|
||||
if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) {
|
||||
return this.hideLoading(loadingUuid);
|
||||
}
|
||||
callback(null);
|
||||
this.hideLoading(loadingUuid);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断是否已经存在节点并且是单例模式
|
||||
const node = this.getUIInScene(name);
|
||||
if (isValid(node, true) && this.getBaseView(node).isSingleton === true) {
|
||||
setTimeout(() => {
|
||||
if (!isValid(this)) return;
|
||||
|
||||
// 验证本次加载是否有效
|
||||
if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) {
|
||||
return this.hideLoading(loadingUuid);
|
||||
}
|
||||
|
||||
// 验证节点是否有效
|
||||
if (isValid(node, true)) {
|
||||
if (this.currScene === name) {
|
||||
callback(node, director.getScene());
|
||||
} else {
|
||||
callback(node);
|
||||
}
|
||||
this.hideLoading(loadingUuid);
|
||||
} else {
|
||||
this.createUI(name, silent, callback);
|
||||
this.hideLoading(loadingUuid);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载UI
|
||||
this.load(name, (asset) => {
|
||||
if (!isValid(this)) return;
|
||||
|
||||
// 验证本次加载是否有效
|
||||
if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) {
|
||||
return this.hideLoading(loadingUuid);
|
||||
}
|
||||
|
||||
// 是场景
|
||||
if (asset instanceof SceneAsset) {
|
||||
callback(this.parsingScene(asset, name), asset.scene);
|
||||
this.hideLoading(loadingUuid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证是否是单例(一个单例会有被同时load多次的情况,因为判断一个ui是否是单例,必须要至少实例化一个后才能获取)
|
||||
const node = this.getUIInScene(name);
|
||||
if (!isValid(node, true) || this.getBaseView(node).isSingleton === false) {
|
||||
callback(this.parsingPrefab(asset, name));
|
||||
this.hideLoading(loadingUuid);
|
||||
} else {
|
||||
callback(node);
|
||||
this.hideLoading(loadingUuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示默认View
|
||||
*/
|
||||
public showDefault(onShow?: (result?: any) => any) {
|
||||
if (this.defaultUI) {
|
||||
this.show({
|
||||
name: this.defaultUI,
|
||||
data: this.defaultData,
|
||||
onShow
|
||||
});
|
||||
} else {
|
||||
Core.inst.manager.ui.showToast('请先设置首界面\n在setting.ts中修改defaultUI', 100);
|
||||
onShow && onShow();
|
||||
this.warn('defaultUI不存在,请在setting.ts中修改');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否展示了(包括加载中和队列中)
|
||||
*/
|
||||
public isShow(name: UIName) {
|
||||
return !!this.getUIInShowing(name) ||
|
||||
this.isInQueue(name) ||
|
||||
this.isLoading(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在队列中
|
||||
*/
|
||||
public isInQueue(name: UIName) {
|
||||
return !!this.showQueue.find((v) => { return v.name == name; });
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否在加载中
|
||||
*/
|
||||
public isLoading(name: UIName) {
|
||||
return this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 放入队列
|
||||
*/
|
||||
private putInShowQueue(data: IShowParams<UIName>) {
|
||||
if (data.queue === 'join' || this.showQueue.length === 0) {
|
||||
this.showQueue.push(data);
|
||||
} else {
|
||||
this.showQueue.splice(1, 0, data);
|
||||
}
|
||||
if (this.showQueue.length === 1) {
|
||||
this.consumeShowQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 消耗队列
|
||||
*/
|
||||
private consumeShowQueue() {
|
||||
if (this.showQueue.length === 0) return;
|
||||
const data = this.showQueue[0];
|
||||
this.show({
|
||||
name: data.name,
|
||||
data: data.data,
|
||||
onShow: data.onShow,
|
||||
onHide: (result: any) => {
|
||||
data.onHide && data.onHide(result);
|
||||
this.showQueue.shift();
|
||||
this.consumeShowQueue();
|
||||
},
|
||||
onError: data.onError ? (error: string, code: 0 | 1) => {
|
||||
const ret = data.onError(error, code);
|
||||
this.showQueue.shift();
|
||||
this.consumeShowQueue();
|
||||
return ret;
|
||||
} : undefined,
|
||||
top: data.top,
|
||||
attr: data.attr,
|
||||
silent: data.silent
|
||||
});
|
||||
}
|
||||
|
||||
private showUI(params: IShowParams<UIName>) {
|
||||
const { name, data, onShow, onHide, onError, top = true, attr = null, silent = false } = params;
|
||||
|
||||
this.createUI(name, silent, (node, scene) => {
|
||||
if (!node) {
|
||||
this.error('show', `${name} 不存在或加载失败`);
|
||||
// 「没有指定onError」或「onError返回true」会自动发起重试
|
||||
if (onError && onError(`${name} 不存在或加载失败`, UIManager.ErrorCode.LoadError) !== true) {
|
||||
return;
|
||||
}
|
||||
this.scheduleOnce(() => this.showUI(params), 1);
|
||||
if (!silent) this.showLoading(1);
|
||||
return;
|
||||
}
|
||||
|
||||
!scene && top && node.setSiblingIndex(-1);
|
||||
|
||||
const com = this.getBaseView(node);
|
||||
this.uiShowingMap.set(com, name);
|
||||
com.constructor.prototype.show.call(com, data, attr,
|
||||
// onShow
|
||||
(result: any) => {
|
||||
this.uiShowingMap.set(com, name);
|
||||
onShow && onShow(result);
|
||||
},
|
||||
// onHide
|
||||
(result: any) => {
|
||||
this.uiShowingMap.delete(com);
|
||||
onHide && onHide(result);
|
||||
},
|
||||
// beforeShow
|
||||
(error: string) => {
|
||||
if (error) {
|
||||
this.uiShowingMap.delete(com);
|
||||
onError && onError(error, UIManager.ErrorCode.LogicError);
|
||||
} else if (BaseView.isPage(name)) {
|
||||
this.uiShowingMap.set(com, name);
|
||||
const oldCom = this.currPage;
|
||||
this.currPage = com;
|
||||
if (isValid(oldCom, true) && oldCom !== com && oldCom.isShow && oldCom.isAlwaysExist === false) {
|
||||
oldCom.constructor.prototype.hide.call(oldCom, { name });
|
||||
}
|
||||
if (scene) {
|
||||
if (oldCom !== com) {
|
||||
this.currScene = name;
|
||||
director.runSceneImmediate(scene, null, () => {
|
||||
this.log(`切换场景: ${name}`);
|
||||
});
|
||||
}
|
||||
} else if (this.currScene !== UIScene) {
|
||||
this.currScene = UIScene;
|
||||
const scene = new Scene(UIScene);
|
||||
scene.autoReleaseAssets = true;
|
||||
director.runSceneImmediate(scene, null, () => {
|
||||
this.log(`切换场景: ${UIScene}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示一个UI
|
||||
* - 此流程一定是异步的
|
||||
*/
|
||||
public show<UI extends BaseView>(params
|
||||
// @ts-ignore
|
||||
: IShowParams<UIName, Parameters<UI['onShow']>[0], ReturnType<UI['onShow']>, ReturnType<UI['onHide']>>) {
|
||||
const { name, data, queue, onError, silent = false } = params;
|
||||
|
||||
// 加入队列中
|
||||
if (queue) {
|
||||
this.putInShowQueue(params);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log(`show: ${name}`);
|
||||
|
||||
// 生成一个UI加载的UUID
|
||||
const loadingUuid = silent ? '' : this.showLoading();
|
||||
const uiLoadingUuid = this.addUILoadingUuid(name, loadingUuid);
|
||||
// 判断ui是否有效
|
||||
Core.inst.lib.task.execute((retry) => {
|
||||
this.checkUIValid(name, data, (valid) => {
|
||||
// 验证本次加载是否有效
|
||||
if (this.removeUILoadingUuid(name, uiLoadingUuid) === false) {
|
||||
this.hideLoading(loadingUuid);
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载失败
|
||||
if (valid === -1) {
|
||||
this.error('show', `${name} 不存在或加载失败`);
|
||||
// 「没有指定onError」或「onError返回true」会自动发起重试
|
||||
if (onError && onError(`${name} 不存在或加载失败`, UIManager.ErrorCode.LoadError) !== true) {
|
||||
return this.hideLoading(loadingUuid);
|
||||
}
|
||||
return retry(1);
|
||||
}
|
||||
|
||||
// ui无效
|
||||
if (valid === 0) {
|
||||
this.warn('show', `${name} 无效`);
|
||||
this.uninstallUI(name);
|
||||
onError && onError(`${name} 无效`, UIManager.ErrorCode.InvalidError);
|
||||
this.hideLoading(loadingUuid);
|
||||
return;
|
||||
}
|
||||
|
||||
this.showUI(params);
|
||||
this.hideLoading(loadingUuid);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示一个UI
|
||||
* - 此流程一定是异步的
|
||||
*/
|
||||
public showAsync<UI extends BaseView>(params
|
||||
// @ts-ignore
|
||||
: IShowAsyncParams<UIName, Parameters<UI['onShow']>[0], ReturnType<UI['onShow']>>): Promise<ReturnType<UI['onHide']>> {
|
||||
return new Promise((resolve) => {
|
||||
this.show({
|
||||
...params,
|
||||
onHide(result) {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭View
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public hide<UI extends BaseView>({ name, data, onHide }
|
||||
// @ts-ignore
|
||||
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
|
||||
const nodes = this.getUIInShowing(name, true);
|
||||
|
||||
this.log(`hide: ${name}`);
|
||||
|
||||
if (nodes.length === 0) {
|
||||
if (!this.uiLoadingMap.has(name) || this.uiLoadingMap.get(name).length === 0) {
|
||||
return this.warn('hide', `${name} 不存在`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.uiLoadingMap.has(name)) {
|
||||
this.uiLoadingMap.get(name).forEach((loadingUuid) => this.hideLoading(loadingUuid));
|
||||
this.uiLoadingMap.get(name).length = 0;
|
||||
}
|
||||
|
||||
for (let index = nodes.length - 1; index >= 0; index--) {
|
||||
const node = nodes[index];
|
||||
const com = this.getBaseView(node);
|
||||
|
||||
if (this.currPage === com) {
|
||||
this.currPage = null;
|
||||
}
|
||||
|
||||
com.constructor.prototype.hide.call(com, data, onHide);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从顶部关闭一个View(不会重复关闭节点)
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public pop<UI extends BaseView>({ name, data, onHide }
|
||||
// @ts-ignore
|
||||
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
|
||||
const nodes = this.getUIInShowing(name, true);
|
||||
|
||||
if (this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length) {
|
||||
const loadingUuid = this.uiLoadingMap.get(name).pop();
|
||||
this.hideLoading(loadingUuid);
|
||||
this.log(`pop: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodes.length) {
|
||||
const node = nodes.pop();
|
||||
const com = this.getBaseView(node);
|
||||
|
||||
if (this.currPage === com) {
|
||||
this.currPage = null;
|
||||
}
|
||||
|
||||
com.constructor.prototype.hide.call(com, data, onHide);
|
||||
this.log(`pop: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.warn('pop', `${name} 不存在`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从底部关闭一个View(不会重复关闭节点)
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public shift<UI extends BaseView>({ name, data, onHide }
|
||||
// @ts-ignore
|
||||
: IHideParams<UIName, Parameters<UI['onHide']>[0], ReturnType<UI['onHide']>>) {
|
||||
const nodes = this.getUIInShowing(name, true);
|
||||
|
||||
if (nodes.length) {
|
||||
const node = nodes[0];
|
||||
const com = this.getBaseView(node);
|
||||
|
||||
if (this.currPage === com) {
|
||||
this.currPage = null;
|
||||
}
|
||||
|
||||
com.constructor.prototype.hide.call(com, data, onHide);
|
||||
this.log(`shift: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.uiLoadingMap.has(name) && this.uiLoadingMap.get(name).length) {
|
||||
const loadingUuid = this.uiLoadingMap.get(name).shift();
|
||||
this.hideLoading(loadingUuid);
|
||||
this.log(`shift: ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.warn('shift', `${name} 不存在`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭全部View
|
||||
* - 不关闭展示中的Page(加载中的会停止)
|
||||
* - 此流程一定是同步的
|
||||
*/
|
||||
public hideAll({ data, exclude }: { data?: any, exclude?: UIName[] } = {}): void {
|
||||
this.log('hideAll');
|
||||
// 展示中的
|
||||
this.uiShowingMap.forEach((name, com) => {
|
||||
if (BaseView.isPaper(name)) return;
|
||||
if (exclude && exclude.indexOf(name) !== -1) return;
|
||||
if (com === this.currPage) return;
|
||||
com.constructor.prototype.hide.call(com, data);
|
||||
});
|
||||
// 加载中的
|
||||
this.uiLoadingMap.forEach((value, name) => {
|
||||
if (BaseView.isPaper(name)) return;
|
||||
if (exclude && exclude.indexOf(name) !== -1) return;
|
||||
value.forEach((loadingUuid) => this.hideLoading(loadingUuid));
|
||||
value.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
public showLoading(timeout = 0) {
|
||||
this.loading.active = true;
|
||||
this.loading.setSiblingIndex(-1);
|
||||
if (this.loading.getComponent(UIMgrLoading)) {
|
||||
this.loading.getComponent(UIMgrLoading).init();
|
||||
} else {
|
||||
// 兼容旧版本
|
||||
this.loading.getComponentInChildren(UIMgrLoading)?.init();
|
||||
}
|
||||
const uuid = this.createUUID();
|
||||
this.showLoadingMap.set(uuid, true);
|
||||
if (timeout > 0) this.scheduleOnce(() => {
|
||||
this.hideLoading(uuid);
|
||||
}, timeout);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public hideLoading(uuid: string) {
|
||||
if (!uuid) return;
|
||||
this.showLoadingMap.delete(uuid);
|
||||
if (this.showLoadingMap.size === 0) {
|
||||
if (this.loading.getComponent(UIMgrLoading)) {
|
||||
this.loading.getComponent(UIMgrLoading).clear();
|
||||
} else {
|
||||
// 兼容旧版本
|
||||
this.loading.getComponentInChildren(UIMgrLoading)?.clear();
|
||||
}
|
||||
this.loading.active = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加触摸屏蔽
|
||||
*/
|
||||
public addTouchMask(timeout = 0) {
|
||||
this.addTouchMaskListener();
|
||||
const uuid = this.createUUID();
|
||||
this.touchMaskMap.set(uuid, true);
|
||||
if (timeout > 0) this.scheduleOnce(() => {
|
||||
this.removeTouchMask(uuid);
|
||||
}, timeout);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除触摸屏蔽
|
||||
* @param uuid addTouchMask的返回值
|
||||
*/
|
||||
public removeTouchMask(uuid: string) {
|
||||
if (!uuid) return;
|
||||
this.touchMaskMap.delete(uuid);
|
||||
this.removeTouchMaskListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示Toast
|
||||
* @param message 文本
|
||||
* @param timeout 持续时间(秒),默认2秒
|
||||
*/
|
||||
public showToast(message: string, timeout?: number) {
|
||||
if (!this.toast) {
|
||||
return this.error('showToast', '请确认首场景中「Canvas/Manager/UIManager」的「Toast Pre」属性存在');
|
||||
}
|
||||
this.toast.setSiblingIndex(-1);
|
||||
this.toast.getComponent(UIMgrToast).add({
|
||||
message, timeout
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理Toast
|
||||
*/
|
||||
public clearToast() {
|
||||
if (!this.toast) return;
|
||||
this.toast.getComponent(UIMgrToast).clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置触摸是否启用
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
public setTouchEnabled(enabled: boolean) {
|
||||
if (enabled) {
|
||||
this.touchEnabled = true;
|
||||
this.removeTouchMaskListener();
|
||||
} else {
|
||||
this.addTouchMaskListener();
|
||||
this.touchEnabled = false;
|
||||
}
|
||||
this.warn('setTouchEnabled', this.touchEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在2DUI根节点上处理事件
|
||||
*/
|
||||
public onUserInterface(...args: Parameters<Node['on']>) {
|
||||
Node.prototype.on.apply(this.UserInterface, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在2DUI根节点上处理事件
|
||||
*/
|
||||
public onceUserInterface(...args: Parameters<Node['once']>) {
|
||||
Node.prototype.once.apply(this.UserInterface, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在2DUI根节点上处理事件
|
||||
*/
|
||||
public offUserInterface(...args: Parameters<Node['off']>) {
|
||||
Node.prototype.off.apply(this.UserInterface, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在2DUI根节点上处理事件
|
||||
*/
|
||||
public targetOffUserInterface(...args: Parameters<Node['targetOff']>) {
|
||||
Node.prototype.targetOff.apply(this.UserInterface, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* 立即给2DUI的子节点排序
|
||||
*/
|
||||
public sortUserInterface(name: IViewType) {
|
||||
this.UserInterface
|
||||
?.getChildByName(name)
|
||||
?.getComponent(UIMgrZOrder)
|
||||
?.updateZOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 屏幕截图
|
||||
* - 需要在Director.EVENT_BEFORE_RENDER事件中调用
|
||||
* @example
|
||||
* director.once(Director.EVENT_BEFORE_RENDER, () => {
|
||||
* const renderTexture = new RenderTexture();
|
||||
* const size = view.getVisibleSize();
|
||||
* renderTexture.reset({ width: size.width, height: size.height });
|
||||
* app.manager.ui.screenshot(renderTexture);
|
||||
* });
|
||||
*/
|
||||
public screenshot(renderTexture: RenderTexture, opts?: {
|
||||
/**摄像机筛选 */
|
||||
cameraFilter?: (camera: Camera) => boolean;
|
||||
/**摄像机列表 */
|
||||
cameraList?: Camera[];
|
||||
}) {
|
||||
const cameras = opts?.cameraList || director.getScene().getComponentsInChildren(Camera);
|
||||
|
||||
const cameraList = cameras.sort((a, b) => a.priority - b.priority)
|
||||
.filter(camera => {
|
||||
if (!camera.enabledInHierarchy) return false;
|
||||
if (camera.targetTexture) return false;
|
||||
return opts?.cameraFilter ? opts.cameraFilter(camera) : true;
|
||||
});
|
||||
const cameraList2 = cameraList.map(camera => camera.camera);
|
||||
|
||||
cameraList.forEach(camera => {
|
||||
camera.targetTexture = renderTexture;
|
||||
});
|
||||
director.root.pipeline.render(cameraList2);
|
||||
cameraList.forEach(camera => {
|
||||
camera.targetTexture = null;
|
||||
});
|
||||
|
||||
return renderTexture;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user