using System.Collections.Generic; using UnityEngine; namespace XericUI.Form { /// /// UI 窗体管理器 — 窗口管理中心。挂载在 Canvas 下作为所有窗口的父容器。 /// 通过 Entry 驱动管理窗口生命周期、层级排序、委托事件发布。 /// 支持任务栏和边缘触发隐藏窗口。 /// [AddComponentMenu("Xeric UI Vessel/Form/UI Form Manager")] [DefaultExecutionOrder(32000)] public class UIFormManager : MonoBehaviour { [Header("边缘触发")] [SerializeField, Tooltip("边缘触发区域大小")] private Vector2 m_EdgeTriggerSize = new Vector2(80, 60); [Header("调试")] [SerializeField, Tooltip("输出事件日志")] private bool m_EnableDebugLog; #region === 委托事件 === /// 窗口创建 public event WindowEntryEventHandler OnWindowCreated; /// 窗口销毁 public event WindowEntryEventHandler OnWindowDestroyed; /// 窗口状态变更 public event WindowEntryEventHandler OnWindowStateChanged; /// 窗口获得焦点(置顶) public event WindowEntryEventHandler OnWindowFocused; #endregion #region === 内部状态 === private readonly Dictionary _entries = new Dictionary(); private readonly List _orderedEntries = new List(); private string _focusedEntryId; // 边缘隐藏(按方向分组) private readonly Dictionary> _edgeHidden = new() { { AutoHideDirection.Left, new List() }, { AutoHideDirection.Right, new List() }, { AutoHideDirection.Up, new List() }, { AutoHideDirection.Down, new List() }, }; private AutoHideDirection? _lastEdgeTrigger; #endregion #region === 生命周期 === protected virtual void Update() { CheckEdgeTriggers(); } #endregion #region === 程序化窗口创建 / 销毁 === /// 程序化创建一个窗口实例并返回其 Entry public WindowEntry CreateWindow(RectTransform prefab, string title = null, Sprite icon = null) { if (prefab == null) { Debug.LogError("[UIFormManager] CreateWindow: prefab 为空", this); return null; } var instance = Instantiate(prefab, transform); instance.name = title ?? prefab.name; var controller = instance.GetComponent(); var entry = new WindowEntry { Title = title ?? prefab.name, Icon = icon, Prefab = prefab, Controller = controller, State = WindowState.Normal, }; if (controller != null) controller.Entry = entry; _entries[entry.Id] = entry; _orderedEntries.Add(entry); MoveToFront(entry.Id); NotifyCreated(entry); if (m_EnableDebugLog) Debug.Log($"[UIFormManager] 创建窗口: {entry.Title} (ID={entry.Id})"); return entry; } /// 销毁窗口及其所有子窗口 public void DestroyWindow(string entryId) { if (!_entries.TryGetValue(entryId, out var entry)) return; // 级联关闭子窗口 CascadeCloseChildren(entry); UnregisterEdgeHiddenForEntry(entry); _orderedEntries.Remove(entry); _entries.Remove(entryId); if (_focusedEntryId == entryId) _focusedEntryId = null; var controller = entry.Controller; entry.Controller = null; NotifyDestroyed(entry); if (controller != null && Application.isPlaying) Destroy(controller.gameObject); } private void CascadeCloseChildren(WindowEntry entry) { for (int i = entry.ChildEntries.Count - 1; i >= 0; i--) { var child = entry.ChildEntries[i]; DestroyWindow(child.Id); } } #endregion #region === 显示控制 === /// 显示窗口并置顶 public void ShowWindow(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; entry.Controller.gameObject.SetActive(true); MoveToFront(entryId); } /// 隐藏窗口 public void HideWindow(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; entry.Controller.gameObject.SetActive(false); } /// 切换窗口显示/隐藏 public void ToggleWindow(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; if (entry.State == WindowState.Minimized) { RestoreWindow(entryId); } else if (entry.Controller.gameObject.activeSelf) { MinimizeWindow(entryId); } else { ShowWindow(entryId); } } /// 将窗口置顶 public void MoveToFront(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; entry.Controller.transform.SetAsLastSibling(); _orderedEntries.Remove(entry); _orderedEntries.Add(entry); _focusedEntryId = entryId; NotifyFocused(entry); } #endregion #region === 最小化 / 恢复 === /// 最小化窗口 public void MinimizeWindow(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; if (entry.State == WindowState.Minimized) return; entry.Controller.gameObject.SetActive(false); entry.State = WindowState.Minimized; NotifyStateChanged(entry); } /// 恢复已最小化的窗口 public void RestoreWindow(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; if (entry.State != WindowState.Minimized) return; ShowWindow(entryId); entry.State = WindowState.Normal; NotifyStateChanged(entry); } public void ToggleMaximize(string entryId) { if (!TryGetEntry(entryId, out var entry)) return; if (entry.Controller == null) return; entry.Controller.ToggleMaximize(); NotifyStateChanged(entry); } #endregion #region === 查询 === public WindowEntry GetEntry(string id) { _entries.TryGetValue(id, out var entry); return entry; } public IReadOnlyList GetAllEntries() => _orderedEntries; public WindowEntry GetFocusedEntry() { if (string.IsNullOrEmpty(_focusedEntryId)) return null; return GetEntry(_focusedEntryId); } public int EntryCount => _entries.Count; #endregion #region === 边缘隐藏 === public void RegisterEdgeHidden(WindowEntry entry, AutoHideDirection direction) { if (entry == null) return; var list = _edgeHidden[direction]; if (!list.Contains(entry)) list.Add(entry); } public void UnregisterEdgeHidden(WindowEntry entry) { if (entry == null) return; foreach (var list in _edgeHidden.Values) list.Remove(entry); } private void UnregisterEdgeHiddenForEntry(WindowEntry entry) { UnregisterEdgeHidden(entry); } private void CheckEdgeTriggers() { Vector2 mousePos = Input.mousePosition; float sw = Screen.width; float sh = Screen.height; bool leftTrigger = mousePos.x < m_EdgeTriggerSize.x; bool rightTrigger = mousePos.x > (sw - m_EdgeTriggerSize.x); bool bottomTrigger = mousePos.y < m_EdgeTriggerSize.y; bool topTrigger = mousePos.y > (sh - m_EdgeTriggerSize.y); AutoHideDirection? activeTrigger = null; if (leftTrigger) activeTrigger = AutoHideDirection.Left; else if (rightTrigger) activeTrigger = AutoHideDirection.Right; else if (bottomTrigger) activeTrigger = AutoHideDirection.Down; else if (topTrigger) activeTrigger = AutoHideDirection.Up; if (activeTrigger.HasValue && activeTrigger != _lastEdgeTrigger) { ShowEdgeWindows(activeTrigger.Value); } if (!activeTrigger.HasValue) _lastEdgeTrigger = null; } public void ShowEdgeWindows(AutoHideDirection direction) { _lastEdgeTrigger = direction; var list = _edgeHidden[direction]; for (int i = list.Count - 1; i >= 0; i--) { var entry = list[i]; if (entry?.Controller == null) { list.RemoveAt(i); continue; } entry.Controller.SlideInFromEdge(); } } #endregion #region === 内部通知 === private void NotifyCreated(WindowEntry entry) => OnWindowCreated?.Invoke(entry); private void NotifyDestroyed(WindowEntry entry) => OnWindowDestroyed?.Invoke(entry); private void NotifyStateChanged(WindowEntry entry) => OnWindowStateChanged?.Invoke(entry); private void NotifyFocused(WindowEntry entry) => OnWindowFocused?.Invoke(entry); #endregion #region === 工具 === private bool TryGetEntry(string id, out WindowEntry entry) { if (string.IsNullOrEmpty(id)) { entry = null; return false; } return _entries.TryGetValue(id, out entry); } #endregion } }