UIManager.ts 15.6 KB
import { ResUtils } from "simba-cc-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";
import { SpecialToastType } from "../../../game/ui/SpecialToast";

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;

    /**场景最上层如提示父节点 */
    let toastParentNode: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,toastParentNode_:cc.Node) {
        let promise = ToastManager.init();
        rootNode = uiRoot;
        rootView = rootNode.addComponent(CCView);
        toastParentNode = toastParentNode_;
        if (!modalRoot) {
            modalRoot = new cc.Node();
            modalRoot.name = "modalRoot";
            modalRoot.zIndex = MODAL_Z;
            rootNode.addChild(modalRoot);
            modalRootView = modalRoot.addComponent(CCView);
        }

        if (!alertRoot) {
            alertRoot = new cc.Node();
            alertRoot.name = "alertRoot";
            alertRoot.zIndex = ALERT_Z;
            rootNode.addChild(alertRoot);
        }

        if (!toastRoot) {
            toastRoot = new cc.Node();
            toastRoot.name = "toastRoot";
            toastRoot.zIndex = TOAST_Z;
            rootNode.addChild(toastRoot);
        }

        if (!loadingContainer) {
            loadingContainer = new cc.Node();
            loadingContainer.name = "loadingContainer";
            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);
        }

        if (!loadingView) {
            let ret = await ResUtils.loadRes("prefab/ui/ViewLoading", cc.Prefab);
            loadingView = cc.instantiate(ret);
            loadingContainer.addChild(loadingView);
            loadingView.active = false;
        }
        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) => {
            try {
                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);
                }
            } catch (error) {
                console.log("pushPresenter error, ", error);
            }

        });
    }

    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();
        }
    }

    /**弹出到具体某个presenter */
    export function popToPresenter(presenter: Presenter<any, any>) {
        while (presenterStack && presenterStack[presenterStack.length - 1] !== presenter) {
            popPresenter();
        }
    }

    /**
     * 弹出所有presenter
     * @param destroy 是否销毁
     */
    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) {
                    if (viewMap[i] && viewMap[i].view) {
                        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);
    }

    /**
     * 在场景最上层弱提示
     * @param parentNode toast的父节点
     * @param content 显示内容
     */
    export function showSceneToast(content: string | cc.Node, time: number = 2){
        ToastManager.addToast(toastParentNode,content, time);
    }

    /**
     * 展示特殊Toast
     * @param type 特殊Toast的类型
     */
    export function showSpecialToast(type: SpecialToastType) {
        ToastManager.addSpecialToast(toastRoot, type);
    }

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

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

}