first commit

This commit is contained in:
宫欣海
2025-02-20 11:27:28 +08:00
commit 68090ca38d
91 changed files with 9915 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
/**
* @Author: Gongxh
* @Date: 2024-12-26
* @Description: 自定义组件扩展帮助类
*/
import { UIObjectFactory } from "fairygui-cc";
import { debug } from "../tool/log";
import { PropsHelper } from "./PropsHelper";
import { _uidecorator } from "./UIDecorator";
export class ComponentExtendHelper {
public static register(): void {
for (const { ctor, res } of _uidecorator.getComponentMaps().values()) {
debug(`自定义组件注册 组件名:${res.name} 包名:${res.pkg}`);
this.registerComponent(ctor, res.pkg, res.name);
}
}
/**
* 注册自定义组件信息
* @param info
*/
private static registerComponent(ctor: any, pkg: string, name: string): void {
// 自定义组件扩展
const onConstruct = function (this: any): void {
PropsHelper.serializeProps(this, pkg);
this.onInit && this.onInit();
};
ctor.prototype.onConstruct = onConstruct;
// 自定义组件扩展
UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
}
}

79
src/ui/IWindow.ts Normal file
View File

@@ -0,0 +1,79 @@
/**
* @Author: Gongxh
* @Date: 2024-12-08
* @Description:
*/
import { AdapterType, WindowType } from "./header";
import { IWindowHeader } from "./IWindowHeader";
import { WindowHeaderInfo } from "./WindowHeaderInfo";
export interface IWindow {
/** 窗口类型 */
type: WindowType;
/** 窗口适配类型 */
adapterType: AdapterType;
/** 底部遮罩的透明度 */
bgAlpha: number;
/**
* 窗口适配 (框架内部使用)
*/
_adapted(): void;
/**
* 初始化方法 (框架内部使用)
* @param swallowTouch 是否吞噬触摸事件
*/
_init(swallowTouch: boolean, bgAlpha: number): void;
/**
* 窗口关闭 (框架内部使用)
*/
_close(): void;
/**
* 显示窗口 (框架内部使用)
* @param userdata 用户自定义数据
*/
_show(userdata?: any): void;
/**
* 从隐藏状态恢复显示
*/
_showFromHide(): void;
/**
* 隐藏窗口 (框架内部使用)
*/
_hide(): void;
/**
* 窗口被遮挡 被同组或者不同组的其他窗口覆盖 (框架内部使用)
*/
_cover(): void;
/**
* 恢复窗口遮挡 被同组或者不同组的其他窗口覆盖恢复 (框架内部使用)
*/
_recover(): void;
/**
* 调整窗口的显示层级
* @param depth
*/
_setDepth(depth: number): void;
/**
* 窗口是否显示
*/
isShowing(): boolean;
/**
* 窗口是否被遮挡了
*/
isCover(): boolean;
/**
* 窗口尺寸发生改变时被调用
*/
screenResize(): void;
/** 获取资源栏数据 */
getHeaderInfo(): WindowHeaderInfo;
setHeader(header: IWindowHeader): void;
}

45
src/ui/IWindowHeader.ts Normal file
View File

@@ -0,0 +1,45 @@
import { AdapterType } from "./header";
import { IWindow } from "./IWindow";
/**
* @Author: Gongxh
* @Date: 2024-12-08
* @Description: 窗口顶边资源栏
*/
export interface IWindowHeader {
/** 资源栏名称 */
name: string;
/** 窗口适配类型 */
adapterType: AdapterType;
/** 引用计数 */
_refCount: number;
/**
* 初始化 (内部方法)
*/
_init(): void;
/**
* 窗口适配 (内部方法)
*/
_adapted(): void;
/**
* 显示 (内部方法)
* @param {IWindow} window 所属窗口
*/
_show(window: IWindow): void;
/**
* 隐藏 (内部方法)
*/
_hide(): void;
/**
* 关闭 (内部方法)
*/
_close(): void;
/** 增加引用计数 (内部方法) */
_addRef(): void;
/** 减少引用计数 (内部方法) */
_decRef(): number;
/** 屏幕大小改变时被调用 (内部方法) */
_screenResize(): void;
}

89
src/ui/PropsHelper.ts Normal file
View File

