UIManager.ts 13.7 KB
import { ResUtils } from "../../utils/ResUtils";
import { ToastManager } from "./ToastManager";
import { Presenter, createPresenter, PresenterParamType } from "../../classbase/PresenterBase";
import { View } from "../../classbase/ViewBase";
import { CCView } from "../../classbase/CCViewBase";
import { AlertManager } from "./AlertManager";

export namespace UIManager {
    const MODAL_Z = 1000;
    const ALERT_Z = 2000;
    const TOAST_Z = 3000;

    let rootNode: cc.Node;
    let rootView: View;
    let modalPresenter: Presenter<any> | undefined;
    let toastRoot: cc.Node;
    let alertRoot: cc.Node;
    let modalRoot: cc.Node;
    let modalRootView: View;
    let loadingContainer: cc.Node;
    let loadingView: cc.Node;

    type PresenterExt = Presenter<any> & { hideViews?: string[] };
    const presenterStack: PresenterExt[] = [];
    const viewMap: { [key: string]: PresenterExt } = {};
    const viewCache: { [key: string]: PresenterExt } = {};
    const loadingViews: string[] = [];

    export async function init(uiRoot: cc.Node) {
        let promise = ToastManager.init();
        rootNode = uiRoot;
        rootView = rootNode.addComponent(CCView);

        modalRoot = new cc.Node();
        modalRoot.zIndex = MODAL_Z;
        rootNode.addChild(modalRoot);
        modalRootView = modalRoot.addComponent(CCView);

        alertRoot = new cc.Node();
        alertRoot.zIndex = ALERT_Z;
        rootNode.addChild(alertRoot);

        toastRoot = new cc.Node();
        toastRoot.zIndex = TOAST_Z;
        rootNode.addChild(toastRoot);

        loadingContainer = new cc.Node();
        loadingContainer.zIndex = 99;
        loadingContainer.active = false;
        rootNode.addChild(loadingContainer);

        let widget = loadingContainer.addComponent(cc.Widget);
        widget.top = widget.bottom = widget.left = widget.right = 0;
        widget.isAlignTop = widget.isAlignBottom = widget.isAlignLeft = widget.isAlignRight = true;
        loadingContainer.addComponent(cc.BlockInputEvents);
        let ret = await ResUtils.loadRes("prefab/ui/ViewLoading", cc.Prefab);
        loadingView = cc.instantiate(ret);
        loadingView.active = false;
        loadingContainer.addChild(loadingView);
        ResUtils.releaseAsset(ret);
        await promise;
    }

