import { ViewManager } from "db://assets/Scripts/Main/Manager/ViewManager"; import { UITransitionHelper } from "../utils/UITransitionHelper"; import GameRootUI from "../../Main/Common/GameRootUI"; import { Node } from "cc"; import li_EventManager from "../../Main/Common/li_EventManager"; import { InnerMsgCode } from "../../Main/Config/InnerMsgCode"; import { ThemePanel } from "../ui/panels/ThemePanel"; /** * 页面过渡动画类型枚举 */ export enum PageTransitionType { NONE = "none", SLIDE_LEFT = "slide_left", SLIDE_RIGHT = "slide_right", } /** * NavigationPanel面板类型枚举 */ export enum PanelType { THEME = "ThemePanel", GIRL_DETAIL = "GirlDetailPanel", CHAT = "ChatPanel", SETTING = "SettingPanel", } /** * 页面过渡动画配置接口 */ export interface PageTransitionConfig { incomingTransition: PageTransitionType; outgoingTransition: PageTransitionType; duration?: number; simultaneous?: boolean; } /** * 导航管理器 * * 负责管理游戏中的页面导航和路由,将导航逻辑从业务逻辑中分离出来 * 统一管理NavigationPanel和其他面板的切换 * * @author AI Chat System * @version 1.0.0 */ export class NavigationManager { private static _instance: NavigationManager; // NavigationPanel相关属性 private navigationPanel: any = null; // NavigationPanel实例引用 private currentActivePanel: PanelType = PanelType.THEME; private panelCache: Map = new Map(); // 面板缓存 private currentVisiblePanel: Node = null; // 选中的角色相关信息 private selectedGirlId: number = 1; // 当前选中的角色ID private selectedThemeId: number = 1; // 当前选中的主题ID /** * 获取NavigationManager的单例实例 * * @returns {NavigationManager} 导航管理器实例 * @static */ public static get Instance(): NavigationManager { if (!this._instance) { this._instance = new NavigationManager(); } return this._instance; } private constructor() { // 初始化事件监听 this.initEventListeners(); } /** * 初始化事件监听 */ private initEventListeners(): void { // 监听导航面板切换事件 li_EventManager.I.addInnerEL( InnerMsgCode.Navigation_PanelSwitch, this.onNavigationEvent, this ); } /** * 处理导航事件 */ private onNavigationEvent(panelType: PanelType): void { console.log( "[NavigationManager] Received navigation event for:", panelType ); this.switchToPanel(panelType); } /** * 注册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): void { if (this.currentActivePanel === panelType && !force) { return; } if (!this.navigationPanel) { console.warn( "[NavigationManager] NavigationPanel not registered, fallback to direct ViewManager call" ); this.fallbackSwitchPanel(panelType); return; } // 通知NavigationPanel显示加载状态 this.navigationPanel.showLoading?.(); // 计算动画方向 const currentIndex = this.getPanelIndex(this.currentActivePanel); const targetIndex = this.getPanelIndex(panelType); const isMovingRight = currentIndex < targetIndex; const hideDirection = isMovingRight ? "left" : "right"; const showDirection = isMovingRight ? "right" : "left"; // 隐藏当前面板(包括子页面) this.hideCurrentPanelWithChildren(hideDirection); // 加载并显示目标面板 this.loadOrGetPanel(panelType, (panel: Node) => { this.currentActivePanel = panelType; this.currentVisiblePanel = panel; // 更新NavigationPanel按钮状态 this.navigationPanel.updateButtonStates?.(panelType); // 显示面板 this.showPanelWithAnimation(panel, showDirection, () => { this.navigationPanel.hideLoading?.(); }); }); } /** * 获取面板索引(用于动画方向计算) */ private getPanelIndex(panelType: PanelType): number { switch (panelType) { case PanelType.THEME: return 0; case PanelType.GIRL_DETAIL: return 1; case PanelType.CHAT: return 2; case PanelType.SETTING: return 3; default: return 0; } } /** * 获取面板名称 */ private getPanelName(panelType: PanelType): string { return panelType as string; } /** * 获取面板数据 */ private getPanelData(panelType: PanelType): any { switch (panelType) { case PanelType.GIRL_DETAIL: return this.selectedGirlId; // 使用当前选中的角色ID case PanelType.CHAT: return { girlId: 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) { // 特殊处理:主题面板刷新(避免重复调用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 hideCurrentPanelWithChildren(direction: "left" | "right") { if (!this.currentVisiblePanel) { return; } // 如果当前是ThemePanel,需要同时处理可能的子页面(GirlListPanel) if (this.currentActivePanel === PanelType.THEME) { // 查找并隐藏GirlListPanel子页面 this.hideThemePanelChildren(direction); // 隐藏ThemePanel主面板 this.hidePanelWithAnimation(this.currentVisiblePanel, direction); } else { // 其他面板直接隐藏 this.hidePanelWithAnimation(this.currentVisiblePanel, direction); } } /** * 隐藏ThemePanel的子页面 */ private hideThemePanelChildren(direction: "left" | "right") { const themePanelNode = this.findThemePanelNode(); if (themePanelNode) { const themePanelComponent = themePanelNode.getComponent("ThemePanel"); if (themePanelComponent) { // 获取子页面节点 const childPanel = this.getThemePanelChildNode(themePanelComponent); if (childPanel && childPanel.active) { console.log( "[NavigationManager] Hiding ThemePanel child with animation" ); this.hidePanelWithAnimation(childPanel, direction); } } } } /** * 获取ThemePanel的子页面节点 */ private getThemePanelChildNode(themePanelComponent: any): Node | null { try { // ThemePanel存储子页面在currentChildPanel属性中 if ( themePanelComponent.currentChildPanel && themePanelComponent.currentChildPanel.node ) { return themePanelComponent.currentChildPanel.node; } // 如果没有直接的引用,尝试通过ViewManager查找GirlListPanel const viewList = (ViewManager.I as any).m_viewList; if (viewList) { for (let i = 0; i < viewList.length; i++) { const view = viewList[i]; if ( view && view.isValid && view.name === "GirlListPanel" && view.active ) { return view; } } } return null; } catch (error) { console.error( "[NavigationManager] Error getting child panel node:", error ); return null; } } /** * 隐藏面板动画 */ private hidePanelWithAnimation( panel: Node, direction: "left" | "right", callback?: Function ) { if (!panel || !panel.isValid || !panel.active) { callback && callback(); return; } if (direction === "left") { UITransitionHelper.slideOutToLeft(panel, 0.3, () => { panel.active = false; callback && callback(); }); } else { UITransitionHelper.slideOutToRight(panel, 0.3, () => { panel.active = false; callback && callback(); }); } } /** * 显示面板动画 */ private showPanelWithAnimation( panel: Node, direction: "left" | "right", callback?: Function ) { if (!panel || !panel.isValid) { callback && callback(); return; } panel.active = true; if (direction === "right") { UITransitionHelper.slideInFromRight(panel, 0.3, () => { callback && callback(); }); } else { UITransitionHelper.slideInFromLeft(panel, 0.3, () => { callback && callback(); }); } } /** * 后备切换方法(当NavigationPanel未注册时) */ private fallbackSwitchPanel(panelType: PanelType): void { const panelName = this.getPanelName(panelType); const openData = this.getPanelData(panelType); ViewManager.I.openBundlesView(panelName, openData); } /** * 获取当前激活的面板类型 */ public getCurrentActivePanel(): PanelType { return this.currentActivePanel; } /** * 设置选中的角色ID * @param girlId 角色ID */ public setSelectedGirlId(girlId: number): void { this.selectedGirlId = girlId; console.log("[NavigationManager] Selected girl ID set to:", girlId); } /** * 获取当前选中的角色ID * @returns 当前选中的角色ID */ public getSelectedGirlId(): number { return this.selectedGirlId; } /** * 设置选中的主题ID * @param themeId 主题ID */ public setSelectedThemeId(themeId: number): void { this.selectedThemeId = themeId; console.log("[NavigationManager] Selected theme ID set to:", themeId); } /** * 获取当前选中的主题ID * @returns 当前选中的主题ID */ public getSelectedThemeId(): number { return this.selectedThemeId; } /** * 通用页面导航方法,支持过渡动画 * * @param {string} targetPanel - 目标面板名称 * @param {any} navigationData - 导航数据 * @param {PageTransitionConfig} transitionConfig - 过渡动画配置 * @param {any} currentPanel - 当前面板实例(可选) * @param {Function} onComplete - 完成回调(可选) * * @example * ```typescript * NavigationManager.Instance.navigateWithTransition( * "ChatPanel", * { roleId: 10001 }, * { incomingTransition: PageTransitionType.SLIDE_RIGHT, outgoingTransition: PageTransitionType.SLIDE_LEFT }, * currentPanel * ); * ``` */ public navigateWithTransition( targetPanel: string, navigationData: any, transitionConfig: PageTransitionConfig, currentPanel?: any, onComplete?: Function ): void { if (!targetPanel) { console.warn("NavigationManager: Target panel name is required"); return; } console.log( `Navigating to ${targetPanel} with transition:`, transitionConfig ); //const correntPos = currentPanel.node.position; const duration = transitionConfig.duration || 0.1; const useTransition = transitionConfig.incomingTransition !== PageTransitionType.NONE || transitionConfig.outgoingTransition !== PageTransitionType.NONE; if (!useTransition) { // 在没有过渡动画的情况下,也需要处理子页面逻辑 if (targetPanel && targetPanel !== "GirlListPanel") { this.handleThemePanelChildPanelsOnNavigation(); } ViewManager.I.openBundlesView(targetPanel, navigationData, onComplete); if ( currentPanel && currentPanel.onClose && typeof currentPanel.onClose === "function" ) { currentPanel.onClose(); //currentPanel.node.position = correntPos; } return; } ViewManager.I.openBundlesView(targetPanel, navigationData, (targetNode) => { this._executeTransition( targetNode, currentPanel, transitionConfig, duration, onComplete ); }); } /** * 执行页面过渡动画的私有方法 * * @private * @param {any} targetNode - 目标节点 * @param {any} currentPanel - 当前面板 * @param {PageTransitionConfig} config - 动画配置 * @param {number} duration - 动画时长 * @param {Function} onComplete - 完成回调 */ private _executeTransition( targetNode: any, currentPanel: any, config: PageTransitionConfig, duration: number, onComplete?: Function ): void { const targetPanelName = targetNode ? targetNode.name : null; const executeIncomingAnimation = (callback?: Function) => { if ( config.incomingTransition === PageTransitionType.NONE || !targetNode ) { callback && callback(); return; } switch (config.incomingTransition) { case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideInFromRight(targetNode, duration, callback); break; case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideInFromLeft(targetNode, duration, callback); break; default: callback && callback(); break; } }; const executeOutgoingAnimation = (callback?: Function) => { if ( config.outgoingTransition === PageTransitionType.NONE || !currentPanel || !currentPanel.node || !currentPanel.node.isValid ) { callback && callback(); return; } switch (config.outgoingTransition) { case PageTransitionType.SLIDE_LEFT: UITransitionHelper.slideOutToLeft( currentPanel.node, duration, callback ); break; case PageTransitionType.SLIDE_RIGHT: UITransitionHelper.slideOutToRight( currentPanel.node, duration, callback ); break; default: callback && callback(); break; } }; const closeCurrentPanel = () => { if ( currentPanel && currentPanel.onClose && typeof currentPanel.onClose === "function" ) { if (currentPanel.node.name !== "ThemePanel") { currentPanel.onClose(); } else { // 如果当前面板是 ThemePanel,检查是否需要关闭子页面 console.log( "[NavigationManager] ThemePanel detected, checking for child panels" ); if ( currentPanel.closeChildPanel && typeof currentPanel.closeChildPanel === "function" ) { currentPanel.closeChildPanel(); } } } // 检查目标面板是否会导致需要隐藏 ThemePanel 的子页面 if (targetPanelName && targetPanelName !== "GirlListPanel") { this.handleThemePanelChildPanelsOnNavigation(); } onComplete && onComplete(); }; if (config.simultaneous) { let completedAnimations = 0; const animationComplete = () => { completedAnimations++; if (completedAnimations >= 2) { closeCurrentPanel(); } }; executeIncomingAnimation(animationComplete); executeOutgoingAnimation(animationComplete); } else { executeOutgoingAnimation(() => { executeIncomingAnimation(closeCurrentPanel); }); } } /** * 进入角色列表页面(作为 ThemePanel 的子页面) * * @param {number} themeId - 主题ID,用于筛选角色 * * @example * ```typescript * NavigationManager.Instance.navigateToGirlList(1); * ``` */ public navigateToGirlList(themeId: number): void { if (themeId == null || themeId < 0) { console.warn("Invalid theme ID for girl list navigation:", themeId); return; } // 更新选中的主题ID this.setSelectedThemeId(themeId); console.log( `[NavigationManager] Navigating to girl list with theme ID: ${themeId}` ); // 先检查是否已有GirlListPanel存在 const existingGirlListPanel = this.findExistingGirlListPanel(); if (existingGirlListPanel) { // 如果面板已存在,重新显示并刷新数据 console.log("[NavigationManager] Reactivating existing GirlListPanel"); this.reactivateGirlListPanel(existingGirlListPanel, themeId); } else { // 如果面板不存在,创建新的 console.log("[NavigationManager] Creating new GirlListPanel"); ViewManager.I.openBundlesView( "GirlListPanel", { themeId: themeId, parentPanel: "ThemePanel", // 标记父页面 }, (girlListPanelNode) => { // 获取 ThemePanel 实例并设置子页面关系 console.log( "[NavigationManager] GirlListPanel opened, setting up parent-child relationship" ); this.setupParentChildRelationship(girlListPanelNode); } ); } } /** * 查找已存在的GirlListPanel */ private findExistingGirlListPanel(): any { try { const viewList = (ViewManager.I as any).m_viewList; if (viewList) { for (let i = 0; i < viewList.length; i++) { const view = viewList[i]; if (view && view.isValid && view.name === "GirlListPanel") { return view; } } } return null; } catch (error) { console.error( "[NavigationManager] Error finding existing GirlListPanel:", error ); return null; } } /** * 重新激活已存在的GirlListPanel */ private reactivateGirlListPanel(panelNode: any, themeId: number): void { try { // 重新激活面板 panelNode.active = true; // 获取面板组件并刷新数据 const girlListComponent = panelNode.getComponent("GirlListPanel"); if (girlListComponent) { // 如果有刷新方法,调用它来更新主题数据 if (typeof girlListComponent.refreshWithTheme === "function") { girlListComponent.refreshWithTheme(themeId); } else if (typeof girlListComponent.Show === "function") { girlListComponent.Show(themeId); } console.log( "[NavigationManager] GirlListPanel reactivated with theme:", themeId ); } // 重新建立父子关系 this.setupParentChildRelationship(panelNode); } catch (error) { console.error( "[NavigationManager] Error reactivating GirlListPanel:", error ); } } /** * 建立父子页面关系的私有方法 * @private * @param girlListPanelNode GirlListPanel 节点 */ private setupParentChildRelationship(girlListPanelNode: any): void { try { // 查找 ThemePanel 实例 const themePanelNode = this.findThemePanelNode(); if (themePanelNode) { const themePanelComponent = themePanelNode.getComponent("ThemePanel"); const girlListPanelComponent = girlListPanelNode.getComponent("GirlListPanel"); if (themePanelComponent && girlListPanelComponent) { console.log( "[NavigationManager] Setting up parent-child relationship" ); themePanelComponent.setChildPanel(girlListPanelComponent); } else { console.warn("[NavigationManager] Failed to get panel components"); } } else { console.warn("[NavigationManager] ThemePanel not found"); } } catch (error) { console.error( "[NavigationManager] Error setting up parent-child relationship:", error ); } } /** * 查找 ThemePanel 节点的私有方法 * @private * @returns ThemePanel 节点或 null */ private findThemePanelNode(): any { console.log("[NavigationManager] Finding ThemePanel node..."); try { // 通过 ViewManager 的视图列表查找 ThemePanel const viewList = (ViewManager.I as any).m_viewList; if (viewList) { for (let i = 0; i < viewList.length; i++) { const view = viewList[i]; if (view && view.isValid && view.name === "ThemePanel") { console.log("[NavigationManager] Found ThemePanel node"); return view; } } } console.log("[NavigationManager] ThemePanel not found in view list"); return null; } catch (error) { console.error("[NavigationManager] Error finding ThemePanel:", error); return null; } } /** * 处理导航时 ThemePanel 子页面的隐藏逻辑 * @private */ private handleThemePanelChildPanelsOnNavigation(): void { console.log( "[NavigationManager] Handling ThemePanel child panels on navigation" ); const themePanelNode = this.findThemePanelNode(); if (themePanelNode) { const themePanelComponent = themePanelNode.getComponent("ThemePanel"); if (themePanelComponent && themePanelComponent.closeChildPanel) { console.log("[NavigationManager] Closing ThemePanel child panels"); themePanelComponent.closeChildPanel(); } } } /** * 进入聊天页面 * * @param {number} roleId - 角色ID * * @example * ```typescript * NavigationManager.Instance.navigateToChat(10001); * ``` */ public navigateToChat(roleId: number): void; /** * 进入聊天页面(带过渡动画) * * @param {number} roleId - 角色ID * @param {PageTransitionConfig} transitionConfig - 过渡动画配置 * @param {any} currentPanel - 当前面板实例(可选) * * @example * ```typescript * NavigationManager.Instance.navigateToChat(10001, * { incomingTransition: PageTransitionType.FADE, outgoingTransition: PageTransitionType.SLIDE_LEFT }, * this * ); * ``` */ public navigateToChat( roleId: number, transitionConfig?: PageTransitionConfig, currentPanel?: any ): void; public navigateToChat( roleId: number, transitionConfig?: PageTransitionConfig, currentPanel?: any ): void { if (roleId == null || roleId <= 0) { console.warn("Invalid role ID for chat navigation:", roleId); return; } // 更新选中的角色ID this.setSelectedGirlId(roleId); // NavigationPanel 始终显示,不需要隐藏 DefaultView console.log(`Navigating to chat with role ID: ${roleId}`); if (transitionConfig) { this.navigateWithTransition( "ChatPanel", roleId, transitionConfig, currentPanel ); } else { ViewManager.I.openBundlesView("ChatPanel", roleId); } } /** * 带过渡动画进入聊天页面 * 从GirlDetailPanel平滑过渡到ChatPanel * * @param {string} categoryId - 类别ID * @param {number} roleId - 角色ID * @param {any} currentPanel - 当前面板实例(GirlDetailPanel) * * @example * ```typescript * NavigationManager.Instance.navigateToChatWithTransition("1", 10001, this); * ``` */ public navigateToChatWithTransition( categoryId: string, roleId: number, currentPanel?: any ): void { if (roleId == null || roleId <= 0) { console.warn( "Invalid role ID for chat navigation with transition:", roleId ); return; } // 更新选中的角色ID和主题ID this.setSelectedGirlId(roleId); if (categoryId != null) { this.setSelectedThemeId(parseInt(categoryId)); } const navigationData = { categoryId: categoryId, roleId: roleId, }; const transitionConfig: PageTransitionConfig = { incomingTransition: PageTransitionType.SLIDE_RIGHT, outgoingTransition: PageTransitionType.SLIDE_LEFT, duration: 0.3, simultaneous: true, }; this.navigateWithTransition( "ChatPanel", navigationData, transitionConfig, currentPanel ); } /** * 进入角色详情页面 * * @param {number} roleId - 角色ID * * @example * ```typescript * NavigationManager.Instance.navigateToGirlDetail(10001); * ``` */ public navigateToGirlDetail(roleId: number): void; /** * 进入角色详情页面(带过渡动画) * * @param {number} roleId - 角色ID * @param {PageTransitionConfig} transitionConfig - 过渡动画配置 * @param {any} currentPanel - 当前面板实例(可选) * * @example * ```typescript * NavigationManager.Instance.navigateToGirlDetail(10001, * { incomingTransition: PageTransitionType.SLIDE_RIGHT, outgoingTransition: PageTransitionType.SLIDE_LEFT }, * this * ); * ``` */ public navigateToGirlDetail( roleId: number, transitionConfig?: PageTransitionConfig, currentPanel?: any ): void; public navigateToGirlDetail( roleId: number, transitionConfig?: PageTransitionConfig, currentPanel?: any ): void { if (roleId == null || roleId <= 0) { console.warn("Invalid role ID for detail navigation:", roleId); return; } // 更新选中的角色ID this.setSelectedGirlId(roleId); console.log(`Navigating to girl detail with role ID: ${roleId}`); if (transitionConfig) { this.navigateWithTransition( "GirlDetailPanel", roleId, transitionConfig, currentPanel ); } else { ViewManager.I.openBundlesView("GirlDetailPanel", roleId); } } /** * 返回主界面 * * @example * ```typescript * NavigationManager.Instance.navigateToMainMenu(); * ``` */ public navigateToMainMenu(): void { console.log("Navigating to main menu"); // TODO: 实现返回主界面的逻辑 // ViewManager.I.openBundlesView("MainMenu"); } /** * 检查是否可以导航到指定页面 * * @param {string} pageName - 页面名称 * @returns {boolean} 是否可以导航 */ public canNavigateTo(pageName: string): boolean { const allowedPages = ["GirlListPanel", "ChatPanel", "GirlDetailPanel"]; return allowedPages.indexOf(pageName) >= 0; } /** * 获取当前页面的导航历史(预留功能) * * @returns {string[]} 导航历史数组 */ public getNavigationHistory(): string[] { // TODO: 实现导航历史记录功能 console.log("Navigation history feature not implemented yet"); return []; } /** * 返回上一页(预留功能) */ public goBack(): void { // TODO: 实现返回上一页功能 console.log("Go back feature not implemented yet"); } /** * 获取预设的过渡动画配置 */ public static getTransitionPresets() { return { SLIDE_LEFT_TO_RIGHT: { incomingTransition: PageTransitionType.SLIDE_RIGHT, outgoingTransition: PageTransitionType.SLIDE_LEFT, duration: 0.1, simultaneous: true, } as PageTransitionConfig, SLIDE_RIGHT_TO_LEFT: { incomingTransition: PageTransitionType.SLIDE_LEFT, outgoingTransition: PageTransitionType.SLIDE_RIGHT, duration: 0.1, simultaneous: true, } as PageTransitionConfig, SLIDE_OVER: { incomingTransition: PageTransitionType.SLIDE_RIGHT, outgoingTransition: PageTransitionType.NONE, duration: 0.1, simultaneous: true, } as PageTransitionConfig, SLIDE_UNDER: { incomingTransition: PageTransitionType.NONE, outgoingTransition: PageTransitionType.SLIDE_LEFT, duration: 0.1, simultaneous: true, } as PageTransitionConfig, }; } }