Files

263 lines
7.4 KiB
C#

using System;
using BriarQueen.Framework.Managers.Player.Data;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace BriarQueen.UI.Codex
{
[ExecuteAlways]
public class CodexEntryButton : MonoBehaviour
{
private const float MIN_WIDTH = 300f;
private const float MAX_WIDTH = 400f;
private const float FIXED_HEIGHT = 70f;
[SerializeField]
private Button _button;
[SerializeField]
private Image _buttonBackgroundImage;
[SerializeField]
private Sprite _defaultSprite;
[SerializeField]
private Sprite _selectedSprite;
[SerializeField]
private TextMeshProUGUI _label;
[SerializeField]
private float _leftPadding = 24f;
[SerializeField]
private float _rightPadding = 24f;
[SerializeField]
private float _extraWidthSafety = 20f;
[SerializeField]
private float _autoSizeMinFontSize = 18f;
[SerializeField]
private bool _debugInEditor = true;
private bool _defaultSpriteCachedFromImage;
private string _lastText;
private float _lastWidth = -1f;
public CodexEntrySo Entry { get; private set; }
private void Awake()
{
ResolveReferences();
CacheDefaultSpriteFromImageIfNeeded();
AddButtonListener();
RefreshVisuals();
ApplyCurrentBackground(false);
}
#if UNITY_EDITOR
private void Update()
{
if (Application.isPlaying)
return;
if (_label == null)
return;
if (_lastText != _label.text)
RefreshVisuals();
}
#endif
private void OnEnable()
{
ResolveReferences();
CacheDefaultSpriteFromImageIfNeeded();
AddButtonListener();
RefreshVisuals();
ApplyCurrentBackground(false);
}
private void OnDestroy()
{
RemoveButtonListener();
}
private void OnValidate()
{
ResolveReferences();
CacheDefaultSpriteFromImageIfNeeded();
RefreshVisuals();
#if UNITY_EDITOR
if (!Application.isPlaying)
ApplyCurrentBackground(false);
#endif
}
public event Action<CodexEntrySo> OnEntryClicked;
public void Initialize(CodexEntrySo entry)
{
Entry = entry;
ResolveReferences();
CacheDefaultSpriteFromImageIfNeeded();
if (_label != null)
_label.text = entry != null ? entry.Title : string.Empty;
RefreshVisuals();
SetSelected(false);
}
public void SetSelected(bool selected)
{
ApplyCurrentBackground(selected);
}
private void HandleClicked()
{
if (Entry == null)
return;
OnEntryClicked?.Invoke(Entry);
}
private void RefreshVisuals()
{
ResizeToFitLabel();
}
private void ResizeToFitLabel()
{
if (_button == null || _label == null)
return;
var buttonRect = _button.GetComponent<RectTransform>();
var labelRect = _label.rectTransform;
if (buttonRect == null || labelRect == null)
return;
StretchLabelToButton(labelRect);
_label.margin = new Vector4(_leftPadding, _label.margin.y, _rightPadding, _label.margin.w);
_label.textWrappingMode = TextWrappingModes.NoWrap;
_label.overflowMode = TextOverflowModes.Overflow;
_label.enableAutoSizing = false;
_label.ForceMeshUpdate();
var preferredSize = _label.GetPreferredValues(_label.text, Mathf.Infinity, Mathf.Infinity);
var desiredWidth = preferredSize.x + _leftPadding + _rightPadding + _extraWidthSafety;
var targetWidth = Mathf.Clamp(desiredWidth, MIN_WIDTH, MAX_WIDTH);
var needsAutoSizing = desiredWidth > MAX_WIDTH;
if (needsAutoSizing)
{
_label.enableAutoSizing = true;
_label.fontSizeMin = _autoSizeMinFontSize;
_label.fontSizeMax = _label.fontSize;
_label.overflowMode = TextOverflowModes.Truncate;
}
buttonRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, targetWidth);
buttonRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, FIXED_HEIGHT);
#if UNITY_EDITOR
if (_debugInEditor && (!Mathf.Approximately(targetWidth, _lastWidth) || _lastText != _label.text))
Debug.Log(
$"[CodexEntryButton] '{_label.text}'\n" +
$"Preferred: {preferredSize.x:F1} | Desired: {desiredWidth:F1} | Final: {targetWidth:F1}\n" +
$"AutoSize: {needsAutoSizing}",
this);
#endif
_lastText = _label.text;
_lastWidth = targetWidth;
LayoutRebuilder.ForceRebuildLayoutImmediate(buttonRect);
_label.ForceMeshUpdate();
}
private void StretchLabelToButton(RectTransform labelRect)
{
labelRect.anchorMin = new Vector2(0f, 0f);
labelRect.anchorMax = new Vector2(1f, 1f);
labelRect.offsetMin = Vector2.zero;
labelRect.offsetMax = Vector2.zero;
labelRect.pivot = new Vector2(0.5f, 0.5f);
}
private void ResolveReferences()
{
if (_buttonBackgroundImage == null && _button != null)
_buttonBackgroundImage = _button.GetComponent<Image>();
}
private void CacheDefaultSpriteFromImageIfNeeded()
{
if (_buttonBackgroundImage == null)
return;
if (_defaultSprite != null)
return;
if (_buttonBackgroundImage.sprite == null)
return;
_defaultSprite = _buttonBackgroundImage.sprite;
_defaultSpriteCachedFromImage = true;
}
private void ApplyCurrentBackground(bool selected)
{
if (_buttonBackgroundImage == null)
return;
Sprite targetSprite = null;
if (selected && _selectedSprite != null)
targetSprite = _selectedSprite;
else if (_defaultSprite != null)
targetSprite = _defaultSprite;
else if (_buttonBackgroundImage.sprite != null) targetSprite = _buttonBackgroundImage.sprite;
if (targetSprite != null)
{
_buttonBackgroundImage.enabled = true;
_buttonBackgroundImage.sprite = targetSprite;
}
else
{
Debug.LogWarning(
"[CodexEntryButton] No default background sprite is available. " +
"Assign _defaultSprite in the inspector or ensure the Image has a sprite on the prefab.",
this);
}
}
private void AddButtonListener()
{
if (_button == null)
return;
_button.onClick.RemoveListener(HandleClicked);
_button.onClick.AddListener(HandleClicked);
}
private void RemoveButtonListener()
{
if (_button == null)
return;
_button.onClick.RemoveListener(HandleClicked);
}
}
}