    let viewInstanceId = 0;
    export function pushPresenter<T extends Presenter<any, any>>(presentCls: { new(): T, uuid: string, allowMultiInstances?: boolean }, param: PresenterParamType<T>, hideOthers = false) {
        return new Promise<T>(async (resolve, reject) => {
            if (presenterStack.length > 0) presenterStack[presenterStack.length - 1].onEnterBackground();
            let nodeName = presentCls.allowMultiInstances ? presentCls.uuid + (viewInstanceId++) : presentCls.uuid;
            if (viewMap[nodeName]) {
                if (presenterStack[presenterStack.length - 1].view.name == nodeName) {
                    console.log(presentCls.uuid + "视图已经在最顶层");
                    presenterStack[presenterStack.length - 1].onOpen(param);
                    resolve(presenterStack[presenterStack.length - 1] as T);
                    return;
                }
                // move view to top
                let index = presenterStack.findIndex(v => v.view.name === nodeName);
                let presenter = presenterStack[index];
                presenterStack.push(presenter);
                presenterStack.splice(index, 1);
                (presenter.view as CCView).node.setSiblingIndex(presenterStack.length);
                presenter.onOpen(param);
                if (presenter.hideViews) {
                    let len = presenterStack.length - 1;
                    if (hideOthers) {
                        for (let i = index; i < len; i++) {
                            if (!presenterStack[i].view.isHidden) {
                                presenterStack[i].view.hide();
                                presenter.hideViews!.push(presenterStack[i].view.name);
                            }
                        }
                    } else {
                        let hideNode: PresenterExt | undefined;
                        for (let i = index; i < len; i++) {
                            if (presenterStack[i].hideViews) {
                                hideNode = presenterStack[i];
                                break;
                            }
                        }
                        if (hideNode) {
                            for (let str of presenter.hideViews!) {
                                hideNode.hideViews!.push(str);
                            }
                        } else {
                            for (let str of presenter.hideViews!) {
                                viewMap[str].view.show();
                            }
                        }
                        delete presenter.hideViews;
                    }
                } else if (hideOthers) {
                    let len = presenterStack.length - 1;
                    presenter.hideViews = [];
                    for (let i = 0; i < len; i++) {
                        if (!presenterStack[i].view.isHidden) {
                            presenterStack[i].view.hide();
                            presenter.hideViews.push(presenterStack[i].view.name);
                        }
                    }
                }
                resolve(presenter as T);
            } else {
                if (!loadingContainer.active) {
                    loadingViews.push(presentCls.uuid);
                    loadingContainer.active = true;
                    loadingView.active = false;
                    (<any>loadingView).timer = setTimeout(() => {
                        (<any>loadingView).timer = undefined;
                        loadingView.active = true;
                    }, 1000);
                } else {
                    let index = loadingViews.findIndex(v => v === presentCls.uuid);
                    if (index >= 0) {
                        reject("View is already loading.");
                        return;
                    }
                }

                let error: Error | undefined;
                let presenter: PresenterExt = viewCache[presentCls.uuid];
                if (!presenter) {
                    // create a view
                    try {
                        presenter = await createPresenter(presentCls);
                        presenter.view.name = nodeName;
                        viewCache[presentCls.uuid] = presenter;
                    } catch (e) {
                        error = e;
                    }
                }
                if (presenter) {
                    (presenter as T).open(rootView, param);
                    (presenter.view as CCView).onClose.once((destroy) => {
                        onPresenterInStackClose(presenter, destroy);
                    });
                    if (hideOthers) {
                        presenter.hideViews = [];
                        let len = presenterStack.length;
                        for (let i = 0; i < len; i++) {
                            if (!presenterStack[i].view.isHidden) {
                                presenterStack[i].view.hide();
                                presenter.hideViews.push(presenterStack[i].view.name);
                            }
                        }
                    }
                    presenterStack.push(presenter);
                    viewMap[nodeName] = presenter;
                }
                let index = loadingViews.findIndex(v => v === presentCls.uuid);
                loadingViews.splice(index, 1);
                if (loadingViews.length == 0) {
                    if ((<any>loadingView).timer) {
                        clearTimeout((<any>loadingView).timer);
                        (<any>loadingView).timer = undefined;
                    }
                    loadingContainer.active = false;
                }
                if (!presenter) reject(error ? error : new Error());
                else resolve(presenter as T);
            }
        });
    }

    export function getTopPresenter(): Presenter {
        return presenterStack[presenterStack.length - 1];
    }

    export function popPresenter(destroy: boolean = false) {
        let presenter = presenterStack.pop();
        if (presenter) {
            delete viewMap[presenter.view.name];
            if (destroy) delete viewCache[presenter.view.name];
            presenter.view.close(destroy);
            if (presenter.hideViews) {
                for (let v of presenter.hideViews) {
                    viewMap[v].view.show();
                }
            }
            if (presenterStack.length > 0) presenterStack[presenterStack.length - 1].onEnterForeground();
        }
    }

    export function popToPresenter(presenter: Presenter<any, any>) {
        while (presenterStack && presenterStack[presenterStack.length - 1] !== presenter) {
            popPresenter();
        }
    }

    export function popAll(destroy?: boolean) {
        while (presenterStack.length) {
            let presenter = presenterStack.pop()!;
            delete viewMap[presenter.view.name];
            presenter.view.close(destroy);
        }
    }

