1237 lines
36 KiB
TypeScript
1237 lines
36 KiB
TypeScript
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";
|
||
import { logger } from "db://assets/Scripts/Main/Common/Logger";
|
||
|
||
/**
|
||
* 页面过渡动画类型枚举
|
||
*/
|
||
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",
|
||
RANK = "RankPanel",
|
||
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;
|
||
hideBasePanel?: boolean; // 是否隐藏basePanel,用于全屏子面板
|
||
}
|
||
|
||
/**
|
||
* 导航管理器
|
||
*
|
||
* 负责管理游戏中的页面导航和路由,将导航逻辑从业务逻辑中分离出来
|
||
* 统一管理NavigationPanel和其他面板的切换
|
||
*
|
||
* @author AI Chat System
|
||
* @version 1.0.0
|
||
*/
|
||
export class NavigationManager {
|
||
private static _instance: NavigationManager;
|
||
|
||
// ==================== 主面板管理相关 ====================
|
||
private navigationPanel: any = null; // NavigationPanel实例引用
|
||
private currentActivePanelType: PanelType = PanelType.PAST_GIRL_LIST;
|
||
private currentActivePanel: Node = null;
|
||
private panelCache: Map<PanelType, Node> = new Map();
|
||
|
||
// ==================== 子面板栈管理相关 ====================
|
||
private subPanelStacks: Map<PanelType, Node[]> = new Map();
|
||
private subPanelMapping: Map<Node, PanelType> = new Map();
|
||
private subPanelAnimationConfigs: Map<Node, SubPanelAnimationConfig> = new Map();
|
||
private hiddenBasePanels: Map<Node, Node> = new Map();
|
||
private subPanelHiddenStates: Map<Node, Map<Node, boolean>> = new Map();
|
||
private tempHiddenStates: Map<Node, boolean> = null;
|
||
|
||
// ==================== 弹窗管理相关 ====================
|
||
private popupPanels: Set<Node> = new Set();
|
||
|
||
// ==================== 角色选择相关 ====================
|
||
private selectedGirlId: number = 10002;
|
||
private selectedCategoryId: number = -1;
|
||
private static LastSelectGirlID = "LastSelectGirlID";
|
||
|
||
// ==================== 动画状态 ====================
|
||
private isAnimating: boolean = false;
|
||
|
||
/**
|
||
* 获取NavigationManager的单例实例
|
||
* @returns 导航管理器实例
|
||
* @static
|
||
*/
|
||
public static get Instance(): NavigationManager {
|
||
if (!this._instance) {
|
||
this._instance = new NavigationManager();
|
||
|
||
//初始化默认选的girl
|
||
let cacheGirlId = sys.localStorage.getItem(this.LastSelectGirlID);
|
||
if (!cacheGirlId) {
|
||
cacheGirlId = 10016;
|
||
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() {
|
||
// 私有构造函数,确保单例模式
|
||
}
|
||
|
||
// ==================== 公共方法 ====================
|
||
|
||
/**
|
||
* 注册NavigationPanel实例
|
||
* @param panel NavigationPanel实例
|
||
*/
|
||
public registerNavigationPanel(panel: any): void {
|
||
this.navigationPanel = panel;
|
||
logger.log("[NavigationManager] NavigationPanel registered");
|
||
}
|
||
|
||
/**
|
||
* 统一的面板切换方法
|
||
* @param panelType 目标面板类型
|
||
* @param force 是否强制切换(即使已经在当前面板)
|
||
* @param callback 切换完成后的回调函数
|
||
* @returns 返回切换后的面板节点,失败返回null
|
||
*/
|
||
public switchToPanel(
|
||
panelType: PanelType,
|
||
force: boolean = false,
|
||
callback?: (panel: Node) => void
|
||
): Node | null {
|
||
if (!panelType) {
|
||
logger.error("[NavigationManager] switchToPanel: panelType is invalid");
|
||
return null;
|
||
}
|
||
|
||
if (this.currentActivePanelType === panelType && !force) {
|
||
logger.log(
|
||
`[NavigationManager] Already on ${panelType}, skipping switch`
|
||
);
|
||
callback && callback(this.getCurrentActivePanelNode());
|
||
return this.currentActivePanel;
|
||
}
|
||
|
||
if (!this.navigationPanel) {
|
||
logger.warn(
|
||
"[NavigationManager] NavigationPanel not registered"
|
||
);
|
||
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.currentActivePanelType) {
|
||
logger.log(
|
||
`[NavigationManager] 切换主面板,清空当前面板的子页面栈: ${this.currentActivePanelType}`
|
||
);
|
||
this.clearSubPanelStack(this.currentActivePanelType, true);
|
||
}
|
||
|
||
// 隐藏当前面板
|
||
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?.();
|
||
if (callback instanceof Function) callback(panel);
|
||
});
|
||
});
|
||
}
|
||
|
||
// ==================== 子面板管理方法 ====================
|
||
|
||
/**
|
||
* 打开子面板
|
||
* @param panelName 子面板名称
|
||
* @param basePanel 基础面板节点
|
||
* @param data 传递给子面板的数据
|
||
* @param animationConfig 动画配置
|
||
*/
|
||
public openSubPanel(
|
||
panelName: string,
|
||
basePanel: Node,
|
||
data: any = null,
|
||
animationConfig?: SubPanelAnimationConfig
|
||
): void {
|
||
if (!panelName) {
|
||
logger.error("[NavigationManager] openSubPanel: panelName is empty");
|
||
return;
|
||
}
|
||
|
||
if (!basePanel || !basePanel.isValid) {
|
||
logger.error("[NavigationManager] openSubPanel: basePanel is invalid");
|
||
return;
|
||
}
|
||
|
||
// 确定主面板类型
|
||
const basePanelType = this.getPanelType(basePanel);
|
||
if (!basePanelType) {
|
||
logger.error(
|
||
`[NavigationManager] 无法确定basePanel类型: ${basePanel.name}`
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 使用默认动画配置
|
||
const defaultConfig: SubPanelAnimationConfig = {
|
||
openAnimation: PageTransitionType.SCALE,
|
||
closeAnimation: PageTransitionType.SCALE,
|
||
duration: 0.3,
|
||
hideBasePanel: false,
|
||
};
|
||
const config = animationConfig || defaultConfig;
|
||
|
||
// 获取当前栈顶的子面板
|
||
const currentTopSubPanel = this.getTopSubPanel(basePanelType);
|
||
|
||
// 如果需要隐藏basePanel,同时隐藏栈中所有子面板,并记录它们的原始显隐状态
|
||
if (config.hideBasePanel) {
|
||
logger.log(
|
||
`[NavigationManager] hideBasePanel=true,隐藏basePanel并记录栈中所有子面板的显隐状态`
|
||
);
|
||
|
||
// 创建临时存储Map来记录所有被隐藏子面板的原始active状态
|
||
this.tempHiddenStates = new Map<Node, boolean>();
|
||
|
||
// 先隐藏当前显示的栈顶子面板(如果有)
|
||
if (currentTopSubPanel && currentTopSubPanel.isValid) {
|
||
// 记录当前栈顶的原始状态
|
||
this.tempHiddenStates.set(
|
||
currentTopSubPanel,
|
||
currentTopSubPanel.active
|
||
);
|
||
logger.log(
|
||
`[NavigationManager] 记录当前栈顶子面板状态: ${currentTopSubPanel.name}, 原始active=${currentTopSubPanel.active}`
|
||
);
|
||
// 隐藏当前栈顶
|
||
if (currentTopSubPanel.active) {
|
||
currentTopSubPanel.active = false;
|
||
logger.log(
|
||
`[NavigationManager] 隐藏当前栈顶子面板: ${currentTopSubPanel.name}`
|
||
);
|
||
}
|
||
}
|
||
|
||
// 获取当前栈中的所有子面板
|
||
const stack = this.subPanelStacks.get(basePanelType);
|
||
if (stack && stack.length > 0) {
|
||
// 遍历栈中所有子面板,记录并隐藏它们
|
||
for (const subPanel of stack) {
|
||
// 跳过已经处理的当前栈顶
|
||
if (subPanel === currentTopSubPanel) {
|
||
continue;
|
||
}
|
||
|
||
if (subPanel && subPanel.isValid) {
|
||
// 记录原始active状态
|
||
this.tempHiddenStates.set(subPanel, subPanel.active);
|
||
logger.log(
|
||
`[NavigationManager] 记录子面板状态: ${subPanel.name}, 原始active=${subPanel.active}`
|
||
);
|
||
|
||
// 隐藏子面板
|
||
if (subPanel.active) {
|
||
subPanel.active = false;
|
||
logger.log(
|
||
`[NavigationManager] 隐藏栈中子面板: ${subPanel.name}`
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 同时记录basePanel的原始active状态
|
||
this.tempHiddenStates.set(basePanel, basePanel.active);
|
||
logger.log(
|
||
`[NavigationManager] 记录basePanel状态: ${basePanel.name}, 原始active=${basePanel.active}`
|
||
);
|
||
|
||
// 隐藏basePanel
|
||
if (basePanel.active) {
|
||
logger.log(`[NavigationManager] 隐藏basePanel: ${basePanel.name}`);
|
||
basePanel.active = false;
|
||
}
|
||
} else {
|
||
// 不需要隐藏basePanel,只隐藏当前栈顶子面板(原有逻辑)
|
||
if (currentTopSubPanel && currentTopSubPanel.isValid) {
|
||
logger.log(
|
||
`[NavigationManager] 隐藏当前栈顶子面板: ${currentTopSubPanel.name}`
|
||
);
|
||
this.hideSubPanel(currentTopSubPanel);
|
||
}
|
||
}
|
||
|
||
const callback = (n: Node) => {
|
||
if (!n || !n.isValid) {
|
||
logger.error(
|
||
"[NavigationManager] openSubPanel: created panel is invalid"
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 初始化栈(如果不存在)
|
||
if (!this.subPanelStacks.has(basePanelType)) {
|
||
this.subPanelStacks.set(basePanelType, []);
|
||
}
|
||
|
||
// 将新子面板推入栈顶
|
||
const stack = this.subPanelStacks.get(basePanelType);
|
||
stack.push(n);
|
||
|
||
// 记录子面板到主面板的映射
|
||
this.subPanelMapping.set(n, basePanelType);
|
||
|
||
// 存储动画配置
|
||
this.subPanelAnimationConfigs.set(n, config);
|
||
|
||
// 如果配置了隐藏basePanel,记录映射关系
|
||
if (config.hideBasePanel) {
|
||
this.hiddenBasePanels.set(n, basePanel);
|
||
logger.log(
|
||
`[NavigationManager] 记录隐藏映射: ${n.name} -> ${basePanel.name}`
|
||
);
|
||
|
||
// 如果有临时存储的隐藏状态,转移到新面板的映射中
|
||
if (this.tempHiddenStates && this.tempHiddenStates.size > 0) {
|
||
this.subPanelHiddenStates.set(n, this.tempHiddenStates);
|
||
logger.log(
|
||
`[NavigationManager] 保存${this.tempHiddenStates.size}个子面板的隐藏状态到${n.name}`
|
||
);
|
||
// 清空临时存储
|
||
this.tempHiddenStates = null;
|
||
}
|
||
}
|
||
|
||
logger.log(
|
||
`[NavigationManager] 子面板已推入栈顶: ${n.name}, 当前栈深度: ${stack.length}`
|
||
);
|
||
|
||
// 执行打开动画
|
||
this.playSubPanelOpenAnimation(n, config);
|
||
|
||
// 动画完成后,根据栈顶子面板更新 basePanel 显隐状态
|
||
this.updateBasePanelVisibility(basePanelType);
|
||
};
|
||
|
||
ViewManager.I.openBundlesView(panelName, data, callback);
|
||
}
|
||
|
||
/**
|
||
* 将过渡类型转换为方向字符串
|
||
*/
|
||
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) {
|
||
logger.error("[NavigationManager] openPopupPanel: panelName is empty");
|
||
return;
|
||
}
|
||
|
||
const popupCallback = (panel: Node) => {
|
||
if (!panel || !panel.isValid) {
|
||
logger.error(
|
||
"[NavigationManager] openPopupPanel: created panel is invalid"
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 将弹窗添加到管理集合中
|
||
this.popupPanels.add(panel);
|
||
logger.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) {
|
||
logger.warn("[NavigationManager] closePopupPanel: panel is invalid");
|
||
return;
|
||
}
|
||
|
||
// 从弹窗管理集合中移除
|
||
if (this.popupPanels.has(panel)) {
|
||
this.popupPanels.delete(panel);
|
||
logger.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) {
|
||
logger.error("[NavigationManager] closePopupPanel error:", error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 关闭所有弹窗
|
||
*/
|
||
private closeAllPopups(): void {
|
||
if (this.popupPanels.size === 0) {
|
||
return;
|
||
}
|
||
|
||
logger.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();
|
||
}
|
||
|
||
/**
|
||
* 获取指定主面板栈顶的子面板
|
||
* @param panelType 主面板类型
|
||
* @returns 栈顶子面板,如果栈为空则返回null
|
||
*/
|
||
private getTopSubPanel(panelType: PanelType): Node | null {
|
||
const stack = this.subPanelStacks.get(panelType);
|
||
if (!stack || stack.length === 0) {
|
||
return null;
|
||
}
|
||
return stack[stack.length - 1];
|
||
}
|
||
|
||
/**
|
||
* 清空指定主面板的子页面栈
|
||
* @param panelType 主面板类型
|
||
* @param withAnimation 是否播放动画
|
||
* @param callback 清空完成后的回调
|
||
*/
|
||
private clearSubPanelStack(
|
||
panelType: PanelType,
|
||
withAnimation: boolean = true,
|
||
callback?: Function
|
||
): void {
|
||
const stack = this.subPanelStacks.get(panelType);
|
||
if (!stack || stack.length === 0) {
|
||
callback && callback();
|
||
return;
|
||
}
|
||
|
||
if (!withAnimation) {
|
||
// 不播放动画,直接销毁所有子面板
|
||
for (let i = stack.length - 1; i >= 0; i--) {
|
||
const subPanel = stack[i];
|
||
if (subPanel && subPanel.isValid) {
|
||
// 清理映射
|
||
this.subPanelMapping.delete(subPanel);
|
||
this.subPanelAnimationConfigs.delete(subPanel);
|
||
this.hiddenBasePanels.delete(subPanel);
|
||
|
||
// 销毁节点
|
||
const baseView = subPanel.getComponent(li_BaseView);
|
||
if (baseView) {
|
||
baseView.close();
|
||
} else {
|
||
subPanel.destroy();
|
||
}
|
||
}
|
||
}
|
||
this.subPanelStacks.delete(panelType);
|
||
callback && callback();
|
||
return;
|
||
}
|
||
|
||
// 播放动画
|
||
let completedCount = 0;
|
||
const totalCount = stack.length;
|
||
|
||
const onAnimationComplete = () => {
|
||
completedCount++;
|
||
if (completedCount >= totalCount) {
|
||
this.subPanelStacks.delete(panelType);
|
||
callback && callback();
|
||
}
|
||
};
|
||
|
||
// 同时播放所有子面板的关闭动画
|
||
for (let i = stack.length - 1; i >= 0; i--) {
|
||
const subPanel = stack[i];
|
||
if (subPanel && subPanel.isValid) {
|
||
// 获取动画配置
|
||
let config = this.subPanelAnimationConfigs.get(subPanel);
|
||
if (!config) {
|
||
config = {
|
||
openAnimation: PageTransitionType.SCALE,
|
||
closeAnimation: PageTransitionType.SCALE,
|
||
duration: 0.2,
|
||
};
|
||
}
|
||
|
||
// 播放关闭动画
|
||
this.playSubPanelCloseAnimation(subPanel, config, () => {
|
||
// 清理映射
|
||
this.subPanelMapping.delete(subPanel);
|
||
this.subPanelAnimationConfigs.delete(subPanel);
|
||
this.hiddenBasePanels.delete(subPanel);
|
||
|
||
// 销毁节点
|
||
const baseView = subPanel.getComponent(li_BaseView);
|
||
if (baseView) {
|
||
baseView.close();
|
||
} else {
|
||
subPanel.destroy();
|
||
}
|
||
|
||
onAnimationComplete();
|
||
});
|
||
} else {
|
||
onAnimationComplete();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示子面板
|
||
* @param panel 子面板节点
|
||
* @param config 动画配置(可选)
|
||
* @param withAnimation 是否播放打开动画,默认为 true
|
||
*/
|
||
private showSubPanel(
|
||
panel: Node,
|
||
config?: SubPanelAnimationConfig,
|
||
withAnimation: boolean = true
|
||
): void {
|
||
if (!panel || !panel.isValid) {
|
||
return;
|
||
}
|
||
|
||
panel.active = true;
|
||
|
||
if (config && withAnimation) {
|
||
this.playSubPanelOpenAnimation(panel, config);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 隐藏子面板(不销毁)
|
||
* @param panel 子面板节点
|
||
* @param callback 隐藏完成后的回调
|
||
*/
|
||
private hideSubPanel(panel: Node, callback?: Function): void {
|
||
if (!panel || !panel.isValid) {
|
||
callback && callback();
|
||
return;
|
||
}
|
||
|
||
// 直接隐藏,不播放动画(因为会被新面板覆盖)
|
||
panel.active = false;
|
||
callback && callback();
|
||
}
|
||
|
||
/**
|
||
* 恢复被隐藏的子面板的原始显隐状态
|
||
* @param panelNode 当前要关闭的子面板
|
||
* @param newTopSubPanel 新的栈顶子面板(可选,保留参数以保持接口兼容性)
|
||
* @private
|
||
*/
|
||
private restoreHiddenSubPanels(
|
||
panelNode: Node,
|
||
newTopSubPanel: Node | null
|
||
): void {
|
||
if (!this.subPanelHiddenStates.has(panelNode)) {
|
||
return;
|
||
}
|
||
|
||
const hiddenStates = this.subPanelHiddenStates.get(panelNode);
|
||
logger.log(
|
||
`[NavigationManager] 恢复${hiddenStates.size}个子面板的原始显隐状态`
|
||
);
|
||
|
||
// 遍历恢复每个子面板的原始active状态
|
||
// 注意:basePanel 的显隐状态现在统一由 updateBasePanelVisibility() 管理
|
||
// 这里只恢复栈中其他子面板的状态
|
||
hiddenStates.forEach((originalActive, subPanel) => {
|
||
if (subPanel && subPanel.isValid) {
|
||
subPanel.active = originalActive;
|
||
logger.log(
|
||
`[NavigationManager] 恢复子面板: ${subPanel.name}, active=${originalActive}`
|
||
);
|
||
}
|
||
});
|
||
|
||
// 清理隐藏状态映射
|
||
this.subPanelHiddenStates.delete(panelNode);
|
||
}
|
||
|
||
/**
|
||
* 执行子面板关闭的降级处理(当面板不在栈中或栈已空时)
|
||
* @private
|
||
*/
|
||
private handleSubPanelCloseFallback(
|
||
panelNode: Node,
|
||
panel: Node | li_BaseView,
|
||
basePanelType: PanelType,
|
||
animationConfig?: SubPanelAnimationConfig
|
||
): void {
|
||
// 获取动画配置
|
||
let config =
|
||
animationConfig || this.subPanelAnimationConfigs.get(panelNode);
|
||
if (!config) {
|
||
config = {
|
||
openAnimation: PageTransitionType.SCALE,
|
||
closeAnimation: PageTransitionType.SCALE,
|
||
duration: 0.3,
|
||
hideBasePanel: false,
|
||
};
|
||
}
|
||
|
||
// 播放关闭动画并销毁
|
||
this.playSubPanelCloseAnimation(panelNode, config, () => {
|
||
// 恢复被隐藏的子面板状态(如果有)
|
||
this.restoreHiddenSubPanels(panelNode, null);
|
||
|
||
// 清理映射
|
||
this.subPanelMapping.delete(panelNode);
|
||
this.subPanelAnimationConfigs.delete(panelNode);
|
||
this.hiddenBasePanels.delete(panelNode);
|
||
|
||
try {
|
||
if (panel instanceof Node) {
|
||
panel.destroy();
|
||
} else {
|
||
panel.close();
|
||
}
|
||
} catch (error) {
|
||
logger.error("[NavigationManager] closeSubPanel error:", error);
|
||
}
|
||
|
||
// 统一使用 updateBasePanelVisibility 更新 basePanel 显隐状态
|
||
this.updateBasePanelVisibility(basePanelType);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 关闭子面板
|
||
* @param panel 要关闭的子面板节点或BaseView组件
|
||
* @param animationConfig 动画配置(可选)
|
||
*/
|
||
public closeSubPanel(
|
||
panel: Node | li_BaseView,
|
||
animationConfig?: SubPanelAnimationConfig
|
||
): void {
|
||
if (!panel) {
|
||
logger.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) {
|
||
logger.warn("[NavigationManager] closeSubPanel: panelNode is invalid");
|
||
return;
|
||
}
|
||
|
||
// 确定子面板所属的主面板类型
|
||
const basePanelType = this.subPanelMapping.get(panelNode);
|
||
if (!basePanelType) {
|
||
logger.warn(
|
||
`[NavigationManager] closeSubPanel: 无法确定子面板所属的主面板类型: ${panelNode.name}`
|
||
);
|
||
return;
|
||
}
|
||
|
||
// 获取栈
|
||
const stack = this.subPanelStacks.get(basePanelType);
|
||
if (!stack || stack.length === 0) {
|
||
logger.warn(
|
||
`[NavigationManager] closeSubPanel: 栈为空或不存在,执行降级处理: ${panelNode.name}`
|
||
);
|
||
this.handleSubPanelCloseFallback(panelNode, panel, basePanelType, animationConfig);
|
||
return;
|
||
}
|
||
|
||
// 查找子面板在栈中的索引
|
||
const panelIndex = stack.indexOf(panelNode);
|
||
if (panelIndex === -1) {
|
||
logger.warn(
|
||
`[NavigationManager] closeSubPanel: 子面板不在栈中,执行降级处理: ${panelNode.name}`
|
||
);
|
||
this.handleSubPanelCloseFallback(panelNode, panel, basePanelType, animationConfig);
|
||
return;
|
||
}
|
||
|
||
// 从栈中移除
|
||
stack.splice(panelIndex, 1);
|
||
logger.log(
|
||
`[NavigationManager] 从栈中移除子面板: ${panelNode.name}, 剩余栈深度: ${stack.length}`
|
||
);
|
||
|
||
// 获取动画配置(优先使用传入的配置,然后使用存储的配置,最后使用默认配置)
|
||
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,
|
||
hideBasePanel: false,
|
||
};
|
||
}
|
||
|
||
// 检查是否需要恢复显示basePanel或显示下一层子面板
|
||
const shouldRestoreBasePanel =
|
||
config.hideBasePanel && this.hiddenBasePanels.has(panelNode);
|
||
const basePanel = shouldRestoreBasePanel
|
||
? this.hiddenBasePanels.get(panelNode)
|
||
: null;
|
||
|
||
// 获取新的栈顶子面板(如果有)
|
||
const newTopSubPanel = this.getTopSubPanel(basePanelType);
|
||
|
||
// 执行关闭动画
|
||
this.playSubPanelCloseAnimation(panelNode, config, () => {
|
||
// 恢复被隐藏的子面板状态(如果有)
|
||
this.restoreHiddenSubPanels(panelNode, newTopSubPanel);
|
||
|
||
// 清理映射
|
||
this.subPanelMapping.delete(panelNode);
|
||
this.subPanelAnimationConfigs.delete(panelNode);
|
||
if (this.hiddenBasePanels.has(panelNode)) {
|
||
this.hiddenBasePanels.delete(panelNode);
|
||
}
|
||
|
||
// 销毁或关闭panel
|
||
try {
|
||
if (panel instanceof Node) {
|
||
panel.destroy();
|
||
} else {
|
||
panel.close();
|
||
}
|
||
} catch (error) {
|
||
logger.error("[NavigationManager] closeSubPanel error:", error);
|
||
}
|
||
|
||
// 关闭动画完成后,决定显示什么
|
||
if (newTopSubPanel && newTopSubPanel.isValid) {
|
||
// 栈中还有子面板,显示新的栈顶(不播放动画,直接显示)
|
||
logger.log(
|
||
`[NavigationManager] 显示新的栈顶子面板(无动画): ${newTopSubPanel.name}`
|
||
);
|
||
const newTopConfig = this.subPanelAnimationConfigs.get(newTopSubPanel);
|
||
this.showSubPanel(newTopSubPanel, newTopConfig, false); // 传入 false,不播放动画
|
||
}
|
||
|
||
// 统一使用 updateBasePanelVisibility 更新 basePanel 显隐状态
|
||
// 这个方法会根据当前栈顶是否是特殊面板自动决定显示或隐藏
|
||
this.updateBasePanelVisibility(basePanelType);
|
||
|
||
// 如果栈完全清空,删除映射
|
||
if (stack.length === 0) {
|
||
this.subPanelStacks.delete(basePanelType);
|
||
logger.log(
|
||
`[NavigationManager] 子页面栈已清空,删除映射: ${basePanelType}`
|
||
);
|
||
}
|
||
});
|
||
}
|
||
|
||
// ==================== 私有辅助方法 ====================
|
||
|
||
/**
|
||
* 检测是否是特殊面板(GirlDetailPanel 或 ChatPanel)
|
||
* 这些特殊面板在显示时需要隐藏对应的主页面
|
||
* @param panel 要检测的面板节点
|
||
* @returns 是否是特殊面板
|
||
* @private
|
||
*/
|
||
private isSpecialPanel(panel: Node): boolean {
|
||
if (!panel || !panel.isValid) {
|
||
return false;
|
||
}
|
||
return panel.name === "GirlDetailPanel" || panel.name === "ChatPanel";
|
||
}
|
||
|
||
/**
|
||
* 根据栈顶子面板更新 basePanel 的显隐状态
|
||
* 如果栈顶是特殊面板(GirlDetailPanel 或 ChatPanel),则隐藏 basePanel
|
||
* 否则显示 basePanel
|
||
* @param basePanelType 主面板类型
|
||
* @private
|
||
*/
|
||
private updateBasePanelVisibility(basePanelType: PanelType): void {
|
||
const basePanel = this.panelCache.get(basePanelType);
|
||
if (!basePanel || !basePanel.isValid) {
|
||
return;
|
||
}
|
||
|
||
const topSubPanel = this.getTopSubPanel(basePanelType);
|
||
|
||
if (topSubPanel && topSubPanel.isValid && this.isSpecialPanel(topSubPanel)) {
|
||
// 栈顶是特殊面板,隐藏 basePanel
|
||
if (basePanel.active) {
|
||
logger.log(
|
||
`[NavigationManager] 隐藏 basePanel(栈顶是特殊面板 ${topSubPanel.name}): ${basePanel.name}`
|
||
);
|
||
basePanel.active = false;
|
||
}
|
||
} else {
|
||
// 栈顶不是特殊面板或栈为空,显示 basePanel
|
||
if (!basePanel.active) {
|
||
logger.log(
|
||
`[NavigationManager] 显示 basePanel(栈顶不是特殊面板): ${basePanel.name}`
|
||
);
|
||
basePanel.active = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取面板索引(用于动画方向计算)
|
||
* @param panelType 面板类型
|
||
* @returns 面板索引
|
||
*/
|
||
private getPanelIndex(panelType: PanelType): number {
|
||
switch (panelType) {
|
||
case PanelType.THEME:
|
||
return 0;
|
||
case PanelType.RANK:
|
||
return 1;
|
||
case PanelType.PAST_GIRL_LIST:
|
||
return 2;
|
||
case PanelType.PERSONAL:
|
||
return 3;
|
||
default:
|
||
return 0;
|
||
}
|
||
}
|
||
/**
|
||
* 根据节点名称获取面板类型
|
||
* @param node 节点
|
||
* @returns 面板类型,如果无法识别则返回null
|
||
*/
|
||
private getPanelType(node: Node): PanelType | null {
|
||
if (!node || !node.isValid) {
|
||
logger.warn("[NavigationManager] getPanelType: node is invalid");
|
||
return null;
|
||
}
|
||
|
||
switch (node.name) {
|
||
case "ThemePanel":
|
||
return PanelType.THEME;
|
||
case "RankPanel":
|
||
return PanelType.RANK;
|
||
case "PastGirlListPanel":
|
||
return PanelType.PAST_GIRL_LIST;
|
||
case "PersonalPanel":
|
||
return PanelType.PERSONAL;
|
||
default:
|
||
logger.warn(
|
||
`[NavigationManager] Unknown panel type for node: ${node.name}`
|
||
);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取面板名称
|
||
*/
|
||
private getPanelName(panelType: PanelType): string {
|
||
return panelType as string;
|
||
}
|
||
|
||
/**
|
||
* 获取面板数据(预留用于扩展)
|
||
* @param panelType 面板类型
|
||
* @returns 面板数据,当前返回null
|
||
*/
|
||
private getPanelData(panelType: PanelType): any {
|
||
// 所有主页面都不需要特殊数据,此方法预留用于未来扩展
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 加载或获取面板(从缓存或新建)
|
||
* @param panelType 面板类型
|
||
* @param callback 面板加载完成的回调函数
|
||
*/
|
||
private loadOrGetPanel(
|
||
panelType: PanelType,
|
||
callback: (panel: Node) => void
|
||
): void {
|
||
// 检查缓存
|
||
if (this.panelCache.has(panelType)) {
|
||
const cachedPanel = this.panelCache.get(panelType);
|
||
if (cachedPanel && cachedPanel.isValid) {
|
||
// 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了
|
||
// 这里不需要再清理
|
||
|
||
// 特殊处理:主题面板刷新(避免重复调用Show)
|
||
if (panelType === PanelType.THEME) {
|
||
const themePanel = cachedPanel.getComponent(ThemePanel);
|
||
if (themePanel) {
|
||
// 不在这里调用Show(),因为ThemePanel已经有加载状态保护
|
||
// 只在必要时调用onHide()确保子页面状态正确
|
||
if (typeof themePanel.onHide === "function") {
|
||
logger.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);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 隐藏面板并播放动画
|
||
* @param panel 要隐藏的面板节点
|
||
* @param direction 动画方向
|
||
* @param callback 动画完成的回调函数
|
||
*/
|
||
private hidePanelWithAnimation(
|
||
panel: Node,
|
||
direction: "left" | "right" | "up" | "down",
|
||
callback?: () => void
|
||
): void {
|
||
if (!panel || !panel.isValid) {
|
||
callback && callback();
|
||
return;
|
||
}
|
||
|
||
// 注意:子面板的清理已经在 switchToPanel 中通过 clearSubPanelStack 完成了
|
||
// 这里只需要播放主面板的隐藏动画
|
||
|
||
if (!panel.active) panel.active = true;
|
||
|
||
const animationCallback = () => {
|
||
panel.active = false;
|
||
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;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 显示面板并播放动画
|
||
* @param panel 要显示的面板节点
|
||
* @param direction 动画方向
|
||
* @param callback 动画完成的回调函数
|
||
*/
|
||
private showPanelWithAnimation(
|
||
panel: Node,
|
||
direction: "left" | "right" | "up" | "down",
|
||
callback?: () => void
|
||
): void {
|
||
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;
|
||
}
|
||
}
|
||
|
||
// ==================== 公共访问器方法 ====================
|
||
|
||
/**
|
||
* 获取当前激活的面板类型
|
||
* @returns 当前激活的面板类型
|
||
*/
|
||
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);
|
||
logger.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;
|
||
logger.log("[NavigationManager] Selected theme ID set to:", themeId);
|
||
}
|
||
|
||
/**
|
||
* 获取当前选中的主题ID
|
||
* @returns 当前选中的主题ID
|
||
*/
|
||
public getSelectedCategoryId(): number {
|
||
return this.selectedCategoryId;
|
||
}
|
||
}
|