@@ -0,0 +1,89 @@
/**
* @Author: Gongxh
* @Date: 2025-01-09
* @Description: 属性辅助类
*/
import { GComponent } from "fairygui-cc";
import { warn } from "../tool/log";
interface IPropsConfig {
[packageName: string]: { [componentName: string]: IPropsInfo };
}
interface IPropsInfo {
props: (string | number)[];
callbacks: (string | number)[];
}
export class PropsHelper {
private static _config: IPropsConfig = {};
public static setConfig(config: IPropsConfig): void {
this._config = config;
}
/** 序列化属性 */
public static serializeProps(component: GComponent, packageName: string): void {
if (!this._config) {
return;
}
const config = this._config[packageName];
if (!config) {
return;
}
const componentName = component.name;
const propsInfo = config[componentName];
if (!propsInfo) {
return;
}
// 设置属性
const props = propsInfo.props;
this.serializationPropsNode(component, props);
// 设置回调
const callbacks = propsInfo.callbacks;
this.serializationCallbacksNode(component, callbacks);
}
/** 给界面中定义的属性赋值 */
private static serializationPropsNode(component: GComponent, props: (string | number)[]) {
const propsCount = props.length;
// [name1, len, ...props1, name2, len, ...props2, ...]
let index = 0;
while (index < propsCount) {
const propName = props[index++] as string;
const endIndex = index + (props[index] as number);
let uinode = component;
while (++index <= endIndex) {
uinode = uinode.getChildAt(props[index] as number);
if (!uinode) {
warn(`无法对UI类${component.name})属性(${propName})赋值,请检查节点配置是否正确`);
break;
}
}
(component as any)[propName] = (uinode == component ? null : uinode);
}
}
private static serializationCallbacksNode(component: GComponent, callbacks: (string | number)[]) {
const propsCount = callbacks.length;
// [name1, len, ...props1, name2, len, ...props2, ...]
let index = 0;
while (index < propsCount) {
const propName = callbacks[index++] as string;
const endIndex = index + (callbacks[index] as number);
let uinode = component;
while (++index <= endIndex) {
uinode = uinode.getChildAt(callbacks[index] as number);
if (!uinode) {
warn(`无法对UI类${component.name})的(${propName})设置回调,请检查节点配置是否正确`);
break;
}
}
if (uinode != component) {
uinode.onClick((component as any)[propName], component);
}
}
}
}

180
src/ui/UIDecorator.ts Normal file
View File

