Files
2026-08-19 08:14:48 +08:00

608 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ============================================================================
* UIManager.js - UI 管理器(gameabc 框架层)
* ============================================================================
*
* 职责:
* - UI 组件的注册、注销、查询和生命周期管理
* - 场景定义与切换(BaseComponent 组合的具名快照)
* - 全局公共 UI:Loading / Message / Confirm 对话框
*
* 设计原则:
* 1. 精灵由 gameabc 编辑器预先创建,UIManager 只控制显示/隐藏状态
* 2. 本文件不含任何游戏专属逻辑,可用于任何基于 gameabc 的子游戏
* 3. 游戏层通过挂载方法(UIManager.xxx = function(){})扩展专属功能
*
* 全局 UI 配置(由子游戏注入,框架不读取任何子游戏全局名):
* 子游戏在初始化时通过 init(config) 或 configureGlobalUI(config) 传入精灵常量:
* config.loadingUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, LOADING_ICON, LOADING_TEXT } }
* config.messageUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TEXT } }
* config.confirmUI = { LAYER, GROUP_ID, SPRITES: { BACKGROUND, TITLE_TEXT, MESSAGE_TEXT, BTN_CONFIRM, BTN_CANCEL } }
* 不传则跳过 Loading/Message/Confirm 全局 UI(场景/组件等其余功能不受影响)。
*
* 依赖(必须在本文件之前加载):
* EventBus (gameabc-framework/system/EventBus.js)
* SpriteManager (gameabc-framework/core/SpriteManager.js)
*
* ============================================================================
* @guide 游戏侧扩展模式
* ============================================================================
*
* 游戏层通过独立 JS 文件向 UIManager 挂载专属方法:
*
* // CreateRoomUI.js(示例)
* (function() {
* 'use strict';
* var CREATE_ROOM_UI = SpriteConstants.CREATE_ROOM_UI;
*
* UIManager.showCreateRoom = function(config) {
* // 游戏专属逻辑...
* SpriteManager.showGroup(CREATE_ROOM_UI.GROUP_ID);
* };
* UIManager.hideCreateRoom = function() {
* SpriteManager.hideGroup(CREATE_ROOM_UI.GROUP_ID);
* };
* })();
*
* ============================================================================
* @guide 场景管理完整用法
* ============================================================================
*
* // 1. 初始化(在 UIBootstrap 中调用);若有全局 UI,注入其精灵常量(框架不读子游戏全局名):
* UIManager.init({ loadingUI: SpriteConstants.LOADING_UI, messageUI: SpriteConstants.MESSAGE_UI, confirmUI: SpriteConstants.CONFIRM_UI });
*
* // 2. 注册组件(BaseComponent 实例)
* UIManager.registerComponent('RoomView', roomView);
* UIManager.registerComponent('GameView', gameView);
*
* // 3. 注册场景(关联组件名列表)
* UIManager.registerScene(UIManager.SCENES.ROOM, ['RoomView']);
* UIManager.registerScene(UIManager.SCENES.GAME, ['GameView', 'JingCardView']);
*
* // 4. 切换场景(自动隐藏旧场景,显示新场景,发射 SCENE_CHANGED 事件)
* UIManager.switchToScene(UIManager.SCENES.GAME);
*
* // 5. 全局 UI
* UIManager.showLoading('正在加载...');
* UIManager.showMessage('操作成功', 2000);
* UIManager.showConfirm('确认', '是否退出?', onOK, onCancel);
*
* @version 1.0.0
*/
var UIManager = (function () {
'use strict';
// ==================== 私有变量 ====================
/**
* 全局 UI 精灵常量(由子游戏注入:init(config) / configureGlobalUI)
* 框架不读取任何子游戏全局名;未注入时三者为 null,对应全局 UI 不可用(优雅跳过)。
*/
var LOADING_UI = null;
var MESSAGE_UI = null;
var CONFIRM_UI = null;
/**
* 已注册的 UI 组件
* { componentName: BaseComponent 实例 }
*/
var _components = {};
/**
* 已注册的场景
* { sceneName: [componentName, ...] }
*/
var _scenes = {};
/**
* 当前活跃场景名
* @type {string|null}
*/
var _currentScene = null;
/**
* 初始化状态
*/
var _isInitialized = false;
// ==================== 场景枚举 ====================
var SCENES = {
ROOM: 'ROOM',
GAME: 'GAME',
LOADING: 'LOADING'
};
// ==================== 私有方法 ====================
/**
* 应用子游戏注入的全局 UI 配置(Loading/Message/Confirm),并隐藏所有全局弹层。
* 框架不认识任何子游戏全局名,配置由子游戏在 init()/configureGlobalUI() 时传入。
* @param {Object} config - { loadingUI, messageUI, confirmUI }
* @returns {boolean} 是否成功配置(缺省或不完整则跳过,返回 false)
* @private
*/
function _applyGlobalUIConfig(config) {
if (!config || !config.loadingUI || !config.messageUI || !config.confirmUI) {
console.warn('[UIManager] 未提供完整的全局 UI 配置(loadingUI/messageUI/confirmUI),跳过全局 UI 初始化');
return false;
}
LOADING_UI = config.loadingUI;
MESSAGE_UI = config.messageUI;
CONFIRM_UI = config.confirmUI;
_initLoadingUI();
_initMessageUI();
_initConfirmUI();
return true;
}
/** @private */
function _initLoadingUI() {
SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.hideGroup(LOADING_UI.GROUP_ID);
// console.log('[UIManager] Loading UI 初始化 (Layer ' + LOADING_UI.LAYER + ')');
}
/** @private */
function _initMessageUI() {
SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID);
// console.log('[UIManager] Message UI 初始化 (Layer ' + MESSAGE_UI.LAYER + ')');
}
/** @private */
function _initConfirmUI() {
SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID);
// console.log('[UIManager] Confirm UI 初始化 (Layer ' + CONFIRM_UI.LAYER + ')');
}
/**
* 隐藏当前场景所有可见组件
* @private
*/
function _hideCurrentScene() {
if (!_currentScene || !_scenes[_currentScene]) { return; }
var names = _scenes[_currentScene];
for (var i = 0; i < names.length; i++) {
var c = _components[names[i]];
if (c && c.isVisible) { c.hide(); }
}
}
/**
* 显示指定场景的所有组件
* @private
*/
function _showScene(sceneName) {
if (!_scenes[sceneName]) {
console.error('[UIManager] 场景不存在:', sceneName);
return;
}
var names = _scenes[sceneName];
for (var i = 0; i < names.length; i++) {
var c = _components[names[i]];
if (c && !c.isVisible) { c.show(); }
}
}
// ==================== 公共 API ====================
var publicAPI = {
/**
* 场景枚举常量
*/
SCENES: SCENES,
/**
* 是否已初始化(外部只读)
*/
initialized: false,
// ============================================================
// 初始化 / 销毁
// ============================================================
/**
* 初始化 UIManager
* @param {Object} [globalUIConfig] - 可选的全局 UI 配置 { loadingUI, messageUI, confirmUI },
* 由子游戏传入(如各自的精灵常量);框架不主动读取任何子游戏全局名。
* 不传则跳过 Loading/Message/Confirm 全局 UI(可后续用 configureGlobalUI 配置)。
* @returns {boolean}
*/
init: function (globalUIConfig) {
if (_isInitialized) {
console.warn('[UIManager] 已初始化,跳过');
return true;
}
// console.log('[UIManager] 初始化 (v1.0.0)');
try {
_components = {};
_scenes = {};
_currentScene = null;
// 全局 UI 为可选能力:未注入配置则跳过,不阻断 UIManager 初始化
if (globalUIConfig) { _applyGlobalUIConfig(globalUIConfig); }
_isInitialized = true;
this.initialized = true;
// console.log('[UIManager] 初始化完成');
return true;
} catch (err) {
console.error('[UIManager] 初始化失败:', err);
return false;
}
},
/**
* 配置全局 UI(Loading/Message/Confirm)的精灵常量。供子游戏注入,可在 init 之后单独调用。
* @param {Object} config - { loadingUI, messageUI, confirmUI }
* @returns {boolean}
*/
configureGlobalUI: function (config) {
return _applyGlobalUIConfig(config);
},
// ============================================================
// 组件管理
// ============================================================
/**
* 注册 UI 组件
* @param {string} name - 组件名称
* @param {Object} component - BaseComponent 实例(必须已 init)
* @returns {boolean}
*/
registerComponent: function (name, component) {
if (!name || typeof name !== 'string') {
console.error('[UIManager] registerComponent: name 必须是字符串');
return false;
}
if (!component || !component.isInitialized) {
console.error('[UIManager] registerComponent: 组件未初始化:', name);
return false;
}
if (_components[name]) {
console.warn('[UIManager] registerComponent: 覆盖已存在组件:', name);
}
_components[name] = component;
// console.log('[UIManager] 注册组件 "' + name + '"');
return true;
},
/**
* 获取已注册组件
* @param {string} name
* @returns {Object|null}
*/
getComponent: function (name) {
return _components[name] || null;
},
/**
* 注销组件(不销毁,只从注册表移除)
* @param {string} name
* @returns {boolean}
*/
unregisterComponent: function (name) {
if (!_components[name]) {
console.warn('[UIManager] unregisterComponent: 组件不存在:', name);
return false;
}
var c = _components[name];
if (c.isVisible) { c.hide(); }
delete _components[name];
// console.log('[UIManager] 注销组件 "' + name + '"');
return true;
},
/**
* 销毁组件(调用 component.destroy() 并移除)
* @param {string} name
* @returns {boolean}
*/
destroyComponent: function (name) {
if (!_components[name]) {
console.warn('[UIManager] destroyComponent: 组件不存在:', name);
return false;
}
_components[name].destroy();
delete _components[name];
// console.log('[UIManager] 销毁组件 "' + name + '"');
return true;
},
/**
* 显示指定视图(便捷方法)
* @param {string} name
*/
showView: function (name) {
var c = _components[name];
if (!c) { console.warn('[UIManager] showView: 组件不存在:', name); return; }
c.show();
},
/**
* 隐藏指定视图(便捷方法)
* @param {string} name
*/
hideView: function (name) {
var c = _components[name];
if (!c) { console.warn('[UIManager] hideView: 组件不存在:', name); return; }
c.hide();
},
// ============================================================
// 场景管理
// ============================================================
/**
* 注册场景
* @param {string} sceneName
* @param {string[]} componentNames - 该场景包含的组件名列表
* @returns {boolean}
*/
registerScene: function (sceneName, componentNames) {
if (!sceneName || typeof sceneName !== 'string') {
console.error('[UIManager] registerScene: sceneName 必须是字符串');
return false;
}
if (!Array.isArray(componentNames)) {
console.error('[UIManager] registerScene: componentNames 必须是数组');
return false;
}
for (var i = 0; i < componentNames.length; i++) {
if (!_components[componentNames[i]]) {
console.error('[UIManager] registerScene: 组件不存在:', componentNames[i]);
return false;
}
}
_scenes[sceneName] = componentNames;
// console.log('[UIManager] 注册场景 "' + sceneName + '" (' + componentNames.length + ' 个组件)');
return true;
},
/**
* 切换到指定场景
* 自动隐藏当前场景,显示新场景,发射 EventBus.Events.SCENE_CHANGED
* @param {string} sceneName
* @returns {boolean}
*/
switchToScene: function (sceneName) {
if (!_scenes[sceneName]) {
console.error('[UIManager] switchToScene: 场景不存在:', sceneName);
return false;
}
if (_currentScene === sceneName) {
return true;
}
// console.log('[UIManager] 切换场景 "' + (_currentScene || 'null') + '" → "' + sceneName + '"');
_hideCurrentScene();
_showScene(sceneName);
var oldScene = _currentScene;
_currentScene = sceneName;
EventBus.emit(EventBus.Events.SCENE_CHANGED, { from: oldScene, to: sceneName });
return true;
},
/**
* 获取当前场景名
* @returns {string|null}
*/
getCurrentScene: function () {
return _currentScene;
},
// ============================================================
// 全局 UI - Loading
// ============================================================
/**
* 显示加载界面
* @param {string} [message='加载中...']
*/
showLoading: function (message) {
if (!LOADING_UI) { return; }
message = message || '加载中...';
SpriteManager.setText(LOADING_UI.SPRITES.LOADING_TEXT, message);
SpriteManager.show(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.show(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.show(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.showGroup(LOADING_UI.GROUP_ID);
},
/**
* 隐藏加载界面
*/
hideLoading: function () {
if (!LOADING_UI) { return; }
SpriteManager.hide(LOADING_UI.SPRITES.BACKGROUND);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_ICON);
SpriteManager.hide(LOADING_UI.SPRITES.LOADING_TEXT);
SpriteManager.hideGroup(LOADING_UI.GROUP_ID);
},
// ============================================================
// 全局 UI - Message
// ============================================================
/**
* 显示轻提示(Toast,无精灵,仅 console)
* @param {string} message
*/
showToast: function (message) {
// console.log('[UIManager] Toast:', message);
},
/**
* 显示一个带标签的提示效果(Toast 形式,通用,不含任何玩法语义)
* @param {string} tag - 效果标签(仅用于日志区分,由调用方自定义)
* @param {string} title - 提示标题
* @param {string} [description] - 补充说明
*/
showEffectToast: function (tag, title, description) {
var msg = description ? (title + ':' + description) : title;
// console.log('[UIManager] EffectToast [' + tag + ']:', msg);
this.showToast(msg);
},
/**
* 显示消息提示(自动隐藏)
* @param {string} message
* @param {number} [duration=2000] 毫秒
*/
showMessage: function (message, duration) {
if (!MESSAGE_UI || !message) { return; }
duration = duration || 2000;
SpriteManager.setText(MESSAGE_UI.SPRITES.TEXT, message);
SpriteManager.show(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.show(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.showGroup(MESSAGE_UI.GROUP_ID);
var self = this;
setTimeout(function () { self.hideMessage(); }, duration);
},
/**
* 隐藏消息提示
*/
hideMessage: function () {
if (!MESSAGE_UI) { return; }
SpriteManager.hide(MESSAGE_UI.SPRITES.BACKGROUND);
SpriteManager.hide(MESSAGE_UI.SPRITES.TEXT);
SpriteManager.hideGroup(MESSAGE_UI.GROUP_ID);
},
// ============================================================
// 全局 UI - Confirm
// ============================================================
/**
* 显示确认对话框
* @param {string} title
* @param {string} message
* @param {Function} [onConfirm]
* @param {Function} [onCancel]
*/
showConfirm: function (title, message, onConfirm, onCancel) {
if (!CONFIRM_UI || !message) { return; }
title = title || '确认';
SpriteManager.setText(CONFIRM_UI.SPRITES.TITLE_TEXT, title);
SpriteManager.setText(CONFIRM_UI.SPRITES.MESSAGE_TEXT, message);
SpriteManager.show(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.show(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.show(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.show(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.showGroup(CONFIRM_UI.GROUP_ID);
// 回调通过全局变量传递(gameabc 引擎通过点击事件触发)
window._confirmCallback = { onConfirm: onConfirm, onCancel: onCancel };
},
/**
* 隐藏确认对话框
*/
hideConfirm: function () {
if (!CONFIRM_UI) { return; }
SpriteManager.hide(CONFIRM_UI.SPRITES.BACKGROUND);
SpriteManager.hide(CONFIRM_UI.SPRITES.TITLE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.MESSAGE_TEXT);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CONFIRM);
SpriteManager.hide(CONFIRM_UI.SPRITES.BTN_CANCEL);
SpriteManager.hideGroup(CONFIRM_UI.GROUP_ID);
if (window._confirmCallback) { delete window._confirmCallback; }
},
// ============================================================
// 工具方法
// ============================================================
/**
* 获取所有已注册组件名称
* @returns {string[]}
*/
getRegisteredComponents: function () {
var names = [];
for (var n in _components) {
if (_components.hasOwnProperty(n)) { names.push(n); }
}
return names;
},
/**
* 获取所有已注册场景名称
* @returns {string[]}
*/
getRegisteredScenes: function () {
var names = [];
for (var n in _scenes) {
if (_scenes.hasOwnProperty(n)) { names.push(n); }
}
return names;
},
/**
* 获取当前状态快照
* @returns {Object}
*/
getState: function () {
return {
isInitialized: _isInitialized,
currentScene: _currentScene,
componentCount: Object.keys(_components).length,
sceneCount: Object.keys(_scenes).length,
components: this.getRegisteredComponents(),
scenes: this.getRegisteredScenes()
};
},
/**
* 打印状态(调试用)
*/
printState: function () {
// console.log('[UIManager] 状态:', JSON.stringify(this.getState(), null, 2));
},
/**
* 销毁 UIManager(销毁所有组件并重置状态)
*/
destroy: function () {
// console.log('[UIManager] 开始销毁');
for (var name in _components) {
if (_components.hasOwnProperty(name)) { this.destroyComponent(name); }
}
this.hideLoading();
this.hideMessage();
this.hideConfirm();
_components = {};
_scenes = {};
_currentScene = null;
_isInitialized = false;
this.initialized = false;
// console.log('[UIManager] 销毁完成');
}
};
return publicAPI;
})();
if (typeof module !== 'undefined' && module.exports) {
module.exports = UIManager;
}