    function onPresenterInStackClose(presenter: Presenter<any>, destroy?: boolean) {
        let view = presenter.view.name;
        if (!viewMap[view]) return;
        let presenterExt = presenter as PresenterExt;
        let index = presenterStack.findIndex(v => v === presenter);
        presenterStack.splice(index, 1);
        if (index === presenterStack.length) {
            presenterStack[presenterStack.length - 1].onEnterForeground();
        }
        delete viewMap[view];
        if (destroy) {
            delete viewCache[view];
        }
        if (presenterExt.hideViews && presenterExt.hideViews.length) {
            let len = presenterStack.length;
            let node1: PresenterExt | undefined;
            for (let i = index; i < len; i++) {
                if (presenterStack[i].hideViews) {
                    node1 = presenterStack[i];
                    break;
                }
            }
            if (node1) {
                presenterExt.hideViews.push(...presenterExt.hideViews);
            } else {
                for (let i of presenterExt.hideViews) {
                    viewMap[i].view.show();
                }
            }
        }
    }

    export function closePresenterInStack(presenter: Presenter<any>, destroy?: boolean) {
        let view = ((presenter.constructor as any).uuid as string);
        if (!viewMap[view]) return;
        if (presenterStack[presenterStack.length - 1] === presenter) {
            popPresenter();
        } else {
            presenter.view.close(destroy);
        }
    }

    export function showModalPresenter<T extends Presenter<any, any>>(presentCls: { new(): T, uuid: string, allowMultiInstances?: boolean }, param: PresenterParamType<T>) {
        return new Promise<T>(async (resolve, reject) => {
            if (modalPresenter instanceof presentCls) {
                resolve(modalPresenter);
            } else {
                let nodeName = presentCls.uuid;
                if (!loadingContainer.active) {
                    loadingViews.push(presentCls.uuid);
                    loadingContainer.active = true;
                    loadingView.active = false;
                    (<any>loadingView).timer = setTimeout(() => {
                        (<any>loadingView).timer = undefined;
                        loadingView.active = true;
                    }, 1000);
                } else {
                    let index = loadingViews.findIndex(v => v === presentCls.uuid);
                    if (index >= 0) {
                        reject("View is already loading.");
                        return;
                    }
                }

                let error: Error | undefined;
                let presenter: PresenterExt = viewCache[presentCls.uuid];
                if (!presenter) {
                    // create a view
                    try {
                        presenter = await createPresenter(presentCls);
                        presenter.view.name = nodeName;
                        viewCache[presentCls.uuid] = presenter;
                    } catch (e) {
                        error = e;
                    }
                }
                if (presenter) {
                    (presenter as T).open(modalRootView, param);
                    modalPresenter = presenter;
                    (modalPresenter.view as CCView).onClose.once(() => {
                        modalPresenter = undefined;
                    })
                }
                let index = loadingViews.findIndex(v => v === presentCls.uuid);
                loadingViews.splice(index, 1);
                if (loadingViews.length == 0) {
                    if ((<any>loadingView).timer) {
                        clearTimeout((<any>loadingView).timer);
                        (<any>loadingView).timer = undefined;
                    }
                    loadingContainer.active = false;
                }
                if (!presenter) reject(error ? error : new Error());
                else resolve(presenter as T);
            }
        });
    }

    export function dismissModalPresenter(destroy?: boolean) {
        if (modalPresenter) {
            modalPresenter.view.close(destroy);
            modalPresenter = undefined;
        }
    }

    export function getModalPresenter() {
        return modalPresenter;
    }

    export function showToast(content: string | cc.Node) {
        ToastManager.addToast(toastRoot, content);
    }

    export function getPresenter(view: string): Presenter | undefined {
        return viewMap[view];
    }

    export function showAlert(nodeOrPrefab: cc.Node | string, animate = true) {
        return AlertManager.showAlert(alertRoot, nodeOrPrefab, animate);
    }

}