refactor: 把 system/ui 改为 monorepo

This commit is contained in:
AncTe committed 2025-03-04 22:50:53 +08:00
1 parent b3d5ad2ca9
commit a5e6f5b5f6
21 files changed
+118 -168

No files matched your search

-1
View File
@@ -1 +0,0 @@
export * from './ui';
-42
View File
@@ -1,42 +0,0 @@
import { defineComponent, VNode } from 'vue';
import { IUIMountable } from './shared';
import { SetupComponentOptions } from '@/module';
export interface UIContainerProps {
controller: IUIMountable;
}
const containerConfig = {
props: ['controller']
} satisfies SetupComponentOptions<UIContainerProps>;
export const UIContainer = defineComponent<UIContainerProps>(props => {
const data = props.controller;
const back = data.backIns;
return (): VNode[] => {
const elements: VNode[] = [];
const b = back.value;
if (b && data.showBack.value && !b.hidden) {
elements.push(
<b.ui.component
{...b.vBind}
controller={data}
instance={b}
key={b.key}
hidden={b.hidden && !b.alwaysShow}
></b.ui.component>
);
}
return elements.concat(
data.stack.map(v => (
<v.ui.component
{...v.vBind}
key={v.key}
controller={data}
instance={v}
hidden={v.hidden && !v.alwaysShow}
></v.ui.component>
))
);
};
}, containerConfig);
-311
View File
@@ -1,311 +0,0 @@
import { logger } from '@motajs/common';
import EventEmitter from 'eventemitter3';
import {
IGameUI,
IKeepController,
IUIInstance,
IUIMountable,
UIComponent,
UIProps
} from './shared';
import { UIInstance } from './instance';
import {
computed,
ComputedRef,
h,
reactive,
ref,
Ref,
shallowRef,
ShallowRef,
VNode
} from 'vue';
import { UIContainer } from './container';
export const enum UIMode {
/** 仅显示最后一个 UI,在关闭时,只会关闭指定的 UI */
LastOnly,
/** 显示所有非手动隐藏的 UI,在关闭时,只会关闭指定 UI */
All,
/** 仅显示最后一个 UI,在关闭时,在此之后的所有 UI 会全部关闭 */
LastOnlyStack,
/** 显示所有非手动隐藏的 UI,在关闭时,在此之后的所有 UI 会全部关闭 */
AllStack,
/** 自定义 UI 显示模式 */
Custom
}
export interface IUICustomConfig {
/**
* 打开一个新的 UI
* @param ins 要打开的 UI 实例
* @param stack 当前的 UI 栈,还未将 UI 实例加入栈中
*/
open(ins: IUIInstance, stack: IUIInstance[]): void;
/**
* 关闭一个 UI
* @param ins 要关闭的 UI 实例
* @param stack 当前的 UI 栈,还未将 UI 实例移除
* @param index 这个 UI 实例在 UI 栈中的索引
*/
close(ins: IUIInstance, stack: IUIInstance[], index: number): void;
/**
* 隐藏一个 UI
* @param ins 要隐藏的 UI 实例,还未进入隐藏状态
* @param stack 当前的 UI 栈
* @param index 这个 UI 实例在 UI 栈中的索引
*/
hide(ins: IUIInstance, stack: IUIInstance[], index: number): void;
/**
* 显示一个 UI
* @param ins 要显示的 UI 实例,还未进入显示状态
* @param stack 当前的 UI 栈
* @param index 这个 UI 实例在 UI 栈中的索引
*/
show(ins: IUIInstance, stack: IUIInstance[], index: number): void;
/**
* 更新所有 UI 的显示,一般会在显示模式更改时调用
* @param stack 当前的 UI 栈
*/
update(stack: IUIInstance[]): void;
}
interface UIControllerEvent {}
export class UIController
extends EventEmitter<UIControllerEvent>
implements IUIMountable
{
static controllers: Map<string, UIController> = new Map();
/** 当前的 ui 栈 */
readonly stack: IUIInstance[] = reactive([]);
/** UI 显示方式 */
mode: UIMode = UIMode.LastOnlyStack;
/** 这个 UI 实例的背景,当这个 UI 处于显示模式时,会显示背景 */
background?: IGameUI;
/** 背景 UI 实例 */
readonly backIns: ShallowRef<IUIInstance | null> = shallowRef(null);
/** 当前是否显示背景 UI */
readonly showBack: ComputedRef<boolean> = computed(
() => this.userShowBack.value && this.sysShowBack.value
);
/** 当前是否显示 UI */
get active() {
return this.showBack.value;
}
/** 自定义显示模式下的配置信息 */
private config?: IUICustomConfig;
/** 是否维持背景 UI */
private keepBack: boolean = false;
/** 用户是否显示背景 UI */
private readonly userShowBack: Ref<boolean> = ref(true);
/** 系统是否显示背景 UI */
private readonly sysShowBack: Ref<boolean> = ref(false);
/**
* 创建一个 ui 控制器
* @param id 这个控制器的唯一标识符
*/
constructor(public readonly id: string) {
super();
if (UIController.controllers.has(id)) {
logger.warn(57, id);
} else {
UIController.controllers.set(id, this);
}
}
/**
* 渲染这个 UI
*/
render(): VNode {
return h(UIContainer, { controller: this });
}
/**
* 设置背景 UI
* @param back 这个 UI 控制器的背景 UI
*/
setBackground(back: IGameUI) {
this.background = back;
}
/**
* 隐藏背景 UI
*/
hideBackground() {
this.userShowBack.value = false;
}
/**
* 显示背景 UI
*/
showBackground() {
this.userShowBack.value = true;
}
/**
* 维持背景 UI,一般用于防闪烁,例如使用道具时可能在关闭道具栏后打开新 UI,这时就需要防闪烁
*/
keep(): IKeepController {
this.keepBack = true;
return {
safelyUnload: () => {
if (this.stack.length > 0) return;
this.sysShowBack.value = false;
this.keepBack = false;
},
unload: () => {
this.sysShowBack.value = false;
this.keepBack = false;
}
};
}
open<T extends UIComponent>(
ui: IGameUI<T>,
vBind: UIProps<T>,
alwaysShow: boolean = false
): IUIInstance<T> {
const ins = new UIInstance(ui, vBind, alwaysShow);
switch (this.mode) {
case UIMode.LastOnly:
case UIMode.LastOnlyStack:
this.stack.forEach(v => v.hide());
this.stack.push(ins);
break;
case UIMode.All:
case UIMode.AllStack:
this.stack.push(ins);
break;
case UIMode.Custom:
this.config?.open(ins, this.stack);
break;
}
this.sysShowBack.value = true;
return ins;
}
close(ui: IUIInstance) {
const index = this.stack.indexOf(ui);
if (index === -1) return;
switch (this.mode) {
case UIMode.LastOnly: {
this.stack.splice(index, 1);
this.stack.forEach(v => v.hide());
const last = this.stack.at(-1);
if (!last) break;
last.show();
break;
}
case UIMode.LastOnlyStack: {
this.stack.splice(index);
this.stack.forEach(v => v.hide());
const last = this.stack[index - 1];
if (!last) break;
last.show();
break;
}
case UIMode.All: {
this.stack.splice(index, 1);
break;
}
case UIMode.AllStack: {
this.stack.splice(index);
break;
}
case UIMode.Custom: {
this.config?.close(ui, this.stack, index);
break;
}
}
if (!this.keepBack && this.stack.length === 0) {
this.sysShowBack.value = false;
}
this.keepBack = false;
}
closeAll(ui?: IGameUI): void {
if (!ui) {
this.stack.splice(0);
} else {
const list = this.stack.filter(v => v.ui === ui);
list.forEach(v => this.close(v));
}
}
hide(ins: IUIInstance): void {
const index = this.stack.indexOf(ins);
if (index === -1) return;
if (this.mode === UIMode.Custom) {
this.config?.hide(ins, this.stack, index);
} else {
ins.hide();
}
}
show(ins: IUIInstance): void {
const index = this.stack.indexOf(ins);
if (index === -1) return;
if (this.mode === UIMode.Custom) {
this.config?.show(ins, this.stack, index);
} else {
ins.show();
}
}
/**
* 设置为仅显示最后一个 UI
* @param stack 是否设置为栈模式,即删除一个 UI 后,其之后打开的 UI 是否也一并删除
*/
lastOnly(stack: boolean = true) {
if (stack) {
this.mode = UIMode.LastOnlyStack;
} else {
this.mode = UIMode.LastOnly;
}
this.stack.forEach(v => v.hide());
this.stack.at(-1)?.show();
}
/**
* 设置为显示所有 UI
* @param stack 是否设置为栈模式,即删除一个 UI 后,其之后打开的 UI 是否也一并删除
*/
showAll(stack: boolean = false) {
if (stack) {
this.mode = UIMode.AllStack;
} else {
this.mode = UIMode.All;
}
this.stack.forEach(v => v.show());
}
/**
* 使用自定义的显示模式
* @param config 自定义显示模式的配置
*/
showCustom(config: IUICustomConfig) {
this.mode = UIMode.Custom;
this.config = config;
config.update(this.stack);
}
/**
* 获取一个元素上的 ui 控制器
* @param id 要获取的 ui 控制器的唯一标识符
*/
static getController(id: string): UIController | null {
const res = this.controllers.get(id);
return res ?? null;
}
}
-5
View File
@@ -1,5 +0,0 @@
export * from './controller';
export * from './ui';
export * from './shared';
export * from './instance';
export * from './container';
-51
View File
@@ -1,51 +0,0 @@
import { Props } from '@motajs/render';
import { IGameUI, IUIInstance, UIComponent, UIProps } from './shared';
import EventEmitter from 'eventemitter3';
import { markRaw, mergeProps } from 'vue';
interface UIInstanceEvent {
hide: [];
show: [];
close: [];
}
export class UIInstance<C extends UIComponent>
extends EventEmitter<UIInstanceEvent>
implements IUIInstance<C>
{
private static counter: number = 0;
readonly key: number = UIInstance.counter++;
readonly ui: IGameUI<C>;
hidden: boolean = false;
constructor(
ui: IGameUI<C>,
public vBind: UIProps<C>,
public readonly alwaysShow: boolean = false
) {
super();
this.ui = markRaw(ui);
}
/**
* 设置这个 UI 实例的响应式数据的值
* @param data 要设置的值
* @param merge 是将传入的值与原先的值合并(true),还是将当前值覆盖掉原先的值(false),默认合并
*/
setVBind(data: Partial<Props<C>>, merge: boolean = true) {
if (merge) {
this.vBind = mergeProps(this.vBind, data) as UIProps<C>;
} else {
this.vBind = data as UIProps<C>;
}
}
hide(): void {
this.hidden = true;
}
show(): void {
this.hidden = false;
}
}
-115
View File
@@ -1,115 +0,0 @@
import { Props } from '@motajs/render';
import { DefineComponent, DefineSetupFnComponent, Ref, ShallowRef } from 'vue';
export type UIComponent = DefineSetupFnComponent<any> | DefineComponent;
export interface UIComponentProps<T extends UIComponent = UIComponent> {
controller: IUIMountable;
instance: IUIInstance<T>;
}
export type UIProps<C extends UIComponent = UIComponent> = Omit<
Props<C>,
keyof UIComponentProps<C>
>;
export interface IGameUI<C extends UIComponent = UIComponent> {
/** 这个 UI 的名称 */
readonly name: string;
/** 这个 UI 的组件 */
readonly component: C;
}
export interface IKeepController {
/**
* 安全关闭背景 UI,如果当前没有 UI 已开启,那么直接关闭,否则维持
*/
safelyUnload(): void;
/**
* 不论当前是否有 UI 已开启,都关闭背景
*/
unload(): void;
}
export interface IUIMountable {
/** 当前的 UI 栈 */
readonly stack: IUIInstance<UIComponent>[];
/** 当前的背景 UI */
readonly backIns: ShallowRef<IUIInstance<UIComponent> | null>;
/** 当前是否显示背景 UI */
readonly showBack: Ref<boolean>;
/**
* 隐藏一个 UI
* @param ins 要隐藏的 UI 实例
*/
hide(ins: IUIInstance<UIComponent>): void;
/**
* 显示一个 UI
* @param ins 要显示的 UI 实例
*/
show(ins: IUIInstance<UIComponent>): void;
/**
* 隐藏背景 UI
*/
hideBackground(): void;
/**
* 显示背景 UI
*/
showBackground(): void;
/**
* 打开一个 ui
* @param ui 要打开的 ui
* @param vBind 传递给这个 ui 的响应式数据
* @param alwaysShow 这个 ui 是否保持开启,对于需要叠加显示的 ui 非常有用
*/
open<T extends UIComponent>(
ui: IGameUI<T>,
vBind: UIProps<T>,
alwaysShow?: boolean
): IUIInstance<T>;
/**
* 关闭一个 ui
* @param ui 要关闭的 ui 实例
*/
close(ui: IUIInstance<UIComponent>): void;
/**
* 关闭所有或指定类型的所有 UI
*/
closeAll(ui?: IGameUI<UIComponent>): void;
/**
* 维持背景,直到下次所有 UI 都被关闭
*/
keep(): IKeepController;
}
export interface IUIInstance<C extends UIComponent = UIComponent> {
/** 这个 ui 实例的唯一 key,用于 vue */
readonly key: number;
/** 这个 ui 实例的 ui 信息 */
readonly ui: IGameUI<C>;
/** 传递给这个 ui 实例的响应式数据 */
readonly vBind: UIProps<C>;
/** 当前元素是否被隐藏 */
readonly hidden: boolean;
/** 是否永远保持开启 */
readonly alwaysShow: boolean;
/**
* 隐藏这个 ui
*/
hide(): void;
/**
* 显示这个 ui
*/
show(): void;
}
-19
View File
@@ -1,19 +0,0 @@
import { IGameUI, UIComponent } from './shared';
export class GameUI<C extends UIComponent> implements IGameUI<C> {
static list: Map<string, GameUI<UIComponent>> = new Map();
constructor(
public readonly name: string,
public readonly component: C
) {}
/**
* 根据 ui 名称获取 ui 实例
* @param id ui 的名称
*/
static get<T extends UIComponent>(id: string): GameUI<T> | null {
const ui = this.list.get(id) as GameUI<T>;
return ui ?? null;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { TextContent, TextContentExpose, TextContentProps } from './textbox';
import { SetupComponentOptions } from './types';
import { TextAlign } from './textboxTyper';
import { Page, PageExpose } from './page';
import { GameUI, IUIMountable } from '@/core/system';
import { GameUI, IUIMountable } from '@motajs/system-ui';
export interface ConfirmBoxProps extends DefaultProps, TextContentProps {
text: string;
+1 -1
View File
@@ -13,7 +13,7 @@ import { Scroll, ScrollExpose, ScrollProps } from './scroll';
import { transitioned } from '../use';
import { hyper } from 'mutate-animate';
import { logger } from '@motajs/common';
import { GameUI, IUIMountable } from '@/core/system';
import { GameUI, IUIMountable } from '@motajs/system-ui';
interface ProgressProps extends DefaultProps {
/** 进度条的位置 */
+1 -1
View File
@@ -1,6 +1,6 @@
import { MotaRenderer, createApp } from '@motajs/render';
import { defineComponent } from 'vue';
import { UIController } from '@/core/system';
import { UIController } from '@motajs/system-ui';
import { mainSceneUI } from './ui/main';
import { MAIN_HEIGHT, MAIN_WIDTH } from './shared';
import { TextboxStore } from './components';
+1 -1
View File
@@ -20,7 +20,7 @@ import { PopText } from '@/plugin/fx/pop';
import { LayerGroupPortal } from '@/plugin/fx/portal';
import { defineComponent, onMounted, reactive, ref } from 'vue';
import { Textbox, Tip } from '../components';
import { GameUI, UIController } from '@/core/system';
import { GameUI, UIController } from '@motajs/system-ui';
import {
MAIN_HEIGHT,
MAIN_WIDTH,
+1 -1
View File
@@ -1,5 +1,5 @@
import { ElementLocator } from '@motajs/render';
import { GameUI, UIComponentProps } from '@/core/system';
import { GameUI, UIComponentProps } from '@motajs/system-ui';
import { defineComponent } from 'vue';
import {
ChoiceItem,
+1 -1
View File
@@ -1,4 +1,4 @@
import { GameUI, UIComponentProps } from '@/core/system';
import { GameUI, UIComponentProps } from '@motajs/system-ui';
import { computed, defineComponent, ref, watch } from 'vue';
import { SetupComponentOptions, TextContent } from '../components';
import {
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@motajs/user",
"dependencies": {
"@motajs/client": "workspace:*",
"@motajs/common": "workspace:*",
"@motajs/render": "workspace:*",
"@motajs/system": "workspace:*",
"@motajs/system-ui": "workspace:*"
}
}