using System.Collections.Generic; using DG.Tweening; using UnityEngine; /// /// 打孔带收藏夹视图。侧栏( 上的 RectTransform)做滑入滑出; /// 可选的 仅显隐,与侧栏同开同关。数据来自 。 /// 固定 3 个槽位,打孔带按顺序放入槽位。 /// namespace AibisDream.FixSystem { public class PunchTapeFavoritesPanel : MonoBehaviour { private const float PanelSlideDuration = 0.5f; [Header("收藏夹 UI")] [Tooltip("3 个固定槽位,按顺序放置打孔带")] [SerializeField] private RectTransform[] punchTapeSlots = new RectTransform[3]; [SerializeField] private GameObject punchTapeItemPrefab; [Tooltip("配套列表面板")] [SerializeField] private RectTransform punchTapePanel; [Header("定位信息")] [SerializeField] private Vector2 panelHiddenAnchoredPos; [SerializeField] private Vector2 panelVisibleAnchoredPos; private readonly Dictionary _rowByTapeKey = new(); private readonly Dictionary _slotIndexByKey = new(); private PunchTapeManager PunchTapeManager => PunchTapeManager.Instance; private void Start() { if (punchTapeSlots == null || punchTapeSlots.Length != 3) { Debug.LogError("[PunchTapeFavoritesPanel] 必须指定 3 个 punchTapeSlots。", this); enabled = false; return; } for (var i = 0; i < punchTapeSlots.Length; i++) { if (punchTapeSlots[i] == null) { Debug.LogError($"[PunchTapeFavoritesPanel] punchTapeSlots[{i}] 未指定。", this); enabled = false; return; } } if (punchTapeItemPrefab == null) { Debug.LogError("[PunchTapeFavoritesPanel] 未指定 punchTapeItemPrefab。", this); enabled = false; return; } PunchTapeManager.OnCollectionChanged += HandleCollectionChanged; ApplyClosedLayout(); SyncListToData(); } private void OnDestroy() { if (PunchTapeManager != null) PunchTapeManager.OnCollectionChanged -= HandleCollectionChanged; punchTapePanel?.DOKill(); ClearAllRows(); _rowByTapeKey.Clear(); _slotIndexByKey.Clear(); } private void HandleCollectionChanged() => SyncListToData(); private void ClearAllRows() { foreach (var kvp in _rowByTapeKey) { if (kvp.Value != null) Destroy(kvp.Value); } _rowByTapeKey.Clear(); _slotIndexByKey.Clear(); } private void SyncListToData() { var favorites = PunchTapeManager.GetFavorites(); // 回收不在前 3 个的项 var keepKeys = new HashSet(); for (var i = 0; i < favorites.Count && i < punchTapeSlots.Length; i++) keepKeys.Add(favorites[i].GetRowKey()); var staleKeys = new List(); foreach (var key in _rowByTapeKey.Keys) { if (!keepKeys.Contains(key)) staleKeys.Add(key); } foreach (var key in staleKeys) { if (_rowByTapeKey.TryGetValue(key, out var go) && go != null) Destroy(go); _rowByTapeKey.Remove(key); } // 确保前 3 个槽位有对应项 for (var i = 0; i < punchTapeSlots.Length; i++) { if (i < favorites.Count) { var tape = favorites[i]; var key = tape.GetRowKey(); _slotIndexByKey[key] = i; // 如果该 key 已经在别的槽位,先移过来 if (_rowByTapeKey.TryGetValue(key, out var existingGo) && existingGo != null) { existingGo.transform.SetParent(punchTapeSlots[i], false); ResetRowRectTransform(existingGo); existingGo.transform.SetSiblingIndex(i); } else { var rowGo = Instantiate(punchTapeItemPrefab, punchTapeSlots[i], false); ResetRowRectTransform(rowGo); _rowByTapeKey[key] = rowGo; BindRow(rowGo, tape); rowGo.SetActive(true); } } else { // 清空该槽位上不再需要的项 ClearSlot(i); } } } private void ClearSlot(int slotIndex) { var slot = punchTapeSlots[slotIndex]; if (slot == null || slot.childCount == 0) return; // 槽位下应该只有一个打孔带子对象 for (var i = slot.childCount - 1; i >= 0; i--) { var child = slot.GetChild(i).gameObject; if (child == null) continue; var item = child.GetComponent(); if (item != null && item.GetPunchTapeData() != null) { var key = item.GetPunchTapeData().GetRowKey(); _rowByTapeKey.Remove(key); _slotIndexByKey.Remove(key); } Destroy(child); } } private static void ResetRowRectTransform(GameObject rowGo) { var rt = rowGo.GetComponent(); if (rt == null) return; rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one; rt.offsetMin = Vector2.zero; rt.offsetMax = Vector2.zero; rt.pivot = new Vector2(0.5f, 0.5f); rt.anchoredPosition = Vector2.zero; rt.localRotation = Quaternion.identity; rt.localScale = Vector3.one; } private void BindRow(GameObject rowGo, PunchTape tape) { var interaction = rowGo.GetComponent(); if (interaction != null) { interaction.SetIsLock(false); interaction.SetIsFavorited(true); } var item = rowGo.GetComponent(); if (item != null && PunchTapeManager.TryGetDefinition(tape, out var definition)) item.Setup(tape, definition); } /// /// 将拖拽回弹的打孔带重新放置到它所属的槽位中。 /// public void ReturnToSlot(PunchTapeInteraction interaction) { if (interaction == null) return; var item = interaction.GetComponent(); if (item == null) return; var tape = item.GetPunchTapeData(); if (tape == null) return; var key = tape.GetRowKey(); if (_slotIndexByKey.TryGetValue(key, out var slotIndex) && slotIndex >= 0 && slotIndex < punchTapeSlots.Length) { interaction.transform.SetParent(punchTapeSlots[slotIndex], false); ResetRowRectTransform(interaction.gameObject); } } private void ApplyClosedLayout() { punchTapePanel.anchoredPosition = panelHiddenAnchoredPos; punchTapePanel.gameObject.SetActive(false); } /// 侧栏滑入并显示; 若有则一并显示。 public void OpenPanel() { SyncListToData(); punchTapePanel.gameObject.SetActive(true); punchTapePanel.DOKill(); punchTapePanel.DOAnchorPos(panelVisibleAnchoredPos, PanelSlideDuration); } /// 侧栏滑出,结束后侧栏与 一并隐藏。 public void ClosePanel() { punchTapePanel.DOKill(); punchTapePanel .DOAnchorPos(panelHiddenAnchoredPos, PanelSlideDuration) .OnComplete(OnSidebarClosed); } private void OnSidebarClosed() { punchTapePanel.gameObject.SetActive(false); } } }