@@ -0,0 +1,180 @@
/**
* @Author: Gongxh
* @Date: 2024-12-11
* @Description: UI 装饰器
*/
import { ObjectHelper } from "../tool/helper/ObjectHelper";
export namespace _uidecorator {
const UIPropMeta = "__uipropmeta__"
const UICBMeta = "__uicbmeta__"
interface IUIInfoBase {
/** 构造函数 */
ctor: any;
/** 属性 */
props: Record<string, 1>;
/** 方法 */
callbacks: Record<string, Function>;
}
/**
* 窗口属性注册数据结构
*/
interface UIWindowInfo extends IUIInfoBase {
/** 配置信息 */
res: {
/** 窗口组名称 */
group: string;
/** fgui包名 */
pkg: string;
/** 窗口名 */
name: string;
};
}
/** 用来存储窗口注册信息 */
const uiclassMap: Map<any, UIWindowInfo> = new Map();
/** 获取窗口注册信息 */
export function getWindowMaps(): Map<any, UIWindowInfo> {
return uiclassMap;
}
/**
* 窗口装饰器
* @param {string} groupName 窗口组名称
* @param {string} pkgName fgui包名
* @param {string} name 窗口名 (与fgui中的组件名一一对应)
*/
export function uiclass(groupName: string, pkgName: string, name: string): Function {
/** target 类的构造函数 */
return function (ctor: any): void {
// debug(`uiclass >${JSON.stringify(res)}<`);
// debug(`uiclass prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
uiclassMap.set(ctor, {
ctor: ctor,
props: ctor[UIPropMeta] || null,
callbacks: ctor[UICBMeta] || null,
res: {
group: groupName,
pkg: pkgName,
name: name,
},
});
};
}
/**
* 组件属性注册数据结构
*/
interface IUIComInfo extends IUIInfoBase {
/** 配置信息 */
res: {
/** fgui包名 */
pkg: string;
/** 组件名 */
name: string;
};
}
/** 用来存储组件注册信息 */
let uicomponentMap: Map<string, IUIComInfo> = new Map();
/** 获取组件注册信息 */
export function getComponentMaps(): Map<any, IUIComInfo> {
return uicomponentMap;
}
/**
* UI组件装饰器
* @param {string} pkg 包名
* @param {string} name 组件名
*/
export function uicom(pkg: string, name: string): Function {
return function (ctor: any): void {
// log(`pkg:【${pkg}】 uicom prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
uicomponentMap.set(ctor, {
ctor: ctor,
props: ctor[UIPropMeta] || null,
callbacks: ctor[UICBMeta] || null,
res: {
pkg: pkg,
name: name,
}
});
};
}
/**
* header属性注册数据结构
*/
interface IUIHeaderInfo extends IUIInfoBase {
/** 配置信息 */
res: {
/** fgui包名 */
pkg: string;
/** 组件名 */
name: string;
};
}
/** 用来存储组件注册信息 */
let uiheaderMap: Map<string, IUIHeaderInfo> = new Map();
/** 获取header注册信息 */
export function getHeaderMaps(): Map<any, IUIHeaderInfo> {
return uiheaderMap;
}
/**
* UI header装饰器
* @param {string} pkg 包名
* @param {string} name 组件名
*/
export function uiheader(pkg: string, name: string): Function {
return function (ctor: any): void {
// log(`pkg:【${pkg}】 uiheader prop >${JSON.stringify(ctor[UIPropMeta] || {})}<`);
uiheaderMap.set(ctor, {
ctor: ctor,
props: ctor[UIPropMeta] || null,
callbacks: ctor[UICBMeta] || null,
res: {
pkg: pkg,
name: name,
}
});
};
}
/**
* UI属性装饰器
* @param {Object} target 实例成员的类的原型
* @param {string} name 属性名
*
* example: @uiprop node: GObject
*/
export function uiprop(target: Object, name: string): any {
// debug("属性装饰器:", target.constructor, name);
ObjectHelper.getObjectProp(target.constructor, UIPropMeta)[name] = 1;
}
/**
* 方法装饰器 (给点击事件用)
* @param {Object} target 实例成员的类的原型
* @param {string} name 方法名
*/
export function uiclick(target: Object, name: string, descriptor: PropertyDescriptor): void {
// debug("方法装饰器:", target.constructor, name, descriptor);
ObjectHelper.getObjectProp(target.constructor, UICBMeta)[name] = descriptor.value;
}
}
let _global = globalThis || window || global;
(_global as any)["getKunpoRegisterWindowMaps"] = function () {
return _uidecorator.getWindowMaps() as any;
};
(_global as any)["getKunpoRegisterComponentMaps"] = function () {
return _uidecorator.getComponentMaps() as any;
};
(_global as any)["getKunpoRegisterHeaderMaps"] = function () {
return _uidecorator.getHeaderMaps() as any;
};

393
src/ui/WindowGroup.ts Normal file
View File

