添加窗口任务栏功能,添加窗口的管理功能

This commit is contained in:
2026-07-26 16:49:32 +08:00
parent 96d6d63a64
commit 0c3b79b22b
17 changed files with 1905 additions and 500 deletions
+88 -255
View File
@@ -7,7 +7,6 @@ namespace XericUI.Form
{
#region 枚举与结构体
/// <summary>范围坐标空间枚举</summary>
public enum FormRangeSpace
{
RelativeOffset,
@@ -22,7 +21,7 @@ namespace XericUI.Form
[Tooltip("坐标空间")]
public FormRangeSpace space;
[Tooltip("范围定义。\nRelativeOffset: x,y=像素偏移; w,h=像素宽高(0=用参考尺寸)\nCanvasLocal: Canvas局部坐标\nAnchorToSelf: x,y=锚点0-1; w,h=尺寸比例0-1\nAnchorToCanvas: x,y=锚点0-1; w,h=尺寸比例0-1")]
[Tooltip("范围定义")]
public Rect rect;
}
@@ -30,31 +29,31 @@ namespace XericUI.Form
/// <summary>
/// UI 窗体控制器 — 挂载到窗口根 GameObject 上,自身即为完整窗口。
/// 支持全屏/还原、边缘吸附、自动隐藏、拖拽移动。
/// 标题栏功能由独立的 UITitleBar 子组件提供,Controller 只提供公开 API。
/// 支持全屏/还原、边缘吸附、自动隐藏、拖拽移动、置顶。
/// 通过 WindowEntry 与 Manager 通信。
/// </summary>
[AddComponentMenu("Xeric UI Vessel/Form/UI Form Controller")]
[RequireComponent(typeof(RectTransform))]
public class UIFormController : MonoBehaviour, IUIFormTarget, IBeginDragHandler, IDragHandler, IEndDragHandler
public class UIFormController : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerDownHandler
{
#region 序列化字段
[Header("窗口基本设置")]
[SerializeField, Tooltip("窗口标题,供外部读取")]
[SerializeField, Tooltip("窗口标题")]
private string m_WindowTitle = "Window";
[Header("边缘吸附")]
[SerializeField, Tooltip("是否启用边缘自动吸附")]
[SerializeField, Tooltip("启用边缘自动吸附")]
private bool m_EnableAutoSnap = true;
[SerializeField, Tooltip("吸附阈值(像素),窗口距边缘小于此值时自动贴边")]
[SerializeField, Tooltip("吸附阈值(像素)")]
private float m_SnapThreshold = 8f;
[Header("自动隐藏")]
[SerializeField, Tooltip("是否启用自动隐藏(贴边后滑出屏幕)")]
[SerializeField, Tooltip("启用自动隐藏")]
private bool m_EnableAutoHide;
[SerializeField, Tooltip("自动隐藏的滑动方向")]
[SerializeField, Tooltip("自动隐藏滑动方向")]
private AutoHideDirection m_AutoHideDirection = AutoHideDirection.Left;
[Header("调试")]
@@ -69,28 +68,29 @@ namespace XericUI.Form
#region 公开属性
public string WindowTitle => m_WindowTitle;
/// <summary>是否处于全屏状态</summary>
public bool IsMaximized => _windowState == WindowState.Maximized;
/// <summary>是否处于边缘隐藏状态</summary>
public bool IsEdgeHidden => _windowState == WindowState.EdgeHidden;
/// <summary>当前窗口状态</summary>
public WindowState CurrentState => _windowState;
/// <summary>自动吸附是否开启</summary>
public bool AutoSnapEnabled => m_EnableAutoSnap;
/// <summary>自动隐藏是否开启</summary>
public bool AutoHideEnabled => m_EnableAutoHide;
/// <summary>自动隐藏方向</summary>
public AutoHideDirection HideDirection => m_AutoHideDirection;
/// <summary>编辑器调试模式</summary>
public bool DebugMode => m_DebugMode;
/// <summary>窗口数据 Entry(由 Manager 在 CreateWindow 时注入)</summary>
public WindowEntry Entry
{
get => _entry;
set
{
_entry = value;
if (_entry != null)
m_WindowTitle = _entry.Title;
}
}
/// <summary>父级 Manager 引用</summary>
public UIFormManager Manager => _manager;
#endregion
#region 内部状态
@@ -98,13 +98,15 @@ namespace XericUI.Form
private RectTransform _rectTransform;
private Canvas _canvas;
private RectTransform _canvasRt;
private UIFormManager _manager;
private WindowEntry _entry;
private WindowState _windowState = WindowState.Normal;
// 拖拽
private Vector2 _dragOffset;
// 全屏还原数据
// 全屏还原
private Vector2 _savePos;
private Vector2 _saveSize;
private Vector2 _saveAnchorMin;
@@ -125,110 +127,72 @@ namespace XericUI.Form
protected virtual void Awake()
{
_rectTransform = (RectTransform)transform;
_manager = GetComponentInParent<UIFormManager>();
_canvas = GetComponentInParent<Canvas>();
_canvasRt = _canvas != null ? (RectTransform)_canvas.transform : null;
}
protected virtual void OnEnable()
{
if (UIFormManager.Main != null)
UIFormManager.Main.RegisterWindow(this);
}
protected virtual void OnDisable()
{
StopAutoHideMonitor();
if (_windowState == WindowState.EdgeHidden && UIFormManager.Main != null)
UIFormManager.Main.UnregisterEdgeHidden(this);
if (UIFormManager.Main != null)
{
UIFormManager.Main.UnregisterWindow(this);
UIFormManager.Main.UnregisterEdgeHidden(this);
}
if (_windowState == WindowState.EdgeHidden && _manager != null)
_manager.UnregisterEdgeHidden(_entry);
#if UNITY_EDITOR
UnsubscribeEditorUpdate();
#endif
_isHovering = false;
}
#endregion
#region IUIFormTarget 实现
#region === 公开 API ===
public bool IsActive => isActiveAndEnabled;
public Rect GetEnterRect() => GetWindowScreenRect();
public Rect GetExitRect() => GetWindowScreenRect();
public void OnPointerEnter()
{
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 鼠标进入窗口范围: {name}", this);
}
public void OnPointerExit()
{
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 鼠标离开窗口范围: {name}", this);
}
#endregion
#region === 公开 API(供 UITitleBar 及外部调用) ===
/// <summary>全屏/还原切换</summary>
public void ToggleMaximize()
{
if (IsMaximized)
RestoreFromFullscreen();
else
MaximizeToFullscreen();
if (_entry != null)
_entry.State = _windowState;
}
/// <summary>最小化窗口</summary>
public void Minimize()
{
if (_windowState == WindowState.Minimized) return;
if (UIFormManager.Main == null) return;
UIFormManager.Main.MinimizeWindow(this);
if (_manager == null || _entry == null) return;
_manager.MinimizeWindow(_entry.Id);
}
/// <summary>关闭窗口(Destroy GameObject)</summary>
public void Close()
{
if (UIFormManager.Main != null)
UIFormManager.Main.CloseWindow(this);
if (Application.isPlaying)
if (_manager != null && _entry != null)
_manager.DestroyWindow(_entry.Id);
else if (Application.isPlaying)
Destroy(gameObject);
else
DestroyImmediate(gameObject);
}
/// <summary>设置边缘吸附开关</summary>
public void SetAutoSnapEnabled(bool enabled)
{
m_EnableAutoSnap = enabled;
}
/// <summary>设置自动隐藏开关</summary>
public void SetAutoSnapEnabled(bool enabled) => m_EnableAutoSnap = enabled;
public void SetAutoHideEnabled(bool enabled)
{
m_EnableAutoHide = enabled;
if (!enabled && _windowState == WindowState.EdgeHidden)
{
// 关闭自动隐藏 → 还原窗口
SlideInFromEdge();
}
}
/// <summary>设置自动隐藏方向</summary>
public void SetAutoHideDirection(AutoHideDirection direction)
public void SetAutoHideDirection(AutoHideDirection direction) => m_AutoHideDirection = direction;
/// <summary>置顶窗口</summary>
public void MoveToFront()
{
m_AutoHideDirection = direction;
transform.SetAsLastSibling();
}
#endregion
@@ -250,9 +214,6 @@ namespace XericUI.Form
_rectTransform.offsetMax = Vector2.zero;
_windowState = WindowState.Maximized;
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 窗口全屏: {name}", this);
}
private void RestoreFromFullscreen()
@@ -265,25 +226,27 @@ namespace XericUI.Form
_rectTransform.sizeDelta = _saveSize;
_windowState = WindowState.Normal;
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 窗口还原: {name}", this);
}
#endregion
#region === 拖拽 ===
#region === 拖拽 & 点击聚焦 ===
public void OnPointerDown(PointerEventData eventData)
{
// 点击窗口任意位置 → 置顶
transform.SetAsLastSibling();
if (_manager != null && _entry != null)
_manager.MoveToFront(_entry.Id);
}
public void OnBeginDrag(PointerEventData eventData)
{
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized)
return;
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
// 全屏拖拽 → 先还原
if (IsMaximized)
{
RestoreFromFullscreen();
// 将窗口移到鼠标附近
_rectTransform.anchoredPosition = eventData.position;
}
@@ -292,8 +255,7 @@ namespace XericUI.Form
public void OnDrag(PointerEventData eventData)
{
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized)
return;
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
_rectTransform.anchoredPosition = eventData.position + _dragOffset;
@@ -303,14 +265,10 @@ namespace XericUI.Form
public void OnEndDrag(PointerEventData eventData)
{
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized)
return;
if (_windowState == WindowState.EdgeHidden || _windowState == WindowState.Minimized) return;
// 拖拽结束 → 如果贴边且启用自动隐藏,则触发
if (m_EnableAutoHide && IsSnappedToEdge())
{
AutoHideToEdge();
}
}
#endregion
@@ -320,54 +278,35 @@ namespace XericUI.Form
private void CheckEdgeSnap()
{
if (_canvasRt == null) return;
Vector2 canvasSize = _canvasRt.rect.size;
Vector2 pos = _rectTransform.anchoredPosition;
Vector2 size = _rectTransform.rect.size;
bool snapped = false;
// 左
if (Mathf.Abs(pos.x) < m_SnapThreshold)
{
pos.x = 0;
snapped = true;
}
// 右
else if (Mathf.Abs(pos.x + size.x - canvasSize.x) < m_SnapThreshold)
{
pos.x = canvasSize.x - size.x;
snapped = true;
}
_rectTransform.anchoredPosition = pos;
}
/// <summary>检测窗口是否贴边</summary>
private bool IsSnappedToEdge()
{
if (_canvasRt == null) return false;
Vector2 canvasSize = _canvasRt.rect.size;
Vector2 pos = _rectTransform.anchoredPosition;
Vector2 size = _rectTransform.rect.size;
return Mathf.Abs(pos.x) < 1f
|| Mathf.Abs(pos.x + size.x - canvasSize.x) < 1f;
return Mathf.Abs(pos.x) < 1f || Mathf.Abs(pos.x + size.x - canvasSize.x) < 1f;
}
/// <summary>获取最近的边缘方向</summary>
public AutoHideDirection GetClosestEdge()
{
if (_canvasRt == null) return AutoHideDirection.Left;
Vector2 canvasSize = _canvasRt.rect.size;
Vector2 pos = _rectTransform.anchoredPosition;
Vector2 size = _rectTransform.rect.size;
float distLeft = Mathf.Abs(pos.x);
float distRight = Mathf.Abs(pos.x + size.x - canvasSize.x);
return distLeft < distRight ? AutoHideDirection.Left : AutoHideDirection.Right;
}
@@ -377,8 +316,7 @@ namespace XericUI.Form
private void AutoHideToEdge()
{
if (_windowState == WindowState.EdgeHidden) return;
if (_canvasRt == null) return;
if (_windowState == WindowState.EdgeHidden || _canvasRt == null) return;
Vector2 canvasSize = _canvasRt.rect.size;
Vector2 pos = _rectTransform.anchoredPosition;
@@ -390,58 +328,40 @@ namespace XericUI.Form
if (dir == AutoHideDirection.Left || dir == AutoHideDirection.Right)
dir = GetClosestEdge();
switch (dir)
_edgeHiddenPos = dir switch
{
case AutoHideDirection.Left:
_edgeHiddenPos = new Vector2(-size.x, pos.y);
break;
case AutoHideDirection.Right:
_edgeHiddenPos = new Vector2(canvasSize.x, pos.y);
break;
case AutoHideDirection.Down:
_edgeHiddenPos = new Vector2(pos.x, -size.y);
break;
case AutoHideDirection.Up:
_edgeHiddenPos = new Vector2(pos.x, canvasSize.y);
break;
}
AutoHideDirection.Left => new Vector2(-size.x, pos.y),
AutoHideDirection.Right => new Vector2(canvasSize.x, pos.y),
AutoHideDirection.Down => new Vector2(pos.x, -size.y),
AutoHideDirection.Up => new Vector2(pos.x, canvasSize.y),
_ => pos
};
_rectTransform.anchoredPosition = _edgeHiddenPos;
_windowState = WindowState.EdgeHidden;
if (UIFormManager.Main != null)
UIFormManager.Main.RegisterEdgeHidden(this, dir);
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 自动隐藏 (方向={dir}): {name}", this);
if (_manager != null && _entry != null)
_manager.RegisterEdgeHidden(_entry, dir);
}
/// <summary>由 Manager 调用 — 从边缘滑出</summary>
public void SlideInFromEdge()
{
if (_windowState != WindowState.EdgeHidden) return;
StopAutoHideMonitor();
_rectTransform.anchoredPosition = _edgeVisiblePos;
_windowState = WindowState.Normal;
if (UIFormManager.Main != null)
UIFormManager.Main.UnregisterEdgeHidden(this);
if (_manager != null)
_manager.UnregisterEdgeHidden(_entry);
// 开始监控鼠标是否离开窗口
_autoHideCoroutine = StartCoroutine(MonitorMouseExit());
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 从边缘滑出: {name}", this);
}
/// <summary>监控鼠标是否离开窗口范围,离开则自动隐藏</summary>
private IEnumerator MonitorMouseExit()
{
while (_windowState == WindowState.Normal && m_EnableAutoHide)
{
yield return null;
Rect screenRect = GetWindowScreenRect();
if (!screenRect.Contains(Input.mousePosition))
{
@@ -464,82 +384,29 @@ namespace XericUI.Form
#region === 窗口屏幕范围 ===
/// <summary>获取窗口自身在屏幕空间中的 Rect</summary>
public Rect GetWindowScreenRect()
{
if (_rectTransform == null || _canvas == null) return Rect.zero;
Vector3[] corners = new Vector3[4];
_rectTransform.GetWorldCorners(corners);
Camera cam = _canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : _canvas.worldCamera;
Vector2 min = Vector2.positiveInfinity;
Vector2 max = Vector2.negativeInfinity;
Vector2 min = Vector2.positiveInfinity, max = Vector2.negativeInfinity;
for (int i = 0; i < 4; i++)
{
Vector2 sp = cam != null
? RectTransformUtility.WorldToScreenPoint(cam, corners[i])
: (Vector2)corners[i];
min = Vector2.Min(min, sp);
max = Vector2.Max(max, sp);
}
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
#endregion
#region === 悬停检测(供 Manager 调用) ===
public bool CheckHover(Vector2 screenPoint)
{
Rect windowRect = GetWindowScreenRect();
if (!_isHovering)
{
if (windowRect.Contains(screenPoint))
{
_isHovering = true;
OnPointerEnter();
}
}
else
{
if (!windowRect.Contains(screenPoint))
{
_isHovering = false;
OnPointerExit();
}
}
return _isHovering;
}
public void SimulateEnter()
{
_isHovering = true;
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 模拟进入: {name}", this);
}
public void SimulateExit()
{
_isHovering = false;
if (m_DebugLogEvents)
Debug.Log($"[UIFormController] 模拟退出: {name}", this);
}
#endregion
#region 编辑器调试
#if UNITY_EDITOR
[System.NonSerialized] private bool _editorDebugSubscribed;
[System.NonSerialized] private Vector2 _editorMouseScreenPos;
@@ -559,87 +426,53 @@ namespace XericUI.Form
protected virtual void OnValidate()
{
if (m_DebugMode)
SubscribeEditorUpdate();
else
UnsubscribeEditorUpdate();
if (m_DebugMode) SubscribeEditorUpdate();
else UnsubscribeEditorUpdate();
}
/// <summary>
/// SceneView 重绘回调 — 在 GUI 上下文中获取鼠标屏幕坐标并执行悬停检测。
/// 使用 GUIUtility.GUIToScreenPoint 将 SceneView 内的 GUI 坐标转为 Unity 屏幕坐标。
/// </summary>
private void OnSceneGUI(UnityEditor.SceneView sceneView)
{
if (this == null)
{
UnsubscribeEditorUpdate();
return;
}
if (this == null) { UnsubscribeEditorUpdate(); return; }
if (Application.isPlaying || !m_DebugMode) return;
if (Application.isPlaying) return;
if (!m_DebugMode) return;
// Event.current.mousePosition 是相对于 SceneView 窗口的 GUI 坐标
// GUIUtility.GUIToScreenPoint 将其转换为屏幕坐标(左下角为原点)
Vector2 guiPos = Event.current.mousePosition;
_editorMouseScreenPos = GUIUtility.GUIToScreenPoint(guiPos);
CheckHover(_editorMouseScreenPos);
Rect rect = GetWindowScreenRect();
_isHovering = rect.Contains(_editorMouseScreenPos);
}
protected virtual void OnDrawGizmosSelected()
{
if (!m_DebugMode) return;
if (_canvas == null) return;
if (_canvasRt == null) return;
if (!m_DebugMode || _canvas == null || _canvasRt == null) return;
Rect screenRect = GetWindowScreenRect();
if (screenRect.width <= 0 || screenRect.height <= 0) return;
// 悬停时高亮边框
Color borderColor = _isHovering
? new Color(0f, 1f, 0f, 0.6f)
: new Color(0f, 0.7f, 1f, 0.3f);
Gizmos.color = borderColor;
DrawScreenRectOnCanvas(screenRect, borderColor, _canvas, _canvasRt);
// 边缘吸附阈值可视化
if (m_EnableAutoSnap)
{
Gizmos.color = new Color(1f, 0.6f, 0f, 0.15f);
DrawScreenRectOnCanvas(
new Rect(screenRect.x + m_SnapThreshold, screenRect.y,
screenRect.width - m_SnapThreshold * 2, screenRect.height),
Gizmos.color, _canvas, _canvasRt);
}
}
private static void DrawScreenRectOnCanvas(Rect screenRect, Color color, Canvas canvas, RectTransform canvasRt)
{
if (screenRect.width <= 0 || screenRect.height <= 0) return;
Gizmos.color = color;
Camera uiCam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
Vector3 p0 = ScreenToWorldOnCanvas(new Vector3(screenRect.xMin, screenRect.yMin), uiCam, canvasRt);
Vector3 p1 = ScreenToWorldOnCanvas(new Vector3(screenRect.xMax, screenRect.yMin), uiCam, canvasRt);
Vector3 p2 = ScreenToWorldOnCanvas(new Vector3(screenRect.xMax, screenRect.yMax), uiCam, canvasRt);
Vector3 p3 = ScreenToWorldOnCanvas(new Vector3(screenRect.xMin, screenRect.yMax), uiCam, canvasRt);
Gizmos.DrawLine(p0, p1);
Gizmos.DrawLine(p1, p2);
Gizmos.DrawLine(p2, p3);
Gizmos.DrawLine(p3, p0);
Vector3 p0 = Stw(new Vector3(screenRect.xMin, screenRect.yMin), uiCam, canvasRt);
Vector3 p1 = Stw(new Vector3(screenRect.xMax, screenRect.yMin), uiCam, canvasRt);
Vector3 p2 = Stw(new Vector3(screenRect.xMax, screenRect.yMax), uiCam, canvasRt);
Vector3 p3 = Stw(new Vector3(screenRect.xMin, screenRect.yMax), uiCam, canvasRt);
Gizmos.DrawLine(p0, p1); Gizmos.DrawLine(p1, p2);
Gizmos.DrawLine(p2, p3); Gizmos.DrawLine(p3, p0);
}
private static Vector3 ScreenToWorldOnCanvas(Vector3 screenPoint, Camera uiCamera, RectTransform canvasRt)
private static Vector3 Stw(Vector3 sp, Camera cam, RectTransform rt)
{
RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRt, (Vector2)screenPoint, uiCamera, out Vector3 wp);
RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, (Vector2)sp, cam, out Vector3 wp);
return wp;
}
#endif
#endregion
+91
View File
@@ -0,0 +1,91 @@
using System.Collections.Generic;
using UnityEngine;
namespace XericUI.Form
{
/// <summary>
/// 任务栏控制器 — 监听 UIFormManager 的窗口创建/销毁/状态变更事件,
/// 动态创建/销毁 UITaskBarItem 图标。
/// </summary>
public class UITaskBar : MonoBehaviour
{
[Header("绑定")]
[SerializeField, Tooltip("窗口管理器引用")]
private UIFormManager m_Manager;
[SerializeField, Tooltip("任务栏图标项预制体")]
private UITaskBarItem m_ItemPrefab;
[SerializeField, Tooltip("图标项的父容器")]
private Transform m_ItemsContainer;
private readonly Dictionary<string, UITaskBarItem> _items = new Dictionary<string, UITaskBarItem>();
#region 生命周期
private void Start()
{
if (m_Manager == null)
{
Debug.LogError("[UITaskBar] 未绑定 UIFormManager!", this);
return;
}
m_Manager.OnWindowCreated += OnWindowCreated;
m_Manager.OnWindowDestroyed += OnWindowDestroyed;
m_Manager.OnWindowStateChanged += OnWindowStateChanged;
}
private void OnDestroy()
{
if (m_Manager != null)
{
m_Manager.OnWindowCreated -= OnWindowCreated;
m_Manager.OnWindowDestroyed -= OnWindowDestroyed;
m_Manager.OnWindowStateChanged -= OnWindowStateChanged;
}
foreach (var item in _items.Values)
{
if (item != null)
Destroy(item.gameObject);
}
_items.Clear();
}
#endregion
#region 事件回调
private void OnWindowCreated(WindowEntry entry)
{
if (entry == null || !entry.ShowInTaskBar) return;
if (m_ItemPrefab == null || m_ItemsContainer == null) return;
var item = Instantiate(m_ItemPrefab, m_ItemsContainer);
item.Bind(entry, m_Manager);
_items[entry.Id] = item;
}
private void OnWindowDestroyed(WindowEntry entry)
{
if (entry == null) return;
if (_items.TryGetValue(entry.Id, out var item))
{
if (item != null)
Destroy(item.gameObject);
_items.Remove(entry.Id);
}
}
private void OnWindowStateChanged(WindowEntry entry)
{
if (entry == null) return;
// 状态变更时触发 Repaint(进度、高亮等由 Item 自己的 Update 处理)
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08764fa4decbd8b4b9f16f7236d36baf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+108
View File
@@ -0,0 +1,108 @@
using UnityEngine;
using UnityEngine.UI;
namespace XericUI.Form
{
/// <summary>
/// 任务栏图标项 — 显示单个窗口的图标、标题、进度条和聚焦高亮。
/// 点击时通过 UIFormManager 切换窗口显示/隐藏并置顶。
/// </summary>
public class UITaskBarItem : MonoBehaviour
{
[Header("控件引用")]
[SerializeField, Tooltip("窗口图标")]
private Image m_Icon;
[SerializeField, Tooltip("窗口标题文本")]
private Text m_TitleText;
[SerializeField, Tooltip("进度条 Image(fillAmount)")]
private Image m_ProgressBar;
[SerializeField, Tooltip("聚焦高亮 Image")]
private Image m_Highlight;
[SerializeField, Tooltip("按钮组件")]
private Button m_Button;
private WindowEntry _entry;
private UIFormManager _manager;
public WindowEntry Entry => _entry;
#region 初始化
public void Bind(WindowEntry entry, UIFormManager manager)
{
_entry = entry;
_manager = manager;
if (m_Icon != null && entry.Icon != null)
m_Icon.sprite = entry.Icon;
if (m_Icon != null)
m_Icon.color = entry.TintColor;
if (m_TitleText != null)
m_TitleText.text = entry.Title;
if (m_Button != null)
m_Button.onClick.AddListener(OnClick);
RefreshHighlight();
RefreshProgress();
}
private void OnDestroy()
{
if (m_Button != null)
m_Button.onClick.RemoveListener(OnClick);
}
#endregion
#region 交互
private void OnClick()
{
if (_entry == null || _manager == null) return;
_manager.ToggleWindow(_entry.Id);
_manager.MoveToFront(_entry.Id);
}
#endregion
#region 每帧同步
private void Update()
{
if (_entry == null) return;
if (m_TitleText != null && m_TitleText.text != _entry.Title)
m_TitleText.text = _entry.Title;
if (m_Icon != null && m_Icon.color != _entry.TintColor)
m_Icon.color = _entry.TintColor;
RefreshProgress();
RefreshHighlight();
}
private void RefreshProgress()
{
if (m_ProgressBar == null) return;
m_ProgressBar.fillAmount = _entry.Progress;
m_ProgressBar.gameObject.SetActive(_entry.ShowProgress && _entry.Progress > 0f);
}
private void RefreshHighlight()
{
if (m_Highlight == null || _manager == null) return;
var focused = _manager.GetFocusedEntry();
m_Highlight.gameObject.SetActive(focused == _entry);
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf298c8b67bb4d345a69728112b72229
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+35 -7
View File
@@ -12,21 +12,23 @@ namespace XericUI.Form
{
/// <summary>
/// 窗口标题栏 — 独立子组件,挂载在 UIFormController 的子对象上。
/// Start 时向上查找父级 UIFormController,绑定按钮事件到其公开 API。
/// 与窗口完全解耦:Controller 不关心标题栏是否存在。
/// Start 时向上查找父级 UIFormController,绑定按钮/Toggle 事件到其公开 API。
/// </summary>
[AddComponentMenu("Xeric UI Vessel/Form/UI Title Bar")]
public class UITitleBar : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
[Header("控件引用")]
[SerializeField, Tooltip("窗口图标")]
private Image m_IconImage;
[SerializeField, Tooltip("标题文本")]
private UI_Text m_TitleText;
[SerializeField, Tooltip("关闭按钮")]
private Button m_CloseButton;
[SerializeField, Tooltip("最大化按钮")]
private Button m_MaximizeButton;
[SerializeField, Tooltip("最大化 Toggle(isOn=全屏)")]
private Toggle m_MaximizeToggle;
[SerializeField, Tooltip("最小化按钮")]
private Button m_MinimizeButton;
@@ -57,9 +59,25 @@ namespace XericUI.Form
{
if (_controller == null) return;
// 实时同步标题(可能由外部修改)
if (m_TitleText != null && m_TitleText.text != _controller.WindowTitle)
m_TitleText.text = _controller.WindowTitle;
// 同步最大化 Toggle 状态
if (m_MaximizeToggle != null)
{
bool maxed = _controller.IsMaximized;
if (m_MaximizeToggle.isOn != maxed)
m_MaximizeToggle.SetIsOnWithoutNotify(maxed);
}
// 同步图标
if (m_IconImage != null && _controller.Entry != null)
{
if (_controller.Entry.Icon != null && m_IconImage.sprite != _controller.Entry.Icon)
m_IconImage.sprite = _controller.Entry.Icon;
if (m_IconImage.color != _controller.Entry.TintColor)
m_IconImage.color = _controller.Entry.TintColor;
}
}
#region 事件绑定
@@ -69,8 +87,8 @@ namespace XericUI.Form
if (m_CloseButton != null)
m_CloseButton.onClick.AddListener(() => _controller.Close());
if (m_MaximizeButton != null)
m_MaximizeButton.onClick.AddListener(() => _controller.ToggleMaximize());
if (m_MaximizeToggle != null)
m_MaximizeToggle.onValueChanged.AddListener(_ => _controller.ToggleMaximize());
if (m_MinimizeButton != null)
m_MinimizeButton.onClick.AddListener(() => _controller.Minimize());
@@ -119,11 +137,21 @@ namespace XericUI.Form
if (m_TitleText != null)
m_TitleText.text = _controller.WindowTitle;
if (m_MaximizeToggle != null)
m_MaximizeToggle.isOn = _controller.IsMaximized;
if (m_AutoSnapToggle != null)
m_AutoSnapToggle.isOn = _controller.AutoSnapEnabled;
if (m_AutoHideToggle != null)
m_AutoHideToggle.isOn = _controller.AutoHideEnabled;
// 图标
if (m_IconImage != null && _controller.Entry != null && _controller.Entry.Icon != null)
{
m_IconImage.sprite = _controller.Entry.Icon;
m_IconImage.color = _controller.Entry.TintColor;
}
}
#endregion