feat(fix-system): 打孔带收藏夹改为固定3槽位布局
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using AibisDream.Kit;
|
||||
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
|
||||
{
|
||||
@@ -14,8 +14,8 @@ namespace AibisDream.FixSystem
|
||||
private const float PanelSlideDuration = 0.5f;
|
||||
|
||||
[Header("收藏夹 UI")]
|
||||
[Tooltip("列表项父节点(Vertical Layout 等)")]
|
||||
[SerializeField] private Transform punchTapeContent;
|
||||
[Tooltip("3 个固定槽位,按顺序放置打孔带")]
|
||||
[SerializeField] private RectTransform[] punchTapeSlots = new RectTransform[3];
|
||||
|
||||
[SerializeField] private GameObject punchTapeItemPrefab;
|
||||
|
||||
@@ -26,19 +26,28 @@ namespace AibisDream.FixSystem
|
||||
[SerializeField] private Vector2 panelHiddenAnchoredPos;
|
||||
[SerializeField] private Vector2 panelVisibleAnchoredPos;
|
||||
|
||||
private SimpleObjectPool<GameObject> _rowPool;
|
||||
private readonly Dictionary<string, GameObject> _rowByTapeKey = new();
|
||||
private readonly Dictionary<string, int> _slotIndexByKey = new();
|
||||
|
||||
private PunchTapeManager PunchTapeManager => PunchTapeManager.Instance;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (punchTapeContent == null)
|
||||
if (punchTapeSlots == null || punchTapeSlots.Length != 3)
|
||||
{
|
||||
Debug.LogError("[PunchTapeFavoritesPanel] 未指定 punchTapeContent。", this);
|
||||
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);
|
||||
@@ -58,53 +67,31 @@ namespace AibisDream.FixSystem
|
||||
|
||||
punchTapePanel?.DOKill();
|
||||
|
||||
DisposeRowPool();
|
||||
ClearAllRows();
|
||||
_rowByTapeKey.Clear();
|
||||
_slotIndexByKey.Clear();
|
||||
}
|
||||
|
||||
private void HandleCollectionChanged() => SyncListToData();
|
||||
|
||||
private void DisposeRowPool()
|
||||
private void ClearAllRows()
|
||||
{
|
||||
if (_rowPool == null)
|
||||
return;
|
||||
|
||||
_rowPool.Clear(Destroy);
|
||||
_rowPool = null;
|
||||
}
|
||||
|
||||
private void EnsureRowPool()
|
||||
{
|
||||
if (_rowPool != null)
|
||||
return;
|
||||
|
||||
_rowPool = new SimpleObjectPool<GameObject>(
|
||||
() =>
|
||||
{
|
||||
var go = Instantiate(punchTapeItemPrefab, punchTapeContent);
|
||||
go.SetActive(false);
|
||||
return go;
|
||||
},
|
||||
go =>
|
||||
{
|
||||
go.SetActive(false);
|
||||
go.transform.SetParent(punchTapeContent, false);
|
||||
});
|
||||
foreach (var kvp in _rowByTapeKey)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
Destroy(kvp.Value);
|
||||
}
|
||||
_rowByTapeKey.Clear();
|
||||
_slotIndexByKey.Clear();
|
||||
}
|
||||
|
||||
private void SyncListToData()
|
||||
{
|
||||
EnsureRowPool();
|
||||
|
||||
var favorites = PunchTapeManager.GetFavorites();
|
||||
RemoveRowsNotInFavorites(favorites);
|
||||
EnsureRowsForFavorites(favorites);
|
||||
}
|
||||
|
||||
private void RemoveRowsNotInFavorites(IReadOnlyList<PunchTape> favorites)
|
||||
{
|
||||
// 回收不在前 3 个的项
|
||||
var keepKeys = new HashSet<string>();
|
||||
for (var i = 0; i < favorites.Count; i++)
|
||||
for (var i = 0; i < favorites.Count && i < punchTapeSlots.Length; i++)
|
||||
keepKeys.Add(favorites[i].GetRowKey());
|
||||
|
||||
var staleKeys = new List<string>();
|
||||
@@ -113,35 +100,85 @@ namespace AibisDream.FixSystem
|
||||
if (!keepKeys.Contains(key))
|
||||
staleKeys.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in staleKeys)
|
||||
{
|
||||
if (_rowByTapeKey.TryGetValue(key, out var go) && go != null)
|
||||
_rowPool.Recycle(go);
|
||||
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 EnsureRowsForFavorites(IReadOnlyList<PunchTape> favorites)
|
||||
private void ClearSlot(int slotIndex)
|
||||
{
|
||||
for (var i = 0; i < favorites.Count; i++)
|
||||
{
|
||||
var tape = favorites[i];
|
||||
var key = tape.GetRowKey();
|
||||
var slot = punchTapeSlots[slotIndex];
|
||||
if (slot == null || slot.childCount == 0)
|
||||
return;
|
||||
|
||||
if (!_rowByTapeKey.TryGetValue(key, out var rowGo) || rowGo == null)
|
||||
// 槽位下应该只有一个打孔带子对象
|
||||
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)
|
||||
{
|
||||
rowGo = _rowPool.Allocate();
|
||||
rowGo.transform.SetParent(punchTapeContent, false);
|
||||
_rowByTapeKey[key] = rowGo;
|
||||
var key = item.GetPunchTapeData().GetRowKey();
|
||||
_rowByTapeKey.Remove(key);
|
||||
_slotIndexByKey.Remove(key);
|
||||
}
|
||||
|
||||
BindRow(rowGo, tape);
|
||||
rowGo.SetActive(true);
|
||||
rowGo.transform.SetSiblingIndex(i);
|
||||
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 static void BindRow(GameObject rowGo, PunchTape tape)
|
||||
{
|
||||
var interaction = rowGo.GetComponent<PunchTapeInteraction>();
|
||||
@@ -156,10 +193,30 @@ namespace AibisDream.FixSystem
|
||||
item.Setup(tape);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,18 +10,11 @@ namespace AibisDream.FixSystem
|
||||
public class PunchTapeInteraction : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler,
|
||||
IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
private Transform originalParent;
|
||||
public Transform OriginalParent => originalParent;
|
||||
private Canvas canvas;
|
||||
[HideInInspector]
|
||||
public bool isFavorited = false;
|
||||
|
||||
private RectTransform rectTransform;
|
||||
private Vector2 originalAnchorMin;
|
||||
private Vector2 originalAnchorMax;
|
||||
private Vector2 originalOffsetMin;
|
||||
private Vector2 originalOffsetMax;
|
||||
private Vector2 originalPivot;
|
||||
|
||||
private CursorMode cursorMode = CursorMode.Auto;
|
||||
|
||||
@@ -38,7 +31,6 @@ namespace AibisDream.FixSystem
|
||||
private void OnEnable()
|
||||
{
|
||||
canvas = GetComponentInParent<Canvas>();
|
||||
StoreOriginalTransform();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
@@ -50,16 +42,6 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void StoreOriginalTransform()
|
||||
{
|
||||
originalParent = transform.parent;
|
||||
originalAnchorMin = rectTransform.anchorMin;
|
||||
originalAnchorMax = rectTransform.anchorMax;
|
||||
originalOffsetMin = rectTransform.offsetMin;
|
||||
originalOffsetMax = rectTransform.offsetMax;
|
||||
originalPivot = rectTransform.pivot;
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
}
|
||||
@@ -78,6 +60,7 @@ namespace AibisDream.FixSystem
|
||||
if (!isFavorited || isLocked) return;
|
||||
|
||||
transform.SetParent(canvas.transform, true);
|
||||
transform.localScale = Vector3.one;
|
||||
transform.SetAsLastSibling();
|
||||
var cg = GetComponent<CanvasGroup>();
|
||||
if (cg != null) cg.blocksRaycasts = false;
|
||||
@@ -120,21 +103,15 @@ namespace AibisDream.FixSystem
|
||||
}
|
||||
|
||||
if (accepted)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
ReturnToOriginalPosition();
|
||||
}
|
||||
|
||||
public void ReturnToOriginalPosition()
|
||||
{
|
||||
isLocked = false;
|
||||
transform.rotation = Quaternion.Euler(0, 0, 0);
|
||||
transform.SetParent(originalParent, false);
|
||||
rectTransform.anchorMin = originalAnchorMin;
|
||||
rectTransform.anchorMax = originalAnchorMax;
|
||||
rectTransform.offsetMin = originalOffsetMin;
|
||||
rectTransform.offsetMax = originalOffsetMax;
|
||||
rectTransform.pivot = originalPivot;
|
||||
{
|
||||
var manager = PunchTapeManager.Instance;
|
||||
if (manager != null && manager.FavoritesPanel != null)
|
||||
manager.FavoritesPanel.ReturnToSlot(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetIsFavorited(bool favorited)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using UnityEngine;
|
||||
using TMPro;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.Localization.Components;
|
||||
using AibisDream.Framework;
|
||||
@@ -12,17 +11,13 @@ namespace AibisDream.FixSystem
|
||||
public class PunchTapeItem : MonoBehaviour
|
||||
{
|
||||
public Image clueImageUI;
|
||||
public TMP_Text categoryText;
|
||||
public LocalizeStringEvent nameText;
|
||||
public GameObject stateText;
|
||||
|
||||
[HideInInspector]
|
||||
public bool used = false;
|
||||
|
||||
private PunchTape punchTapeData;
|
||||
|
||||
public GameObject finished;
|
||||
|
||||
public void Setup(PunchTape punchTape)
|
||||
{
|
||||
punchTapeData = punchTape;
|
||||
@@ -41,11 +36,6 @@ namespace AibisDream.FixSystem
|
||||
if (clueImageUI != null && pt.used)
|
||||
clueImageUI.color = Color.grey;
|
||||
|
||||
if (stateText != null)
|
||||
stateText.SetActive(pt.used);
|
||||
|
||||
if (categoryText != null)
|
||||
categoryText.text = "分类:" + pt.Category;
|
||||
if (nameText != null)
|
||||
{
|
||||
nameText.StringReference.Arguments = new object[] { LocalizationKit.LocalizeParam(pt.ClueName) };
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace AibisDream.FixSystem
|
||||
public GameObject punchTapecluePrefab;
|
||||
|
||||
[SerializeField] private PunchTapeFavoritesPanel favoritesPanel;
|
||||
public PunchTapeFavoritesPanel FavoritesPanel => favoritesPanel;
|
||||
[SerializeField] private PunchTapePrinter punchTapePrinter;
|
||||
public PunchTapePrinter PunchTapePrinter => punchTapePrinter;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user