@@ -0,0 +1,393 @@
/**
* @Author: Gongxh
* @Date: 2024-12-08
* @Description: 窗口组 (在同一个窗口容器的上的窗口)
*/
import { Color, warn } from "cc";
import { GComponent, GGraph, UIPackage } from "fairygui-cc";
import { WindowBase } from "../fgui/WindowBase";
import { WindowHeader } from "../fgui/WindowHeader";
import { Screen } from "../global/Screen";
import { WindowType } from "./header";
import { IWindow } from "./IWindow";
import { PropsHelper } from "./PropsHelper";
import { WindowManager } from "./WindowManager";
import { WindowInfo } from "./WindowResPool";
export class WindowGroup {
/** 窗口组的名字 */
private _name: string = "";
/** 窗口组的根节点 */
private _root: GComponent;
/** 忽略顶部窗口查询 */
private _ignoreQuery: boolean = false;
/** 吞噬触摸事件 */
private _swallowTouch: boolean = false;
/** 窗口容器中的窗口名列表 */
private _windowNames: string[] = [];
/** 窗口顶部资源栏 */
private _headers: Map<string, WindowHeader> = new Map();
/** 半透明遮罩的透明度 */
private _bgAlpha: number = 0;
/** 半透明节点 */
private _alphaGraph: GGraph;
/** 半透明遮罩的颜色 */
private _color: Color = new Color(0, 0, 0, 255);
/**
* 获取窗口组的名称。
* @returns {string} 窗口组的名称。
*/
public get name(): string {
return this._name;
}
/**
* 获取当前窗口组中窗口的数量。
* @returns 窗口数量
*/
public get size(): number {
return this._windowNames.length;
}
/**
* 获取是否忽略查询的状态。
* @returns {boolean} 如果忽略查询,则返回 true否则返回 false。
*/
public get isIgnore(): boolean {
return this._ignoreQuery;
}
/**
* 实例化
* @param name 组名
* @param root 窗口组的根节点 一个fgui的组件
* @param ignoreQuery 是否忽略顶部窗口查询
* @param swallowTouch 是否吞掉触摸事件
*/
constructor(name: string, root: GComponent, ignoreQuery: boolean, swallowTouch: boolean, bgAlpha: number) {
this._name = name;
this._root = root;
this._ignoreQuery = ignoreQuery;
this._swallowTouch = swallowTouch;
this._bgAlpha = bgAlpha;
const alphaGraph = new GGraph();
alphaGraph.touchable = false;
alphaGraph.name = "bgAlpha";
alphaGraph.setPosition(root.width * 0.5, root.height * 0.5);
alphaGraph.setSize(root.width, root.height, true);
alphaGraph.setPivot(0.5, 0.5, true);
root.addChild(alphaGraph);
this._alphaGraph = alphaGraph;
}
/**
* 根据窗口名创建窗口 并添加到显示节点
* @param windowName 窗口名
*/
private _createWindow(pkg: string, name: string): WindowBase {
let window = UIPackage.createObject(pkg, name) as WindowBase;
window.name = name;
PropsHelper.serializeProps(window, pkg);
window._init(this._swallowTouch, this._bgAlpha);
window._adapted();
this._createHeader(window);
// 添加到显示节点
this._addWindow(window);
return window;
}
private _addWindow(window: WindowBase): void {
this._root.addChild(window);
WindowManager._addWindow(window.name, window);
}
public showWindow(info: WindowInfo, userdata?: any): void {
let name = info.name;
let window = WindowManager.getWindow(name);
if (window) {
window._show(userdata);
} else {
window = this._createWindow(info.pkg, name);
this._processWindowCloseStatus(window);
this._windowNames.push(name);
window._show(userdata);
}
this._moveWindowToTop(name);
// 处理header的显示
this._processHeaderStatus();
// 显示窗口组
this._root.visible = true;
}
/**
* 移除指定名称的窗口。
* @param name 窗口的名称。
*/
public _removeWindow(name: string): void {
let index = this._windowNames.lastIndexOf(name);
let lastIndex = this.size - 1;
if (index < 0) {
warn(`窗口组${this._name}中未找到窗口${name} 删除失败`);
return;
}
let window = WindowManager.getWindow<WindowBase>(name);
let header = window.getHeader();
header && this._removeHeader(header);
this._windowNames.splice(index, 1);
// 关闭窗口 并从窗口map中移除
WindowManager._removeWindow(name);
// 处理窗口显示和隐藏状态
this._processWindowHideStatus(this.size - 1, true);
if (this.size == 0) {
// 窗口组中不存在窗口时 隐藏窗口组节点
this._root.visible = false;
} else if (lastIndex == index && index > 0) {
// 删除的窗口是最后一个 并且前边还有窗口 调整半透明节点的显示层级
let topName = this.getTopWindowName();
let window = WindowManager.getWindow(topName);
// 调整半透明遮罩
this._adjustAlphaGraph(window);
// 调整窗口的显示层级
window._setDepth(this._root.numChildren - 1);
}
this._processHeaderStatus();
}
/**
* 将指定名称的窗口移动到窗口组的最顶层。
* @param name 窗口的名称。
*/
public _moveWindowToTop(name: string): boolean {
let isMoved = false;
if (this.size == 0) {
warn(`WindowGroup.moveWindowToTop: window group 【${this._name}】 is empty`);
return;
}
if (this._windowNames[this.size - 1] == name) {
// 已经在最顶层了
} else {
const index = this._windowNames.indexOf(name);
if (index == -1) {
warn(`WindowGroup.moveWindowToTop: window 【${name}】 not found in window group 【${this._name}`);
return;
}
if (index < this._windowNames.length - 1) {
this._windowNames.splice(index, 1);
// 放到数组的末尾
this._windowNames.push(name);
isMoved = true;
}
}
let window = WindowManager.getWindow(name);
// 先调整半透明遮罩
this._adjustAlphaGraph(window);
// 再调整窗口的显示层级
window._setDepth(this._root.numChildren - 1);
// 处理窗口显示和隐藏状态
this._processWindowHideStatus(this.size - 1, isMoved);
}
/**
* 处理index下层窗口的隐藏状态的私有方法。递归调用
* @param index - 窗口索引
* @param isRecursion - 是否递归调用
*/
private _processWindowHideStatus(index: number, isRecursion: boolean = true): void {
if (index < 0) {
return;
}
let windowName = this._windowNames[index];
let curWindow = WindowManager.getWindow(windowName);
// 如果当前是当前组中的最后一个窗口并且当前窗口是隐藏状态 则恢复隐藏
if (index == this.size - 1 && !curWindow.isShowing()) {
curWindow._showFromHide();
}
if (index == 0) {
return;
}
let windowType = curWindow.type;
if (windowType == WindowType.HideAll) {
for (let i = index - 1; i >= 0; --i) {
let name = this._windowNames[i];
const window = WindowManager.getWindow(name);
window.isShowing() && window._hide();
}
return;
} else if (windowType == WindowType.HideOne) {
// 隐藏前一个
let prevWindowName = this._windowNames[index - 1];
let prevWindow = WindowManager.getWindow(prevWindowName);
prevWindow.isShowing() && prevWindow._hide();
} else {
// 如果前一个窗口被隐藏了 需要恢复显示
let prevWindowName = this._windowNames[index - 1];
let prevWindow = WindowManager.getWindow(prevWindowName);
!prevWindow.isShowing() && prevWindow._showFromHide();
}
isRecursion && this._processWindowHideStatus(index - 1, isRecursion);
}
/**
* 新创建窗口时,根据新创建的窗口类型
* 处理上一个窗口或者所有窗口的关闭
*/
private _processWindowCloseStatus(window: IWindow): void {
// 新创建窗口 如果需要关闭窗口或者关闭所有窗口 处理窗口的关闭
if (window.type == WindowType.CloseOne) {
let size = this.size;
while (size > 0) {
let name = this._windowNames.pop();
let window = WindowManager.getWindow<WindowBase>(name);
let header = window.getHeader();
header && this._removeHeader(header);
WindowManager._removeWindow(name);
break;
}
} else if (window.type == WindowType.CloseAll) {
let size = this.size;
for (let i = size; i > 0;) {
let name = this._windowNames[--i]
let window = WindowManager.getWindow<WindowBase>(name);
let header = window.getHeader();
header && this._removeHeader(header);
WindowManager._removeWindow(name);
}
this._windowNames.length = 0;
}
}
/** 处理header的显示状态 并调整层级 */
private _processHeaderStatus(): void {
// 找到第一个要显示的header
let firstHeader: WindowHeader = null;
let firstWindow: IWindow = null;
let index = this.size - 1;
for (let i = this.size - 1; i >= 0; --i) {
let name = this._windowNames[i];
let window = WindowManager.getWindow<WindowBase>(name);;
if (window.isShowing() && window.getHeader()) {
firstWindow = window;
firstHeader = window.getHeader();
index = i;
break;
}
}
this._headers.forEach((header, name) => {
this._root.setChildIndex(header, 0);
if (!firstHeader && header.visible) {
header._hide();
} else if (firstHeader) {
if (firstHeader.name == name && !header.visible) {
header._show(firstWindow);
} else if (firstHeader.name != name && header.visible) {
header._hide();
}
}
});
if (firstHeader) {
if (index == this.size - 1) {
this._root.setChildIndex(firstHeader, this._root.numChildren - 1);
} else {
this._root.setChildIndex(firstHeader, this._root.numChildren - this.size + index - 1);
}
}
}
/**
* 调整指定窗口的透明度图形。并根据窗口的背景透明度绘制半透明遮罩。
* @param window - 需要调整透明度的窗口对象。
*/
private _adjustAlphaGraph(window: IWindow): void {
this._root.setChildIndex(this._alphaGraph, this._root.numChildren - 1);
// 半透明遮罩绘制
this._color.a = window.bgAlpha * 255;
this._alphaGraph.clearGraphics();
this._alphaGraph.drawRect(0, this._color, this._color);
}
public hasWindow(name: string): boolean {
return this._windowNames.indexOf(name) >= 0;
}
/**
* 获取窗口组中顶部窗口的名称。
* @returns {string} 顶部窗口的名称。
*/
public getTopWindowName(): string {
if (this.size > 0) {
return this._windowNames[this.size - 1];
}
warn(`WindowGroup.getTopWindowName: window group 【${this._name}】 is empty`);
}
/** 根据窗口 创建顶部资源栏 (内部方法) */
private _createHeader(window: IWindow): void {
// 只有创建界面的时候, 才会尝试创建顶部资源栏
let headerInfo = window.getHeaderInfo();
if (!headerInfo) {
return;
}
let name = headerInfo.name;
let header = this._getHeader(name);
if (header) {
window.setHeader(header);
header._addRef();
} else {
// 创建header节点
let { pkg } = WindowManager._getResPool().getHeader(name);
let newHeader = UIPackage.createObject(pkg, name) as WindowHeader;
newHeader.name = name;
newHeader.opaque = false;
window.setHeader(newHeader);
newHeader.visible = false;
PropsHelper.serializeProps(newHeader, pkg);
newHeader._init();
newHeader._adapted();
this._root.addChild(newHeader);
// 添加到显示节点
newHeader._addRef();
this._headers.set(newHeader.name, newHeader);
}
}
/**
* 顶部资源栏窗口 从管理器中移除 (内部方法)
* @param header 资源栏
*/
public _removeHeader(header: WindowHeader): void {
if (this._headers.has(header.name)) {
let refCount = header._decRef();
if (refCount <= 0) {
this._headers.delete(header.name);
header._close();
}
}
}
/**
* 获取顶部资源栏 (内部方法)
* @param name 资源栏的名称
*/
public _getHeader<T extends WindowHeader>(name: string): T | null {
return this._headers.get(name) as T;
}
/** 屏幕大小改变时被调用 (内部方法) */
public _screenResize(): void {
this._headers.forEach((header) => {
header._screenResize();
});
this._alphaGraph.setPosition(Screen.ScreenWidth * 0.5, Screen.ScreenHeight * 0.5);
this._alphaGraph.setSize(Screen.ScreenWidth, Screen.ScreenHeight, true);
this._alphaGraph.setPivot(0.5, 0.5, true);
}
}

