Files
lifeRestart/src/ui/uiManager.js
T

392 lines
11 KiB
JavaScript

import Views from './views.js'
export default class UIManager {
constructor(stage) {
UIManager.#views = Views
if (!stage) {
stage = Laya.stage
}
this.#stage = stage
stage.addChild(this.#viewLayer)
this.#viewLayer.zOrder = 1
stage.addChild(this.#dialogLayer)
this.#dialogLayer.zOrder = 2
stage.addChild(this.#popupLayer)
this.#popupLayer.zOrder = 3
this.#viewLayer.top =
this.#viewLayer.bottom =
this.#viewLayer.left =
this.#viewLayer.right =
this.#dialogLayer.top =
this.#dialogLayer.bottom =
this.#dialogLayer.left =
this.#dialogLayer.right =
this.#popupLayer.top =
this.#popupLayer.bottom =
this.#popupLayer.left =
this.#popupLayer.right =
this.#dialogMask.top =
this.#dialogMask.bottom =
this.#dialogMask.left =
this.#dialogMask.right =
0
this.#dialogMask.graphics.drawRect(0, 0, 5000, 5000, '#000000')
this.#dialogMask.alpha = 0.4
this.#dialogMask.on(Laya.Event.CLICK, this, () => {
this.#dialogStack.at(-1)?.close?.()
})
}
static #instance = {}
static #views
#stage
#loading
#currentView
#viewLayer = new Laya.Panel()
#dialogLayer = new Laya.Panel()
#popupLayer = new Laya.Panel()
#dialogMask = new Laya.Sprite()
#viewMap = new Map()
#class = new Map()
#dialogStack = []
static get inst() {
return this.getInstance()
}
static getInstance(name = 'default') {
return this.#instance[name] || (this.#instance[name] = new UIManager())
}
static get pages() {
return this.#views.pages
}
static get popups() {
return this.#views.popups
}
static theme(theme, prop) {
return this.#views.themes[theme][prop]
}
async setLoading(loading) {
const className = this.#pages[loading]
const view = await this.getView(className, null, null, loading)
view.top = view.bottom = view.left = view.right = 0
view.zOrder = 4
this.#loading = view
}
async switchView(viewName, args, actions) {
const className = this.#pages[viewName]
// get view instance
const view = await this.getView(
className,
args,
actions?.load,
viewName,
'pages',
)
view.top = view.bottom = view.left = view.right = 0
// close current view
this.clearAllDialog()
await this.#currentView?.__close?.(view)
await this.#currentView?.close?.(view)
this.#viewLayer.removeChildren()
// open new view
await view.init?.(args)
this.#currentView = view
this.#viewLayer.addChild(view)
view.__close = actions?.close
await actions?.open?.(view)
await view.show?.()
}
async getView(className, args, preload, viewName, type) {
// check if view is already loaded
let view = await this.#viewMap.get(className)
let timeout
if (this.#loading) {
timeout = setTimeout(
() => this.#stage.addChild(this.#loading),
3000,
)
}
const onProgress = this.#loading?.onProgress
if (!view) {
// load view
const ViewClass = await this.loadView(className)
const resourceList = await ViewClass.load?.(args)
const scanedResourceList = this.#loading
? this.scanResource(ViewClass.uiView)
: []
if (preload) {
preload = preload.concat(scanedResourceList)
} else {
preload = scanedResourceList
}
await this.loadRes(resourceList, preload, onProgress)
// create view
view = new ViewClass()
// add view to map
this.#viewMap.set(className, view)
} else {
// load resource
const resourceList = await view.constructor.load?.(args)
await this.loadRes(resourceList, preload, onProgress)
}
if (timeout) clearTimeout(timeout)
this.#loading?.removeSelf()
this.#config(view, viewName, type)
// return view
return view
}
async loadView(className) {
// load view
if (this.#class.has(className)) return this.#class.get(className)
const views = import.meta.glob('./themes/**/*.js')
const view = (await views[`./themes/${className}.js`]()).default
this.#class.set(className, view)
return view
}
async loadRes(resourceList, preload, onProgress) {
const cnt = (resourceList?.length || 0) + (preload?.length || 0)
if (resourceList?.length) {
const s = resourceList.length / cnt
await Laya.promises.loader.load(
resourceList,
Laya.Handler.create(null, prg => onProgress?.(prg * s)),
)
}
if (preload?.length) {
const s = 1 - preload.length / cnt
const l = preload.length / cnt
await Laya.promises.loader.load(
preload,
Laya.Handler.create(null, prg => onProgress?.(prg * l + s)),
)
}
}
#showDialogStack() {
if (this.#dialogStack.length == 0) {
this.#dialogLayer.visible = false
return
}
this.#dialogLayer.visible = true
this.#dialogStack.forEach((dialog, i) => {
this.#dialogLayer.addChild(dialog)
dialog.zOrder = i
})
this.#dialogLayer.addChild(this.#dialogMask)
const l = this.#dialogStack.length
this.#dialogMask.zOrder = l - 1
this.#dialogStack[l - 1].zOrder = l
}
async showDialog(dialogName, args, actions) {
const className = this.#pages[dialogName]
const dialog = await this.getView(
className,
args,
actions?.load,
dialogName,
'pages',
)
const index = this.#dialogStack.indexOf(dialog)
if (index != -1) {
this.#dialogStack.splice(index, 1)
}
this.#dialogStack.push(dialog)
dialog.init?.(args)
dialog.centerX = dialog.centerY = 0
this.#showDialogStack()
const open =
actions?.open ||
(async () => {
dialog.scaleX = 0
dialog.scaleY = 0
await Laya.promises.Tween.to(
dialog,
{ scaleX: 1, scaleY: 1 },
300,
Laya.Ease.backOut,
)
})
await open(dialog)
dialog.mouseThrough = true
dialog.mouseEnabled = true
dialog.close = async () => {
if (actions?.close) {
await actions.close()
} else {
await Laya.promises.Tween.to(
dialog,
{ scaleX: 0, scaleY: 0 },
300,
Laya.Ease.strongIn,
)
}
const index = this.#dialogStack.indexOf(dialog)
if (index != -1) {
this.#dialogStack.splice(index, 1)
}
this.#showDialogStack()
}
this.#dialogLayer.addChild(dialog)
}
async popup(type, args) {
const className = this.#popups[type]
const popup = await this.getView(className, args, null, type, 'popups')
this.#popupLayer.addChild(popup)
await popup.popup(args, this.#popupLayer)
popup.removeSelf()
}
clearAllDialog() {
this.#dialogStack = []
this.#showDialogStack()
}
#config(view, key, type) {
const config = this.#configs?.[type]?.[key]
if (!config) return
if (view.config?.(config)) return
const applyConfig = (target, config) => {
if (!target) return
if (typeof config == 'string') {
config = this.#configs?.class?.[config]
}
$_.deepMapSet(target, config)
}
if (config.names)
for (const name in config.names)
this.#deepGetChildsByName(view, name).forEach(child =>
applyConfig(child, config.names[name]),
)
if (config.vars)
for (const key in config.vars)
applyConfig(view[key], config.vars[key])
}
#deepGetChildsByName(parent, name) {
const list = []
if (!parent?._childs) return list
for (const child of parent._childs) {
if (child.name == name) list.push(child)
if (child._childs)
list.push(...this.#deepGetChildsByName(child, name))
}
return list
}
#cutPath(path) {
path = '' + path
let index = path.length
do {
index--
if (path[index] == '.') {
break
}
} while (index > 0)
return [path.substring(0, index), path.substring(index, path.length)]
}
#subSkin(skin, type) {
if (!skin?.replace(/\s/g, '')) return []
switch (type) {
case 'ProgressBar':
return [skin, ...this.#progressBarSkin(skin)]
case 'ScrollBar':
return [skin, ...this.#scrollBarSkin(skin)]
default:
return [skin]
}
}
#progressBarSkin(skin) {
if (!skin.replace(/\s/g, '')) return []
let p = this.#cutPath(skin)
return [`${p[0]}$bar${p[1]}`]
}
#scrollBarSkin(skin) {
if (!skin.replace(/\s/g, '')) return []
let p = this.#cutPath(skin)
return [
`${p[0]}$bar${p[1]}`,
`${p[0]}$up${p[1]}`,
`${p[0]}$down${p[1]}`,
]
}
scanResource(uiView) {
if (!uiView) return []
return [
this.#subSkin(uiView.props?.skin, uiView.type),
this.#subSkin(uiView.props?.hScrollBarSkin, 'ScrollBar'),
this.#subSkin(uiView.props?.vScrollBarSkin, 'ScrollBar'),
uiView.child?.flatMap(child => this.scanResource(child)),
]
.flat()
.filter(item => !!item)
}
get currentView() {
return this.#currentView
}
get currentDialog() {
return this.#dialogStack.at(-1)
}
get theme() {
return localStorage.getItem('theme')
}
set theme(value) {
localStorage.setItem('theme', value)
this.#stage.bgColor = this.#configs.bgColor
document.body.style.backgroundColor = this.#configs.bgColor
document
?.querySelector?.('meta[name="theme-color"]')
?.setAttribute?.('content', this.#configs.bgColor)
}
get #pages() {
return UIManager.theme(this.theme, 'pages')
}
get #popups() {
return UIManager.theme(this.theme, 'popups')
}
get #configs() {
return UIManager.theme(this.theme, 'configs')
}
get common() {
return this.#configs.common
}
gradeColor(grade) {
return this.common.grade[grade]
}
gradeFilter(grade) {
return this.common.filter[grade]
}
}