import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager"; import { UITransitionHelper } from "../utils/UITransitionHelper"; import GameRootUI from "../../Main/Common/GameRootUI"; import { Node, sys } from "cc"; import li_EventManager from "../../Main/Common/li_EventManager"; import { InnerMsgCode } from "../../Main/Config/InnerMsgCode"; import { ThemePanel } from "../ui/panels/ThemePanel"; import { GirlListPanel } from "../ui/panels/GirlListPanel"; import li_BaseView from "../../Main/Common/li_BaseView"; /** * 页面过渡动画类型枚举 */ export enum PageTransitionType { NONE = "none", SLIDE_LEFT = "slide_left", SLIDE_RIGHT = "slide_right", SLIDE_UP = "slide_up", SLIDE_DOWN = "slide_down", FADE = "fade", SCALE = "scale", } /** * NavigationPanel面板类型枚举 */ export enum PanelType { THEME = "ThemePanel", GIRL_DETAIL = "GirlDetailPanel", PAST_GIRL_LIST = "PastGirlListPanel", PERSONAL = "PersonalPanel", } /** * 页面过渡动画配置接口 */ export interface PageTransitionConfig { incomingTransition: PageTransitionType; outgoingTransition: PageTransitionType; duration?: number; simultaneous?: boolean; } /** * 子面板动画配置接口 */ export interface SubPanelAnimationConfig { openAnimation: PageTransitionType; closeAnimation: PageTransitionType; duration?: number; } /** * 导航管理器 * * 负责管理游戏中的页面导航和路由,将导航逻辑从业务逻辑中分离出来 * 统一管理NavigationPanel和其他面板的切换 * * @author AI Chat System * @version 1.0.0 */ export class NavigationManager { private static _instance: NavigationManager; // NavigationPanel相关属性 private navigationPanel: any = null; // NavigationPanel实例引用 private currentActivePanelType: PanelType = PanelType.PAST_GIRL_LIST; private currentActivePanel: Node = null; // 初始化为null private panelCache: Map = new Map(); // 面板缓存 // 选中的角色相关信息 private selectedGirlId: number = 10002; // 当前选中的角色ID private selectedCategoryId: number = -1; // 当前选中的主题ID private static LastSelectGirlID = "LastSelectGirlID"; /** * 获取NavigationManager的单例实例 * * @returns {NavigationManager} 导航管理器实例 * @static */ public static get Instance(): NavigationManager { if (!this._instance) { this._instance = new NavigationManager(); //初始化默认选的girl let cacheGirlId = sys.localStorage.getItem(this.LastSelectGirlID); if (!cacheGirlId) { cacheGirlId = 10002; sys.localStorage.setItem(this.LastSelectGirlID, cacheGirlId); } this._instance.selectedGirlId = Number(cacheGirlId); let cacheCategoryId = sys.localStorage.getItem("LastSelectCategoryId"); if (!cacheCategoryId) { cacheCategoryId = 0; sys.localStorage.setItem("LastSelectCategoryId", cacheCategoryId); } this._instance.selectedCategoryId = cacheCategoryId; } return this._instance; } private constructor() { // 初始化事件监听 this.initEventListeners(); } /** * 初始化事件监听 */ private initEventListeners(): void { // } /** * 注册NavigationPanel实例 * @param panel NavigationPanel实例 */ public registerNavigationPanel(panel: any): void { this.navigationPanel = panel; console.log("[NavigationManager] NavigationPanel registered"); } /** * 统一的面板切换方法 * @param panelType 目标面板类型 */ public switchToPanel(panelType: PanelType, force: boolean = false): Node { if (!panelType) { console.error("[NavigationManager] switchToPanel: panelType is invalid"); return null; } if (this.currentActivePanelType === panelType && !force) { console.log(`[NavigationManager] Already on ${panelType}, skipping switch`); return this.currentActivePanel; } if (!this.navigationPanel) { console.warn( "[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call" ); //this.fallbackSwitchPanel(panelType); return null; } // 切换面板前先关闭所有弹窗 this.closeAllPopups(); // 通知NavigationPanel显示加载状态 this.navigationPanel.showLoading?.(); // 计算动画方向 const currentIndex = this.getPanelIndex(this.currentActivePanelType); const targetIndex = this.getPanelIndex(panelType); const isMovingRight = currentIndex < targetIndex; const hideDirection = isMovingRight ? "left" : "right"; const showDirection = isMovingRight ? "right" : "left"; // 隐藏当前面板(包括子页面) if (!this.currentActivePanel || !this.currentActivePanel.isValid) { return; } this.hidePanelWithAnimation(this.currentActivePanel, hideDirection); // 加载并显示目标面板 this.loadOrGetPanel(panelType, (panel: Node) => { // 清理当前面板的subPanel(在hidePanelWithAnimation中已经清理,这里不需要重复) this.currentActivePanelType = panelType; // 更新NavigationPanel按钮状态 this.navigationPanel.updateButtonStates?.(panelType); // 显示面板 this.showPanelWithAnimation(panel, showDirection, () => { this.currentActivePanel = panel; this.navigationPanel.hideLoading?.(); }); }); } private subPanelDic: Map = new Map(); private subPanelMapping: Map = new Map(); private subPanelAnimationConfigs: Map = new Map(); // 弹窗管理相关属性 private popupPanels: Set = new Set(); // 管理所有打开的弹窗 // 动画锁,防止动画中重复操作 private isAnimating: boolean = false; /** * 统一的subPanel清理方法 * @param basePanel 主面板节点 * @param destroyPanels 是否销毁面板,默认为true */ private clearSubPanels(basePanel: Node, destroyPanels: boolean = true): void { if (!basePanel || !basePanel.isValid) { return; } const subPanels = this.subPanelDic.get(basePanel); if (!subPanels || subPanels.length === 0) { return; } // 清理所有子面板 for (let i = subPanels.length - 1; i >= 0; i--) { const subPanel = subPanels[i]; if (subPanel && subPanel.isValid) { // 清理subPanelMapping this.subPanelMapping.delete(subPanel); // 清理动画配置 this.subPanelAnimationConfigs.delete(subPanel); if (destroyPanels) { const baseView = subPanel.getComponent(li_BaseView); if (baseView) { baseView.close(); } else { subPanel.destroy(); } } } } // 清理subPanelDic this.subPanelDic.delete(basePanel); } public openSubPanel( panelName: string, basePanel: Node, data: any = null, animationConfig?: SubPanelAnimationConfig ) { if (!panelName) { console.error("[NavigationManager] openSubPanel: panelName is empty"); return; } if (!basePanel || !basePanel.isValid) { console.error("[NavigationManager] openSubPanel: basePanel is invalid"); return; } // 使用默认动画配置 const defaultConfig: SubPanelAnimationConfig = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.3 }; const config = animationConfig || defaultConfig; const callback = (n: Node) => { if (!n || !n.isValid) { console.error("[NavigationManager] openSubPanel: created panel is invalid"); return; } if (!this.subPanelDic.has(basePanel)) { this.subPanelDic.set(basePanel, []); } const subPanels = this.subPanelDic.get(basePanel); subPanels.push(n); const basePanelType = this.getPanelType(basePanel); if (basePanelType) { this.subPanelMapping.set(n, basePanelType); } else { console.warn(`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`); } // 存储动画配置 this.subPanelAnimationConfigs.set(n, config); // 执行打开动画 this.playSubPanelOpenAnimation(n, config); }; ViewManager.I.openBundlesView(panelName, data, callback); } /** * 支持自定义动画方向的面板切换方法 * @param panelType 目标面板类型 * @param transitionConfig 过渡动画配置 * @param force 是否强制切换 */ public switchToPanelWithTransition( panelType: PanelType, transitionConfig: PageTransitionConfig, force: boolean = false ): Node { if (!panelType) { console.error("[NavigationManager] switchToPanelWithTransition: panelType is invalid"); return null; } if (this.currentActivePanelType === panelType && !force) { console.log(`[NavigationManager] Already on ${panelType}, skipping switch`); return this.currentActivePanel; } if (!this.navigationPanel) { console.warn( "[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call" ); return null; } // 切换面板前先关闭所有弹窗 this.closeAllPopups(); // 通知NavigationPanel显示加载状态 this.navigationPanel.showLoading?.(); // 获取动画方向 const hideDirection = this.getDirectionFromTransitionType(transitionConfig.outgoingTransition); const showDirection = this.getDirectionFromTransitionType(transitionConfig.incomingTransition); // 隐藏当前面板(包括子页面) if (this.currentActivePanel && this.currentActivePanel.isValid) { this.hidePanelWithAnimation(this.currentActivePanel, hideDirection); } // 加载并显示目标面板 this.loadOrGetPanel(panelType, (panel: Node) => { this.currentActivePanelType = panelType; // 更新NavigationPanel按钮状态 this.navigationPanel.updateButtonStates?.(panelType); // 显示面板 this.showPanelWithAnimation(panel, showDirection, () => { this.currentActivePanel = panel; this.navigationPanel.hideLoading?.(); }); }); return null; } /** * 将过渡类型转换为方向字符串 */ private getDirectionFromTransitionType(transitionType: PageTransitionType): "left" | "right" | "up" | "down" { switch (transitionType) { case PageTransitionType.SLIDE_LEFT: return "left"; case PageTransitionType.SLIDE_RIGHT: return "right"; case PageTransitionType.SLIDE_UP: return "up"; case PageTransitionType.SLIDE_DOWN: return "down"; default: return "left"; // 默认向左 } } /** * 执行子面板打开动画 */ private playSubPanelOpenAnimation(panel: Node, config: SubPanelAnimationConfig): void { if (!panel || !panel.isValid) { return; } const duration = config.duration || 0.3; switch (config.openAnimation) { case PageTransitionType.SCALE: UITransitionHelper.scaleIn(panel, duration); break; case PageTransitionType.FADE: UITransitionHelper.fadeIn(panel, duration); break; case PageTransitionType.SLIDE_UP: UITransitionHelper.slideInFromBottom(panel, duration); break; case PageTransitionType.SLIDE_DOWN: UITransitionHelper.slideInFromTop(panel, duration); break; case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideInFromRight(panel, duration); break; case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideInFromLeft(panel, duration); break; default: // 无动画,直接显示 break; } } /** * 执行子面板关闭动画 */ private playSubPanelCloseAnimation( panel: Node, config: SubPanelAnimationConfig, callback?: Function ): void { if (!panel || !panel.isValid) { callback && callback(); return; } const duration = config.duration || 0.3; switch (config.closeAnimation) { case PageTransitionType.SCALE: UITransitionHelper.scaleOut(panel, duration, callback); break; case PageTransitionType.FADE: UITransitionHelper.fadeOut(panel, duration, callback); break; case PageTransitionType.SLIDE_UP: UITransitionHelper.slideOutToTop(panel, duration, callback); break; case PageTransitionType.SLIDE_DOWN: UITransitionHelper.slideOutToBottom(panel, duration, callback); break; case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideOutToLeft(panel, duration, callback); break; case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideOutToRight(panel, duration, callback); break; default: // 无动画,直接执行回调 callback && callback(); break; } } /** * 打开弹窗面板 * @param panelName 弹窗面板名称 * @param data 传递的数据 * @param callback 可选的回调函数 */ public openPopupPanel(panelName: string, data: any = null, callback?: Function): void { if (!panelName) { console.error("[NavigationManager] openPopupPanel: panelName is empty"); return; } const popupCallback = (panel: Node) => { if (!panel || !panel.isValid) { console.error("[NavigationManager] openPopupPanel: created panel is invalid"); return; } // 将弹窗添加到管理集合中 this.popupPanels.add(panel); console.log(`[NavigationManager] Popup panel opened: ${panelName}, total popups: ${this.popupPanels.size}`); if (callback) { callback(panel); } }; ViewManager.I.openBundlesPopupView(panelName, data, popupCallback); } /** * 关闭弹窗面板 * @param panel 要关闭的弹窗节点 */ public closePopupPanel(panel: Node): void { if (!panel || !panel.isValid) { console.warn("[NavigationManager] closePopupPanel: panel is invalid"); return; } // 从弹窗管理集合中移除 if (this.popupPanels.has(panel)) { this.popupPanels.delete(panel); console.log(`[NavigationManager] Popup panel closed: ${panel.name}, total popups: ${this.popupPanels.size}`); } // 关闭面板 try { const baseView = panel.getComponent(li_BaseView); if (baseView) { baseView.close(); } else { panel.destroy(); } } catch (error) { console.error("[NavigationManager] closePopupPanel error:", error); } } /** * 关闭所有弹窗 */ private closeAllPopups(): void { if (this.popupPanels.size === 0) { return; } console.log(`[NavigationManager] Closing all popups, count: ${this.popupPanels.size}`); // 复制Set以避免在迭代过程中修改 const popupPanelsToClose = Array.from(this.popupPanels); for (const panel of popupPanelsToClose) { if (panel && panel.isValid) { this.closePopupPanel(panel); } } // 确保清空集合 this.popupPanels.clear(); } public closeSubPanel(panel: Node | li_BaseView, animationConfig?: SubPanelAnimationConfig) { if (!panel) { console.warn("[NavigationManager] closeSubPanel: panel is null or undefined"); return; } let panelNode: Node; if (panel instanceof Node) { panelNode = panel; } else { panelNode = panel.node; } if (!panelNode || !panelNode.isValid) { console.warn("[NavigationManager] closeSubPanel: panelNode is invalid"); return; } // 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置) let config = animationConfig; if (!config && this.subPanelAnimationConfigs.has(panelNode)) { config = this.subPanelAnimationConfigs.get(panelNode); } if (!config) { config = { openAnimation: PageTransitionType.SCALE, closeAnimation: PageTransitionType.SCALE, duration: 0.3 }; } // 执行关闭动画 this.playSubPanelCloseAnimation(panelNode, config, () => { // 从管理映射中移除 this.removeSubPanelFromMappings(panelNode); // 销毁或关闭panel try { if (panel instanceof Node) { panel.destroy(); } else { panel.close(); } } catch (error) { console.error("[NavigationManager] closeSubPanel error:", error); } }); } /** * 从管理映射中移除子面板 */ private removeSubPanelFromMappings(panelNode: Node): void { // 从subPanelMapping和subPanelDic中移除 if (this.subPanelMapping.has(panelNode)) { const baseType = this.subPanelMapping.get(panelNode); const baseNode = this.panelCache.get(baseType); if (baseNode && this.subPanelDic.has(baseNode)) { const subPanels = this.subPanelDic.get(baseNode); const index = subPanels.indexOf(panelNode); if (index !== -1) { subPanels.splice(index, 1); // 如果subPanels为空,删除整个条目 if (subPanels.length === 0) { this.subPanelDic.delete(baseNode); } } else { console.warn(`[NavigationManager] ${panelNode.name} 不在子页面列表中`); } } this.subPanelMapping.delete(panelNode); } // 移除动画配置 if (this.subPanelAnimationConfigs.has(panelNode)) { this.subPanelAnimationConfigs.delete(panelNode); } } /** * 获取面板索引(用于动画方向计算) */ private getPanelIndex(panelType: PanelType): number { switch (panelType) { case PanelType.THEME: return 0; case PanelType.GIRL_DETAIL: return 1; case PanelType.PAST_GIRL_LIST: return 2; case PanelType.PERSONAL: return 3; default: return 0; } } private getPanelType(node: Node): PanelType { if (!node || !node.isValid) { console.warn("[NavigationManager] getPanelType: node is invalid"); return null; } switch (node.name) { case "ThemePanel": return PanelType.THEME; case "GirlDetailPanel": return PanelType.GIRL_DETAIL; case "PastGirlListPanel": return PanelType.PAST_GIRL_LIST; case "PersonalPanel": return PanelType.PERSONAL; default: console.warn(`[NavigationManager] Unknown panel type for node: ${node.name}`); return null; } } /** * 获取面板名称 */ private getPanelName(panelType: PanelType): string { return panelType as string; } /** * 获取面板数据 */ private getPanelData(panelType: PanelType): any { switch (panelType) { case PanelType.GIRL_DETAIL: return this.selectedGirlId; // 使用当前选中的角色ID default: return null; } } /** * 加载或获取面板 */ private loadOrGetPanel( panelType: PanelType, callback: (panel: Node) => void ) { // 检查缓存 if (this.panelCache.has(panelType)) { const cachedPanel = this.panelCache.get(panelType); if (cachedPanel && cachedPanel.isValid) { // 确保缓存panel的subPanel状态正确,清理可能残留的subPanel引用 this.clearSubPanels(cachedPanel, false); // 不销毁panel,只清理引用 // 特殊处理:主题面板刷新(避免重复调用Show) if (panelType === PanelType.THEME) { const themePanel = cachedPanel.getComponent(ThemePanel); if (themePanel) { // 不在这里调用Show(),因为ThemePanel已经有加载状态保护 // 只在必要时调用onHide()确保子页面状态正确 if (typeof themePanel.onHide === "function") { console.log("[NavigationManager] 确保主题面板子页面状态正确"); themePanel.onHide(); } } } callback(cachedPanel); return; } else { this.panelCache.delete(panelType); } } // 加载新面板 const panelName = this.getPanelName(panelType); const openData = this.getPanelData(panelType); ViewManager.I.openBundlesView(panelName, openData, (panel: Node) => { if (panel && panel.isValid) { this.panelCache.set(panelType, panel); callback(panel); } }); } /** * 隐藏面板动画 */ private hidePanelWithAnimation( panel: Node, direction: "left" | "right" | "up" | "down", callback?: Function ) { if (!panel || !panel.isValid || !panel.active) { callback && callback(); return; } const animationCallback = () => { panel.active = false; // 使用统一的subPanel清理方法 this.clearSubPanels(panel, true); callback && callback(); }; switch (direction) { case "left": UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback); break; case "right": UITransitionHelper.slideOutToRight(panel, 0.3, animationCallback); break; case "up": UITransitionHelper.slideOutToTop(panel, 0.3, animationCallback); break; case "down": UITransitionHelper.slideOutToBottom(panel, 0.3, animationCallback); break; default: // 默认左侧滑出 UITransitionHelper.slideOutToLeft(panel, 0.3, animationCallback); break; } } /** * 显示面板动画 */ private showPanelWithAnimation( panel: Node, direction: "left" | "right" | "up" | "down", callback?: Function ) { if (!panel || !panel.isValid) { callback && callback(); return; } panel.active = true; switch (direction) { case "left": UITransitionHelper.slideInFromLeft(panel, 0.3, callback); break; case "right": UITransitionHelper.slideInFromRight(panel, 0.3, callback); break; case "up": UITransitionHelper.slideInFromTop(panel, 0.3, callback); break; case "down": UITransitionHelper.slideInFromBottom(panel, 0.3, callback); break; default: // 默认从右侧滑入 UITransitionHelper.slideInFromRight(panel, 0.3, callback); break; } } /** * 获取当前激活的面板类型 */ public getCurrentActivePanel(): PanelType { return this.currentActivePanelType; } /** * 获取当前激活的面板节点 */ public getCurrentActivePanelNode(): Node { return this.currentActivePanel; } /** * 设置选中的角色ID * @param girlId 角色ID */ public setSelectedGirlId(girlId: number): void { this.selectedGirlId = girlId; sys.localStorage.setItem(NavigationManager.LastSelectGirlID, girlId); console.log("[NavigationManager] Selected girl ID set to:", girlId); } /** * 获取当前选中的角色ID * @returns 当前选中的角色ID */ public getSelectedGirlId(): number { return this.selectedGirlId; } /** * 设置选中的主题ID * @param themeId 主题ID */ public setSelectedCategoryId(themeId: number): void { this.selectedCategoryId = themeId; console.log("[NavigationManager] Selected theme ID set to:", themeId); } /** * 获取当前选中的主题ID * @returns 当前选中的主题ID */ public getSelectedCategoryId(): number { return this.selectedCategoryId; } }