View File

@@ -0,0 +1,25 @@
/**
* @Author: Gongxh
* @Date: 2025-01-10
* @Description: 窗口顶部资源栏信息
*/
export class WindowHeaderInfo {
/** header名字 */
name: string;
/** 自定义数据 用于Header窗口 onShow方法的自定义参数 */
userdata: any;
/**
* 创建 WindowHeaderInfo
* @param {string} name header窗口名
* @param {*} [userdata] 自定义数据
* @returns {WindowHeaderInfo}
*/
static create(name: string, userdata?: any): WindowHeaderInfo {
const info = new WindowHeaderInfo();
info.name = name;
info.userdata = userdata;
return info;
}
}

216
src/ui/WindowManager.ts Normal file
View File

@@ -0,0 +1,216 @@
/**
* @Author: Gongxh
* @Date: 2024-12-07
* @Description: 窗口管理类
*/
import { debug, warn } from "../tool/log";
import { ComponentExtendHelper } from "./ComponentExtendHelper";
import { IWindow } from "./IWindow";
import { _uidecorator } from "./UIDecorator";
import { WindowGroup } from "./WindowGroup";
import { WindowResPool } from "./WindowResPool";
export class WindowManager {
/** 窗口组 */
private static _groups: Map<string, WindowGroup> = new Map();
/** 不忽略查询的窗口组名 */
private static _queryGroupNames: string[] = [];
/** 所有窗口全部放到这个map中 */
private static _windows: Map<string, IWindow> = new Map();
/** 初始化时传入实例 */
private static _resPool: WindowResPool;
/**
* 打开一个窗口
* @param windowName 窗口名
* @param userdata 用户数据
*/
public static showWindow(windowName: string, userdata?: any): void {
//TODO::如果没有资源 加载资源
this.showWindowIm(windowName, userdata);
}
/**
* 显示指定名称的窗口,并传递可选的用户数据。
* @param windowName - 窗口的名称。
* @param userdata - 可选参数,用于传递给窗口的用户数据。
*/
public static showWindowIm(windowName: string, userdata?: any): void {
const info = this._resPool.get(windowName);
const windowGroup = this.getWindowGroup(info.group);
windowGroup.showWindow(info, userdata);
}
/**
* 关闭窗口
* @param windowName 窗口名
*/
public static closeWindow(windowName: string): void {
if (!this._windows.has(windowName)) {
warn(`窗口不存在 ${windowName} 不需要关闭`);
return;
}
// 先在窗口组中移除
let info = this._resPool.get(windowName);
const windowGroup = this.getWindowGroup(info.group);
windowGroup._removeWindow(windowName);
// 窗口组中没有窗口了
if (windowGroup.size == 0) {
let index = this._queryGroupNames.indexOf(windowGroup.name);
if (index > 0 && windowGroup.name == this.getTopGroupName()) {
do {
const groupName = this._queryGroupNames[--index];
let group = this.getWindowGroup(groupName);
if (group.size > 0) {
this.getWindow(group.getTopWindowName())._recover();
break;
}
} while (index >= 0);
}
}
}
/**
* 获取当前最顶层的窗口实例。
* @template T - 窗口实例的类型,必须继承自 IWindow 接口。
* @returns {T | null} - 返回最顶层的窗口实例,如果没有找到则返回 null。
* @description 该方法会遍历所有窗口组,找到最顶层的窗口并返回其实例。
*/
public static getTopWindow<T extends IWindow>(): T | null {
let len = this._queryGroupNames.length;
for (let i = len; i > 0;) {
let group = this.getWindowGroup(this._queryGroupNames[--i]);
if (group.size > 0) {
return this.getWindow<T>(group.getTopWindowName());
}
}
return null;
}
/**
* 根据窗口名称获取窗口实例。
* @template T 窗口类型必须继承自IWindow接口。
* @param name 窗口的名称。
* @returns 如果找到窗口则返回对应类型的窗口实例否则返回null。
*/
public static getWindow<T extends IWindow>(name: string): T | null {
return this._windows.get(name) as T;
}
/**
* 检查是否存在指定名称的窗口。
* @param name 窗口的名称。
* @returns 如果存在指定名称的窗口,则返回 true否则返回 false。
*/
public static hasWindow(name: string): boolean {
return this._windows.has(name);
}
/**
* 根据给定的组名获取窗口组。如果组不存在,则抛出错误。
* @param groupName 窗口组的名称。
* @returns 返回找到的窗口组。
*/
public static getWindowGroup(groupName: string): WindowGroup {
if (this._groups.has(groupName)) {
return this._groups.get(groupName);
}
throw new Error(`WindowManager.getWindowGroup: window group 【${groupName}】 not found`);
}
/**
* 获取当前顶层窗口组的名称。
* 返回第一个包含至少一个窗口的窗口组名称。(该方法只检查不忽略查询的窗口组)
* 如果没有找到任何包含窗口的组,则返回空字符串。
*/
public static getTopGroupName(): string {
let len = this._queryGroupNames.length;
for (let i = len - 1; i >= 0; i--) {
let name = this._queryGroupNames[i];
let group = this._groups.get(name);
if (group.size > 0) {
return name;
}
}
return "";
}
/**
* 初始化窗口管理器,设置资源池。 (框架内部使用)
* @param resPool - 窗口资源池实例。
*/
public static _init(resPool: WindowResPool): void {
this._resPool = resPool;
}
/**
* 向窗口管理器添加一个新窗口。 (框架内部使用)
* @param name 窗口的唯一标识符。
* @param window 要添加的窗口对象,需实现 IWindow 接口。
*/
public static _addWindow(name: string, window: IWindow): void {
this._windows.set(name, window);
}
/**
* 移除指定名称的窗口。 (框架内部使用)
* @param name 窗口的名称。
*/
public static _removeWindow(name: string): void {
if (this.hasWindow(name)) {
this._windows.get(name)._close();
this._windows.delete(name);
}
}
/**
* 注册所有UI窗口类到资源池中。 (框架内部使用)
* 该方法遍历所有通过_uidecorator.getWindowMaps()获取的窗口映射,
* 并将每个窗口的资源名称、构造函数、分组和包信息添加到资源池中。
*/
public static registerUI(): void {
// 窗口注册
for (const { ctor, res } of _uidecorator.getWindowMaps().values()) {
debug(`窗口注册 窗口名:${res.name} 包名:${res.pkg} 组名:${res.group}`);
this._resPool.add(ctor, res.group, res.pkg, res.name);
}
// 窗口header注册
for (const { ctor, res } of _uidecorator.getHeaderMaps().values()) {
debug(`header注册 header名:${res.name} 包名:${res.pkg}`);
this._resPool.addHeader(ctor, res.pkg, res.name);
}
// 组件注册
ComponentExtendHelper.register();
}
/**
* 向窗口管理器添加一个窗口组 如果窗口组名称已存在,则抛出错误. (内部方法)
* @param group 要添加的窗口组
*/
public static _addWindowGroup(group: WindowGroup): void {
if (this._groups.has(group.name)) {
throw new Error(`WindowManager._addWindowGroup: window group 【${group.name}】 already exists`);
}
this._groups.set(group.name, group);
// 不忽略查询 加到列表中
!group.isIgnore && this._queryGroupNames.push(group.name);
}
/**
* 屏幕大小改变时 调用所有窗口的screenResize方法 (内部方法)
*/
public static _screenResize(): void {
this._windows.forEach((window: IWindow) => {
window.screenResize();
});
this._groups.forEach((group: WindowGroup) => {
group._screenResize();
});
}
public static _getResPool(): WindowResPool {
return this._resPool;
}
}

