Files
oops-plugin-framework/assets/core/gui/layer/LayerManager.ts
2025-03-25 20:53:43 +08:00

468 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Camera, Layers, Node, ResolutionPolicy, SafeArea, Widget, screen, view, warn } from "cc";
import { oops } from "../../Oops";
import { UICallbacks } from "./Defines";
import { DelegateComponent } from "./DelegateComponent";
import { LayerDialog } from "./LayerDialog";
import { LayerNotify } from "./LayerNotify";
import { LayerPopUp } from "./LayerPopup";
import { LayerUI } from "./LayerUI";
/** 屏幕适配类型 */
export enum ScreenAdapterType {
/** 自动适配 */
Auto,
/** 横屏适配 */
Landscape,
/** 竖屏适配 */
Portrait
}
/** 界面层类型 */
export enum LayerType {
/** 二维游戏层 */
Game = "LayerGame",
/** 主界面层 */
UI = "LayerUI",
/** 弹窗层 */
PopUp = "LayerPopUp",
/** 模式窗口层 */
Dialog = "LayerDialog",
/** 系统触发模式窗口层 */
System = "LayerSystem",
/** 消息提示层 */
Notify = "LayerNotify",
/** 新手引导层 */
Guide = "LayerGuide"
}
/** 界面层组件类型 */
export enum LayerTypeCls {
/** 主界面层 */
UI = "UI",
/** 弹窗层 */
PopUp = "PopUp",
/** 模式窗口层 */
Dialog = "Dialog",
/** 消息提示层 */
Notify = "Notify",
/** 自定义节点层 */
Node = "Node"
}
/**
* 界面配置结构体
* @help https://gitee.com/dgflash/oops-framework/wikis/pages?sort_id=12037986&doc_id=2873565
* @example
// 界面唯一标识
export enum UIID {
Loading = 1,
Window,
Netinstable
}
// 打开界面方式的配置数据
export var UIConfigData: { [key: number]: UIConfig } = {
[UIID.Loading]: { layer: LayerType.UI, prefab: "loading/prefab/loading", bundle: "resources" },
[UIID.Netinstable]: { layer: LayerType.PopUp, prefab: "common/prefab/netinstable" },
[UIID.Window]: { layer: LayerType.Dialog, prefab: "common/prefab/window" }
}
*/
export interface UIConfig {
/** -----公共属性----- */
/** 远程包名 */
bundle?: string;
/** 窗口层级 */
layer: string;
/** 预制资源相对路径 */
prefab: string;
/** 是否自动施放(默认不自动释放) */
destroy?: boolean;
/** -----弹窗属性----- */
/** 是否触摸非窗口区域关闭(默认关闭) */
vacancy?: boolean,
/** 是否打开窗口后显示背景遮罩(默认关闭) */
mask?: boolean;
/** 是否启动真机安全区域显示 */
safeArea?: boolean;
/** 界面弹出时的节点排序索引 */
siblingIndex?: number;
}
/** 界面层级管理器 */
export class LayerManager {
/** 界面根节点 */
root!: Node;
/** 界面摄像机 */
camera!: Camera;
/** 游戏界面特效层 */
game!: Node;
/** 新手引导层 */
guide!: Node;
/** 窗口宽高比例 */
windowAspectRatio: number = 0;
/** 设计宽高比例 */
designAspectRatio: number = 0;
/** 是否开启移动设备安全区域适配 */
mobileSafeArea: boolean = false;
/** 消息提示控制器请使用show方法来显示 */
private notify!: LayerNotify;
/** UI配置 */
private configs: { [key: number]: UIConfig } = {};
/** 界面层集合 - 无自定义类型 */
private uiLayers: Map<string, LayerUI> = new Map();
/** 界面层组件集合 */
private clsLayers: Map<string, any> = new Map();
constructor() {
this.clsLayers.set(LayerTypeCls.UI, LayerUI);
this.clsLayers.set(LayerTypeCls.PopUp, LayerPopUp);
this.clsLayers.set(LayerTypeCls.Dialog, LayerDialog);
this.clsLayers.set(LayerTypeCls.Notify, LayerNotify);
this.clsLayers.set(LayerTypeCls.Node, null);
}
/**
* 注册自定义界面层对象
* @param type 自定义界面层类型
* @param cls 自定义界面层对象
*/
registerLayerCls(type: string, cls: any) {
if (this.clsLayers.has(type)) {
console.error("已存在自定义界面层类型", type);
return;
}
this.clsLayers.set(type, cls);
}
/**
* 初始化界面层
* @param root 界面根节点
*/
private initLayer(root: Node, config: any) {
if (config == null) {
console.error("请升级到最新版本框架,界面层级管理修改为数据驱动。参考模板项目中的config.json配置文件");
return;
}
this.root = root;
this.initScreenAdapter();
this.camera = this.root.getComponentInChildren(Camera)!;
// 创建界面层
for (let i = 0; i < config.length; i++) {
let data = config[i];
let layer: Node = null!;
if (data.type == LayerTypeCls.Node) {
layer = this.create_node(data.name);
}
else {
let cls = this.clsLayers.get(data.type);
if (cls) {
layer = new cls(data.name);
}
else {
console.error("未识别的界面层类型", data.type);
}
}
root.addChild(layer);
if (layer instanceof LayerUI)
this.uiLayers.set(data.name, layer);
else if (layer instanceof LayerNotify)
this.notify = layer;
}
}
/** 初始化屏幕适配 */
private initScreenAdapter() {
const drs = view.getDesignResolutionSize();
const ws = screen.windowSize;
this.windowAspectRatio = ws.width / ws.height;
this.designAspectRatio = drs.width / drs.height;
let finalW: number = 0;
let finalH: number = 0;
if (this.windowAspectRatio > this.designAspectRatio) {
finalH = drs.height;
finalW = finalH * ws.width / ws.height;
oops.log.logView("适配屏幕高度", "【横屏】");
}
else {
finalW = drs.width;
finalH = finalW * ws.height / ws.width;
oops.log.logView("适配屏幕宽度", "【竖屏】");
}
view.setDesignResolutionSize(finalW, finalH, ResolutionPolicy.UNKNOWN);
if (this.mobileSafeArea) {
this.root.addComponent(SafeArea);
oops.log.logView("开启移动设备安全区域适配");
}
}
/**
* 初始化所有UI的配置对象
* @param configs 配置对象
*/
init(configs: { [key: number]: UIConfig }): void {
this.configs = configs;
}
/**
* 设置窗口打开失败回调
* @param callback 回调方法
*/
setOpenFailure(callback: Function) {
this.uiLayers.forEach((layer: LayerUI) => {
layer.onOpenFailure = callback;
})
}
/**
* 渐隐飘过提示
* @param content 文本表示
* @param useI18n 是否使用多语言
* @example
* oops.gui.toast("提示内容");
*/
toast(content: string, useI18n: boolean = false) {
this.notify.toast(content, useI18n)
}
/** 打开等待提示 */
waitOpen() {
this.notify.waitOpen();
}
/** 关闭等待提示 */
waitClose() {
this.notify.waitClose();
}
/**
* 设置界面配置
* @param uiId 要设置的界面id
* @param config 要设置的配置
*/
setConfig(uiId: number, config: UIConfig): void {
this.configs[uiId] = config;
}
/**
* 同步打开一个窗口
* @param uiId 窗口唯一编号
* @param uiArgs 窗口参数
* @param callbacks 回调对象
* @example
var uic: UICallbacks = {
onAdded: (node: Node, params: any) => {
var comp = node.getComponent(LoadingViewComp) as ecs.Comp;
}
onRemoved:(node: Node | null, params: any) => {
}
};
oops.gui.open(UIID.Loading, null, uic);
*/
open(uiId: number, uiArgs: any = null, callbacks?: UICallbacks): void {
const config = this.configs[uiId];
if (config == null) {
warn(`打开编号为【${uiId}】的界面失败,配置信息不存在`);
return;
}
let layer = this.uiLayers.get(config.layer);
if (layer) {
layer.add(config, uiArgs, callbacks);
}
else {
console.error(`打开编号为【${uiId}】的界面失败,界面层不存在`);
}
}
/**
* 异步打开一个窗口
* @param uiId 窗口唯一编号
* @param uiArgs 窗口参数
* @example
* var node = await oops.gui.openAsync(UIID.Loading);
*/
async openAsync(uiId: number, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>((resolve, reject) => {
const callbacks: UICallbacks = {
onAdded: (node: Node, params: any) => {
resolve(node);
},
onLoadFailure: () => {
resolve(null);
}
};
this.open(uiId, uiArgs, callbacks);
});
}
/**
* 场景替换
* @param removeUiId 移除场景编号
* @param openUiId 新打开场景编号
* @param uiArgs 新打开场景参数
*/
replace(removeUiId: number, openUiId: number, uiArgs: any = null) {
const callbacks: UICallbacks = {
onAdded: (node: Node, params: any) => {
this.remove(removeUiId);
}
};
this.open(openUiId, uiArgs, callbacks);
}
/**
* 异步场景替换
* @param removeUiId 移除场景编号
* @param openUiId 新打开场景编号
* @param uiArgs 新打开场景参数
*/
replaceAsync(removeUiId: number, openUiId: number, uiArgs: any = null): Promise<Node | null> {
return new Promise<Node | null>(async (resolve, reject) => {
const node = await this.openAsync(openUiId, uiArgs);
if (node) {
this.remove(removeUiId);
resolve(node);
}
else {
resolve(null);
}
});
}
/**
* 缓存中是否存在指定标识的窗口
* @param uiId 窗口唯一标识
* @example
* oops.gui.has(UIID.Loading);
*/
has(uiId: number): boolean {
const config = this.configs[uiId];
if (config == null) {
warn(`编号为【${uiId}】的界面配置不存在,配置信息不存在`);
return false;
}
var result = false;
let layer = this.uiLayers.get(config.layer);
if (layer) {
result = layer.has(config.prefab);
}
else {
console.error(`验证编号为【${uiId}】的界面失败,界面层不存在`);
}
return result;
}
/**
* 缓存中是否存在指定标识的窗口
* @param uiId 窗口唯一标识
* @example
* oops.gui.has(UIID.Loading);
*/
get(uiId: number): Node {
const config = this.configs[uiId];
if (config == null) {
warn(`编号为【${uiId}】的界面配置不存在,配置信息不存在`);
return null!;
}
let result: Node = null!;
let layer = this.uiLayers.get(config.layer);
if (layer) {
result = layer.get(config.prefab);
}
else {
console.error(`获取编号为【${uiId}】的界面失败,界面层不存在`);
}
return result;
}
/**
* 移除指定标识的窗口
* @param uiId 窗口唯一标识
* @param isDestroy 移除后是否释放
* @example
* oops.gui.remove(UIID.Loading);
*/
remove(uiId: number, isDestroy?: boolean) {
const config = this.configs[uiId];
if (config == null) {
warn(`删除编号为【${uiId}】的界面失败,配置信息不存在`);
return;
}
let layer = this.uiLayers.get(config.layer);
if (layer) {
layer.remove(config.prefab, isDestroy);
}
else {
console.error(`移除编号为【${uiId}】的界面失败,界面层不存在`);
}
}
/**
* 删除一个通过this框架添加进来的节点
* @param node 窗口节点
* @param isDestroy 移除后是否释放资源
* @example
* oops.gui.removeByNode(cc.Node);
*/
removeByNode(node: Node, isDestroy?: boolean) {
if (node instanceof Node) {
let comp = node.getComponent(DelegateComponent);
if (comp && comp.vp) {
// 释放显示的界面
if (node.parent) {
this.uiLayers.get(LayerType.UI)!.remove(comp.vp.config.prefab, isDestroy);
}
// 释放缓存中的界面
else if (isDestroy) {
let layer = this.uiLayers.get(comp.vp.config.layer);
if (layer) {
// @ts-ignore 注:不对外使用
layer.removeCache(comp.vp.config.prefab);
}
}
}
else {
warn(`当前删除的node不是通过界面管理器添加到舞台上`);
node.destroy();
}
}
}
/**
* 清除所有窗口
* @param isDestroy 移除后是否释放
* @example
* oops.gui.clear();
*/
clear(isDestroy: boolean = false) {
this.uiLayers.forEach((layer: LayerUI) => {
layer.clear(isDestroy);
})
}
private create_node(name: string) {
const node = new Node(name);
node.layer = Layers.Enum.UI_2D;
const w: Widget = node.addComponent(Widget);
w.isAlignLeft = w.isAlignRight = w.isAlignTop = w.isAlignBottom = true;
w.left = w.right = w.top = w.bottom = 0;
w.alignMode = 2;
w.enabled = true;
return node;
}
}