using System; using System.Collections; using System.Collections.Generic; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.UI; namespace AppUI.View.Home.Main { [Serializable] public class TopBarButtonInfoTMP { public string Name; public Image Icon; [Tooltip("选中态图标,不填则保持当前 Sprite 仅切换颜色")] public Sprite selectedIconSprite; [Tooltip("未选中态图标,不填则保持当前 Sprite 仅切换颜色")] public Sprite unselectedIconSprite; public TMP_Text text; public bool selected; public DeviceMode deviceMode; public GameObject container; [Tooltip("背景滑动对齐目标(如 Archery / Gun 按钮节点),不填则使用 container")] public RectTransform tabRect; } public class HomeViewTopBar : MonoBehaviour { public List topBarButtonInfos; public UnityEngine.Events.UnityEvent onChangeTypeEvent; [Header("背景滑动")] [SerializeField] RectTransform btnBg; [SerializeField] float btnBgMoveDuration = 0.25f; [SerializeField] Ease btnBgMoveEase = Ease.OutCubic; [Tooltip("切换时是否同步缩放到 tab 节点尺寸")] [SerializeField] bool matchTabSize; [Header("背景 Padding")] [Tooltip("位置偏移,相对标签本地坐标(X 向右,Y 向上)")] [SerializeField] Vector2 btnBgPositionOffset; [Tooltip("在标签尺寸基础上向四周扩展(仅 Match Tab Size 开启时生效)")] [SerializeField] float btnBgPaddingLeft; [SerializeField] float btnBgPaddingRight; [SerializeField] float btnBgPaddingTop; [SerializeField] float btnBgPaddingBottom; static readonly Color32 SelectedIconColor = new Color32(0, 0, 0, 255); static readonly Color32 SelectedTextColor = new Color32(48, 57, 57, 255); static readonly Color32 UnselectedIconColor = new Color32(255, 255, 255, 255); static readonly Color32 UnselectedTextColor = new Color32(141, 150, 151, 255); string _saveKey = "DeviceModeKey"; int _value; int _valueDefault; Tween _btnBgTween; void Start() { int deviceValue = GetDeviceModeValue(); if (topBarButtonInfos != null && topBarButtonInfos.Count > 0) deviceValue = Mathf.Clamp(deviceValue, 0, topBarButtonInfos.Count - 1); GlobalData.MyDeviceMode = (DeviceMode)deviceValue; Debug.Log(" GlobalData.MyDeviceMode :" + GlobalData.MyDeviceMode); onChangeTypeEvent?.Invoke(GlobalData.MyDeviceMode); UpdateTabVisuals(deviceValue); StartCoroutine(InitBtnBgPositionAfterLayout(deviceValue)); } IEnumerator InitBtnBgPositionAfterLayout(int index) { yield return null; Canvas.ForceUpdateCanvases(); MoveBtnBgToTab(index, false); } void OnDestroy() { _btnBgTween?.Kill(); if (btnBg != null) btnBg.DOKill(); } public TopBarButtonInfoTMP GetCurrentTopBarButtonInfo() { return topBarButtonInfos[(int)GlobalData.MyDeviceMode]; } public void onChangeType(int index) { if (topBarButtonInfos == null || index < 0 || index >= topBarButtonInfos.Count) return; UpdateTabVisuals(index); MoveBtnBgToTab(index, true); SetDeviceModeValue(index); GlobalData.MyDeviceMode = (DeviceMode)index; Debug.Log("Set GlobalData.MyDeviceMode :" + GlobalData.MyDeviceMode); onChangeTypeEvent?.Invoke(GlobalData.MyDeviceMode); } void UpdateTabVisuals(int selectedIndex) { if (topBarButtonInfos == null) return; for (int i = 0; i < topBarButtonInfos.Count; i++) { TopBarButtonInfoTMP topBarButtonInfo = topBarButtonInfos[i]; bool isSelected = i == selectedIndex; topBarButtonInfo.selected = isSelected; ApplyIconVisual(topBarButtonInfo, isSelected); if (topBarButtonInfo.text == null) continue; topBarButtonInfo.text.color = isSelected ? SelectedTextColor : UnselectedTextColor; } } static void ApplyIconVisual(TopBarButtonInfoTMP info, bool isSelected) { if (info?.Icon == null) return; info.Icon.color = isSelected ? SelectedIconColor : UnselectedIconColor; Sprite targetSprite = isSelected ? info.selectedIconSprite : info.unselectedIconSprite; if (targetSprite != null) info.Icon.sprite = targetSprite; } void MoveBtnBgToTab(int index, bool animate) { if (btnBg == null || topBarButtonInfos == null || index < 0 || index >= topBarButtonInfos.Count) return; RectTransform target = GetTabBgTarget(topBarButtonInfos[index]); if (target == null) return; Canvas.ForceUpdateCanvases(); CalcBtnBgTarget(target, out Vector3 targetPos, out Vector2 targetSize); _btnBgTween?.Kill(); btnBg.DOKill(); if (animate) { Sequence seq = DOTween.Sequence(); seq.Join(btnBg.DOMove(targetPos, btnBgMoveDuration).SetEase(btnBgMoveEase)); if (matchTabSize) seq.Join(btnBg.DOSizeDelta(targetSize, btnBgMoveDuration).SetEase(btnBgMoveEase)); _btnBgTween = seq; } else { btnBg.position = targetPos; if (matchTabSize) btnBg.sizeDelta = targetSize; } } void CalcBtnBgTarget(RectTransform target, out Vector3 worldPos, out Vector2 size) { Vector2 localCenter = target.rect.center; localCenter.x += btnBgPositionOffset.x + (btnBgPaddingRight - btnBgPaddingLeft) * 0.5f; localCenter.y += btnBgPositionOffset.y + (btnBgPaddingTop - btnBgPaddingBottom) * 0.5f; Vector3 targetWorldCenter = target.TransformPoint(localCenter); worldPos = new Vector3(targetWorldCenter.x, targetWorldCenter.y, btnBg.position.z); size = new Vector2( target.rect.width + btnBgPaddingLeft + btnBgPaddingRight, target.rect.height + btnBgPaddingTop + btnBgPaddingBottom); } RectTransform GetTabBgTarget(TopBarButtonInfoTMP info) { if (info == null) return null; if (info.tabRect != null) return info.tabRect; if (info.container != null) return info.container.GetComponent(); return null; } public int GetDeviceModeValue() { _value = PlayerPrefs.GetInt(_saveKey, _valueDefault); return _value; } public void SetDeviceModeValue(int value) { _value = value; PlayerPrefs.SetInt(_saveKey, _value); PlayerPrefs.Save(); } } }