85
src/ui/WindowResPool.ts Normal file
View File

@@ -0,0 +1,85 @@
/**
* @Author: Gongxh
* @Date: 2024-12-13
* @Description:
*/
import { UIObjectFactory } from "fairygui-cc";
export interface WindowInfo {
/** 类的构造函数 */
ctor: any;
/** 窗口组名 */
group: string;
/** fgui包名 */
pkg: string;
/** 窗口名 */
name: string;
}
export interface HeaderInfo {
ctor: any;
pkg: string;
}
export class WindowResPool {
/** 窗口信息池 */
protected _windowInfos: Map<string, WindowInfo> = new Map<string, any>();
/** 窗口header信息池 */
protected _headerInfos: Map<string, HeaderInfo> = new Map<string, any>();
/** 可扩展 窗口资源引用计数 */
/**
* 注册窗口信息
* @param info
*/
public add(ctor: any, group: string, pkg: string, name: string): void {
if (this.has(name)) {
throw new Error(`窗口【${name}】信息已注册 请勿重复注册`);
}
this._windowInfos.set(name, {
ctor: ctor,
group: group,
pkg: pkg,
name: name
});
// 窗口组件扩展
UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
}
public has(name: string): boolean {
return this._windowInfos.has(name);
}
public get(name: string): WindowInfo {
if (!this.has(name)) {
throw new Error(`窗口【${name}】未注册,请使用 _uidecorator.uiclass 注册窗口`);
}
return this._windowInfos.get(name);
}
/**
* 注册窗口header信息
* @param info
*/
public addHeader(ctor: any, pkg: string, name: string): void {
this._headerInfos.set(name, {
ctor: ctor,
pkg: pkg
});
// 窗口header扩展
UIObjectFactory.setExtension(`ui://${pkg}/${name}`, ctor);
}
public hasHeader(name: string): boolean {
return this._headerInfos.has(name);
}
public getHeader(name: string): HeaderInfo {
if (!this.hasHeader(name)) {
throw new Error(`窗口header【${name}】未注册,请使用 _uidecorator.uiheader 注册窗口header`);
}
return this._headerInfos.get(name);
}
}

30
src/ui/header.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* @Author: Gongxh
* @Date: 2024-12-08
* @Description: 窗口的一些类型配置
*/
/** 窗口显示时,对其他窗口的隐藏处理类型 */
export enum WindowType {
/** 不做任何处理 */
Normal = 0,
/** 关闭所有 */
CloseAll = 1 << 0,
/** 关闭上一个 */
CloseOne = 1 << 1,
/** 隐藏所有 */
HideAll = 1 << 2,
/** 隐藏上一个 */
HideOne = 1 << 3,
}
/** 窗口适配类型,默认全屏 */
export enum AdapterType {
/** 全屏适配 */
Full = 0,
/** 空出刘海 */
Bang = 1,
/** 固定的 不适配 */
Fixed = 2,
}