import { HZEngineCore, Platform } from "../index.js";
import { Save, CustomSave } from "../storage/decorator.js";
import { Storage } from "../storage/index.js";
// /
// import * as hmUI from "@zos/ui";
// import {} from "@zos/ui";
// import { getDeviceInfo, SCREEN_SHAPE_SQUARE } from "@zos/device";
// const { width, height, screenShape } = getDeviceInfo();
export class UI {
constructor(public _core: HZEngineCore) {
this._initUI();
}
private _initUI() {
this.addLayer("bg", 1);
this.addLayer("fg", 2);
this.addLayer("ct", 3);
this.addLayer("overlay", 4);
this.addRouter("page", "overlay", false);
}
private _cleanUI() {
for (let [key, value] of this.layerList) {
value.destroy();
}
this.layerList.clear();
this._routerMap.clear();
}
resetUI() {
this._cleanUI();
this._initUI();
}
// Layer
@CustomSave(
"ui.layerList",
function serializer(layerList) {
let obj: Record = {};
for (let [key, value] of this.layerList) {
obj[key] = [value.name, value.z_index];
}
return obj;
},
function deserializer(obj) {
// destroy old layer
for (let [key, value] of this.layerList) {
value.destroy();
}
this.layerList.clear();
let newLayerList = new Map>();
// create new layer
for (let key in obj) {
let newLayer = new UI.Layer(this._core, obj[key][0], obj[key][1]);
newLayerList.set(key, newLayer);
this._core.emit("afterAddLayer", newLayer);
}
return newLayerList;
}
)
private accessor _layerList: Map> = new Map();
get layerList() {
return this._layerList;
}
addLayer(name: string, z_index: number) {
this._core.emit("beforeAddLayer", name, z_index);
if (this._layerList.has(name)) throw `Layer ${name} already exist`;
let newLayer = new UI.Layer(this._core, name, z_index);
this._layerList.set(name, newLayer);
this._core.emit("afterAddLayer", newLayer);
}
getLayer(name: string): UI.Layer | undefined {
return this.layerList.get(name);
}
// View Class
private _viewClassMap: Map<
string,
UI.ViewClass, PlatformType>
> = new Map();
// _activeViewList: [name: string, layer: string, instance: UI.View][] =
// [];
registerView>(
name: string,
cls: UI.ViewClass
): void {
this._viewClassMap.set(name, cls);
}
// View
@Save("ui.nextViewId")
private accessor _nextViewId: number = 50;
@CustomSave(
"ui.viewMap",
function serializer(viewMap) {
let obj: Record = {};
for (let [id, view] of viewMap) {
// 注意viewMap中的id是number,而obj中的id會自動轉成string
if (view.isSave) obj[id] = view.serialize();
}
return obj;
},
function deserializer(obj) {
let newViewMap = new Map<
number,
UI.View, PlatformType>
>();
for (let key in obj) {
let item = obj[key] as UI.View.Serialized;
let view = this._produceViewWithId(
item.name,
item.layer,
item.prop,
Number(key)
);
view.isSave = true;
newViewMap.set(Number(key), view);
}
return newViewMap;
}
)
private accessor _viewMap: Map<
number,
UI.View, PlatformType>
> = new Map();
getView(id: number): UI.View, PlatformType> | null {
return this._viewMap.get(id) ?? null;
}
createView>(
name: string,
layer: string,
prop: PropType,
isSave: boolean
): UI.View {
let id = this._nextViewId++;
let viewInstance = this._produceViewWithId(name, layer, prop, id);
this._core.debug.log(`creating view ${viewInstance.name}`);
viewInstance.isSave = isSave;
this._viewMap.set(id, viewInstance);
return viewInstance;
}
updateView>(
viewInstance: UI.View,
new_prop: PropType
) {
viewInstance.commit(new_prop);
}
destroyView(viewInstance: UI.View, PlatformType>) {
if (viewInstance.id != null) this._viewMap.delete(viewInstance.id);
viewInstance.destroy();
}
/**由調用者提供id,創建一個View,不會處理isSave,也不會更新viewMap */
private _produceViewWithId>(
name: string,
layer: string,
prop: PropType,
id: number
): UI.View {
if (!this._viewClassMap.get(name)) {
throw "要创建的View不存在";
}
let _ViewFactory = this._viewClassMap.get(name);
let viewInstance = new _ViewFactory!(layer, this._core) as UI.View<
PropType,
PlatformType
>;
viewInstance.id = id;
viewInstance.name = name;
viewInstance.create(prop);
this._core.debug.log(`producing view ${viewInstance.name}`);
return viewInstance;
}
@CustomSave(
"ui.routerMap",
function serializer(routerMap) {
let obj: Record = {};
for (let [key, value] of routerMap) {
if (!value.isSave) continue;
obj[key] = value.serialize();
}
return obj;
},
function deserializer(obj) {
let newRouterMap = new Map>();
// reshow not save router
for (let [name, router] of this._routerMap) {
if (!router.isSave) {
if (router.length > 0) {
// TODO 因爲讀檔的時候會重置整個ui系統,所以要重新創建activeViewInstance 這裏感覺有點問題
router.activeViewInstance = this._core.ui.createView(
router.viewStack[0][0],
router.layer,
router.viewStack[0][1],
router.isSave
);
}
newRouterMap.set(name, router);
}
}
// reshow save router
for (let key in obj) {
newRouterMap.set(key, UI.Router.deserialize(this, obj[key]));
}
return newRouterMap;
}
)
private accessor _routerMap: Map> = new Map();
getRouter(tag: string) {
return this._routerMap.get(tag);
}
addRouter(tag: string, layer: string, isSave: boolean = true) {
if (this._routerMap.has(tag))
throw `Route with tag [${tag}] already exist!`;
let router = new UI.Router(this, tag, layer, isSave);
this._routerMap.set(tag, router);
return router;
}
getScreenSize() {
let [width, height] = this._core.platform.getScreenSize();
return { width, height };
}
/**
* 根据 BasicUniversalProp 计算屏幕上的位置
* @param prop 包含 BasicUniversalProp 的 prop
* @param size (可选)图像的尺寸,若不指定,返回的anchor坐标和origin坐标一样
* @returns
*/
calcPosition(
prop: UI.BasicUniversalProp,
size?: UI.Size
): {
/** 锚点(算上偏移)的屏幕位置 */
anchor: UI.Coordinate;
/** 图像左上角的屏幕位置 */
origin: UI.Coordinate;
} {
let { width, height } = this.getScreenSize();
// 1. 确定 anchor
// 2. 通过 align 确定初始位置
// 3. offset
// 返回左上角的位置
let anchor_coord = {
x:
(width * ((prop.xalign ?? 0) + 1)) / 2 + // 根据 align 求出 anchor 位置
(prop.xoffset ?? 0), // offset
y:
(height * ((prop.yalign ?? 0) + 1)) / 2 + // 根据 align 求出 anchor 位置
(prop.yoffset ?? 0), // offset
};
let origin_coord = {
x: anchor_coord.x - (((prop.xanchor ?? 0) + 1) / 2) * (size?.width ?? 0),
y: anchor_coord.y - (((prop.yanchor ?? 0) + 1) / 2) * (size?.height ?? 0),
};
return {
anchor: anchor_coord,
origin: origin_coord,
};
}
}
export namespace UI {
export type ViewClass<
PropType extends Storage.Saveable,
PlatformType extends Platform
> = {
new (layer: string, core: HZEngineCore): View<
PropType,
PlatformType
>;
};
export abstract class View<
PropType extends Storage.Saveable,
PlatformType extends Platform = any
> {
public id: number | null = null;
public name: string | null = null;
public isSave: boolean = true;
private _prop: PropType | null = null;
public get prop(): PropType | null {
return this._prop;
}
private set prop(prop: PropType | null) {
this._prop = prop;
}
constructor(
public layer: string,
public core: HZEngineCore
) {}
create(prop: PropType) {
this.prop = prop;
this.onCreate(prop);
}
protected abstract onCreate(prop: PropType): void;
commit(prop: PropType) {
this.prop = prop;
this.onCommit(prop);
}
protected abstract onCommit(prop: PropType): void;
destroy() {
this.onDestroy();
this.prop = null;
this.id = null;
}
protected abstract onDestroy(): void;
serialize(): View.Serialized {
if (this.name == null)
throw new Error("View name is null when serialize");
return {
name: this.name,
layer: this.layer,
prop: this.prop,
};
}
}
export namespace View {
export interface Serialized {
name: string;
layer: string;
prop: Storage.Saveable;
}
}
export interface BasicUniversalProp {
// 透明度 [0, 1]
alpha?: number;
// 锚点对齐位置 [0, 1]; 在不指定的情况下,默认位于屏幕中心
xalign?: number;
yalign?: number;
// 锚点相对图像位置 [0, 1]; 在不指定的情况下,以图像中心作为锚点
xanchor?: number;
yanchor?: number;
// px; 不指定就是不偏移
xoffset?: number;
yoffset?: number;
}
// export function getScreenSize(): Size {
// let [width, height] =
// return {
// width,
// height,
// };
// }
export interface Message {
who: string;
what: string;
}
export abstract class MessageView extends View<
Message,
PlatformType
> {}
export interface MenuItemData {
text: string;
position: [path: string, index: number];
enable_js_expression?: string;
}
export type MenuViewProp = {
itemList: MenuItemData[];
};
export abstract class MenuView extends View<
MenuViewProp,
PlatformType
> {}
export type Coordinate = { x: number; y: number };
export type Size = { width: number; height: number };
/**
* 立绘/道具等显示在`fg`layer上的图片View
*/
export type FgImgViewProp = {
imgPath: string;
offset: Coordinate;
size: Size;
};
export abstract class FgImgView extends View<
FgImgViewProp,
PlatformType
> {}
/**
* cg等显示在`bg`layer上的图片View
*/
export type BgImgViewProp = {
imgPath: string;
offset: Coordinate;
size: Size;
};
export abstract class BgImgView extends View<
FgImgViewProp,
PlatformType
> {}
export class Layer {
widgetFactory: ReturnType;
constructor(
public _core: HZEngineCore,
public name: string,
public z_index: number
) {
// this.widgetFactory = hmUI.createWidget(
// (hmUI.widget as any).VIEW_CONTAINER,
// {
// scroll_enable: 0,
// z_index,
// }
// ) as unknown as Layer.WidgetFactory;
this.widgetFactory = _core.platform.createUILayer({
z_index,
});
}
destroy() {
// hmUI.deleteWidget(this.widgetFactory as any);
this._core.platform.deleteUILayer(this.widgetFactory);
}
}
export namespace Layer {
// export interface WidgetFactory {
// createWidget(widgetType: number, option: Record): any;
// deleteWidget(widget: any): void;
// }
}
export class Router {
constructor(
private _ui: UI,
public tag: string,
public layer: string,
public isSave = true
) {}
serialize(): Router.Serialized {
return {
tag: this.tag,
layer: this.layer,
isSave: this.isSave,
viewStack: this.viewStack,
activeViewId: this.activeViewInstance?.id ?? null,
};
}
defaultRouteStrategy: Router.RouteStrategy = {
destroy: (viewInstance, ui) => {
this._ui._core.debug.log(`destroy view ${viewInstance.name}`);
ui.destroyView(viewInstance);
},
create: (viewName, layer, prop, ui, isSave) => {
this._ui._core.debug.log(`create view ${viewName}`);
return ui.createView(viewName, layer, prop, isSave);
},
update: (viewInstance, prop, ui) => {
this._ui._core.debug.log(`update view ${viewInstance.name}`);
ui.updateView(viewInstance, prop);
},
};
static deserialize(
ui: UI,
data: Router.Serialized
) {
let router = new Router(ui, data.tag, data.layer, data.isSave);
router.viewStack = data.viewStack;
if (data.activeViewId != null) {
let viewInstance = ui.getView(data.activeViewId);
if (!viewInstance)
throw `View [${data.activeViewId}] not found when deserialize`;
router.activeViewInstance = viewInstance;
}
return router;
}
viewStack: [view_name: string, prop: Storage.Saveable][] = [];
get length() {
return this.viewStack.length;
}
activeViewInstance: View, PlatformType> | null =
null;
push>(
view_name: string,
prop: T,
strategy?: Router.RouteStrategy
) {
if (this.activeViewInstance) {
// this._ui.destroyView(this.activeViewInstance);
// (strategy?.destroy ?? this.defaultRouteStrategy.destroy!)(
// this.activeViewInstance,
// this._ui
// );
if (strategy?.destroy) {
strategy.destroy(this.activeViewInstance, this._ui);
} else {
this.defaultRouteStrategy.destroy!(this.activeViewInstance, this._ui);
}
this.activeViewInstance = null;
}
let layerInstance = this._ui.getLayer(this.layer);
if (!layerInstance) throw `Layer [${this.layer}] not found`;
// this.activeViewInstance = (
// strategy?.create ?? this.defaultRouteStrategy.create!
// )(view_name, this.layer, prop, this._ui, this.isSave);
if (strategy?.create) {
this.activeViewInstance = strategy.create(
view_name,
this.layer,
prop,
this._ui,
this.isSave
);
} else {
this.activeViewInstance = this.defaultRouteStrategy.create!(
view_name,
this.layer,
prop,
this._ui,
this.isSave
);
}
this.viewStack.push([view_name, prop]);
}
pop>(
back_prop?: T,
strategy?: Router.RouteStrategy
) {
if (this.activeViewInstance) {
// this._ui.destroyView(this.activeViewInstance);
// (strategy?.destroy ?? this.defaultRouteStrategy.destroy!)(
// this.activeViewInstance,
// this._ui
// );
if (strategy?.destroy) {
strategy.destroy(this.activeViewInstance, this._ui);
} else {
this.defaultRouteStrategy.destroy!(this.activeViewInstance, this._ui);
}
this.activeViewInstance = null;
}
this.viewStack.pop();
if (this.viewStack.length) {
let backViewInfo = this.viewStack[this.viewStack.length - 1];
let layerInstance = this._ui.getLayer(this.layer);
if (!layerInstance) throw `Layer [${this.layer}] not found`;
// this.activeViewInstance = (
// strategy?.create ?? this.defaultRouteStrategy.create!
// )(
// backViewInfo[0],
// this.layer,
// back_prop ?? backViewInfo[1],
// this._ui,
// this.isSave
// );
if (strategy?.create) {
this.activeViewInstance = strategy.create(
backViewInfo[0],
this.layer,
back_prop ?? backViewInfo[1],
this._ui,
this.isSave
);
} else {
this.activeViewInstance = this.defaultRouteStrategy.create!(
backViewInfo[0],
this.layer,
back_prop ?? backViewInfo[1],
this._ui,
this.isSave
);
}
}
}
replace>(
view_name: string,
prop: T,
strategy?: Router.RouteStrategy
) {
if (this.activeViewInstance) {
// this._ui.destroyView(this.activeViewInstance);
// (strategy?.destroy ?? this.defaultRouteStrategy.destroy!)(
// this.activeViewInstance,
// this._ui
// );
if (strategy?.destroy) {
strategy.destroy(this.activeViewInstance, this._ui);
} else {
this.defaultRouteStrategy.destroy!(this.activeViewInstance, this._ui);
}
this.activeViewInstance = null;
}
this.viewStack.pop();
let layerInstance = this._ui.getLayer(this.layer);
if (!layerInstance) throw `Layer [${this.layer}] not found`;
// this.activeViewInstance = (
// strategy?.create ?? this.defaultRouteStrategy.create!
// )(view_name, this.layer, prop, this._ui, this.isSave);
if (strategy?.create) {
this.activeViewInstance = strategy.create(
view_name,
this.layer,
prop,
this._ui,
this.isSave
);
} else {
this.activeViewInstance = this.defaultRouteStrategy.create!(
view_name,
this.layer,
prop,
this._ui,
this.isSave
);
}
this.viewStack.push([view_name, prop]);
}
update>(
prop: T,
strategy?: Router.RouteStrategy
) {
if (!this.activeViewInstance)
throw `Update View but activeViewInstance is null`;
this.viewStack[this.viewStack.length - 1][1] = prop;
// (strategy?.update ?? this.defaultRouteStrategy.update!)(
// this.activeViewInstance,
// prop,
// this._ui
// );
if (strategy?.update) {
strategy.update(this.activeViewInstance, prop, this._ui);
} else {
this.defaultRouteStrategy.update!(
this.activeViewInstance,
prop,
this._ui
);
}
}
clear(strategy?: Router.RouteStrategy) {
if (this.activeViewInstance) {
// (strategy?.destroy ?? this.defaultRouteStrategy.destroy!)(
// this.activeViewInstance,
// this._ui
// );
if (strategy?.destroy) {
strategy.destroy(this.activeViewInstance, this._ui);
} else {
this.defaultRouteStrategy.destroy!(this.activeViewInstance, this._ui);
}
this.activeViewInstance = null;
}
this.viewStack = [];
}
}
export namespace Router {
export interface Serialized {
tag: string;
layer: string;
isSave: boolean;
viewStack: [view_name: string, prop: Storage.Saveable][];
activeViewId: number | null;
}
// 实现自定义路由策略,允许接管Router对View的创建和销毁,实现例如动画等功能
export interface RouteStrategy<
PropType extends Storage.Saveable = Storage.Saveable,
PlatformType extends Platform = any
> {
destroy?(
viewInstance: View,
ui: UI
): void;
create?(
viewName: string,
layer: string,
prop: PropType,
ui: UI,
isSave: boolean
): View, PlatformType>;
update?(
viewInstance: View,
prop: PropType,
ui: UI
): void;
}
}
}