using System; using System.Collections.Generic; using TMPro; using UnityEngine; using UnityEngine.UI; namespace Ashwild.Settings { /// /// A "◄ value ►" control that cycles a list of preset values. Pure UI: it knows nothing about /// which setting it drives. The hosting panel calls once with the presets /// (each a value plus its display label) and the action to run when the user steps; afterwards /// the panel pushes the current value through . Because each preset /// carries its own label, a value like -1 can read as "Unlimited" rather than a number. /// [DisallowMultipleComponent] public class SettingStepperWidget : MonoBehaviour { #region Serialized Fields [Header("References")] [SerializeField] private Button previousButton; [SerializeField] private Button nextButton; [SerializeField] private TMP_Text valueLabel; #endregion #region State private IReadOnlyList<(int value, string label)> presets; private Action onChanged; private bool wrap; private int currentIndex; #endregion #region Public API /// /// Configures the stepper with its presets and wrap behaviour, and registers the action to /// run when the user steps. Call once (the action is not invoked by ). /// public void Init(IReadOnlyList<(int value, string label)> presets, bool wrap, Action onChanged) { if (presets == null || presets.Count == 0) { Debug.LogError($"[SettingStepperWidget] '{name}' was initialized with no presets.", this); return; } this.presets = presets; this.wrap = wrap; this.onChanged = onChanged; if (previousButton != null) { previousButton.onClick.RemoveListener(StepDown); previousButton.onClick.AddListener(StepDown); } if (nextButton != null) { nextButton.onClick.RemoveListener(StepUp); nextButton.onClick.AddListener(StepUp); } } /// /// Selects the preset matching the given value (falling back to the first) and shows its /// label, without invoking the apply action. /// public void SetValue(int value) { if (presets == null) return; currentIndex = IndexOfValue(value); UpdateLabel(); } #endregion #region Event Handlers private void StepDown() => Step(-1); private void StepUp() => Step(+1); #endregion #region Internal Helpers /// /// Moves the selection by one step (clamped or wrapped), shows it and runs the apply action. /// private void Step(int direction) { if (presets == null || presets.Count == 0) return; int count = presets.Count; int next = currentIndex + direction; if (wrap) next = (next % count + count) % count; else next = Mathf.Clamp(next, 0, count - 1); if (next == currentIndex) return; currentIndex = next; UpdateLabel(); onChanged?.Invoke(presets[currentIndex].value); } /// /// Returns the preset index whose value equals the given one, or 0 when none matches. /// private int IndexOfValue(int value) { for (int i = 0; i < presets.Count; i++) if (presets[i].value == value) return i; return 0; } /// /// Shows the current preset's label and disables arrows that would do nothing. /// private void UpdateLabel() { if (valueLabel != null) valueLabel.text = presets[currentIndex].label; if (!wrap) { if (previousButton != null) previousButton.interactable = currentIndex > 0; if (nextButton != null) nextButton.interactable = currentIndex < presets.Count - 1; } } #endregion } }