Files

249 lines
8.6 KiB
C#

using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
/// <summary>
/// 打孔带收藏夹视图。侧栏(<see cref="favoritesUI"/> 上的 RectTransform)做滑入滑出;
/// 可选的 <see cref="punchTapePanel"/> 仅显隐,与侧栏同开同关。数据来自 <see cref="PunchTapeManager"/>。
/// 固定 3 个槽位,打孔带按顺序放入槽位。
/// </summary>
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<string, GameObject> _rowByTapeKey = new();
private readonly Dictionary<string, int> _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<string>();
for (var i = 0; i < favorites.Count && i < punchTapeSlots.Length; i++)
keepKeys.Add(favorites[i].GetRowKey());
var staleKeys = new List<string>();
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<PunchTapeItem>();
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<RectTransform>();
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<PunchTapeInteraction>();
if (interaction != null)
{
interaction.SetIsLock(false);
interaction.SetIsFavorited(true);
}
var item = rowGo.GetComponent<PunchTapeItem>();
if (item != null && PunchTapeManager.TryGetDefinition(tape, out var definition))
item.Setup(tape, definition);
}
/// <summary>
/// 将拖拽回弹的打孔带重新放置到它所属的槽位中。
/// </summary>
public void ReturnToSlot(PunchTapeInteraction interaction)
{
if (interaction == null) return;
var item = interaction.GetComponent<PunchTapeItem>();
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);
}
/// <summary>侧栏滑入并显示;<see cref="punchTapePanel"/> 若有则一并显示。</summary>
public void OpenPanel()
{
SyncListToData();
punchTapePanel.gameObject.SetActive(true);
punchTapePanel.DOKill();
punchTapePanel.DOAnchorPos(panelVisibleAnchoredPos, PanelSlideDuration);
}
/// <summary>侧栏滑出,结束后侧栏与 <see cref="punchTapePanel"/> 一并隐藏。</summary>
public void ClosePanel()
{
punchTapePanel.DOKill();
punchTapePanel
.DOAnchorPos(panelHiddenAnchoredPos, PanelSlideDuration)
.OnComplete(OnSidebarClosed);
}
private void OnSidebarClosed()
{
punchTapePanel.gameObject.SetActive(false);
}
}
}