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 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(); 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(); } 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); } } }