From 8d56feee807c525ccf34f91e972cbdb2d50f52cf Mon Sep 17 00:00:00 2001
From: KoalaTrapLord <265133236+KoalaTrapLord@users.noreply.github.com>
Date: Sat, 28 Mar 2026 19:55:17 -0400
Subject: [PATCH] feat(#140): add PhaseTrackerWidget and PhaseStepButton
---
.../UI/Battleground/PhaseStepButton.cs | 31 +++++++
.../UI/Battleground/PhaseTrackerWidget.cs | 80 +++++++++++++++++++
2 files changed, 111 insertions(+)
create mode 100644 unity-client/Assets/Scripts/UI/Battleground/PhaseStepButton.cs
create mode 100644 unity-client/Assets/Scripts/UI/Battleground/PhaseTrackerWidget.cs
diff --git a/unity-client/Assets/Scripts/UI/Battleground/PhaseStepButton.cs b/unity-client/Assets/Scripts/UI/Battleground/PhaseStepButton.cs
new file mode 100644
index 0000000..851ca40
--- /dev/null
+++ b/unity-client/Assets/Scripts/UI/Battleground/PhaseStepButton.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using UnityEngine.UI;
+using TMPro;
+
+namespace CommanderAILab.UI
+{
+ ///
+ /// A single phase pill in the PhaseTrackerWidget strip.
+ /// Changes color, font weight, and scale when activated.
+ ///
+ public class PhaseStepButton : MonoBehaviour
+ {
+ [SerializeField] private Image background;
+ [SerializeField] private TMP_Text label;
+
+ [Header("Colors")]
+ [SerializeField] private Color activeColor = new Color(1f, 0.85f, 0f); // amber/yellow
+ [SerializeField] private Color inactiveColor = new Color(0.2f, 0.2f, 0.2f); // dark grey
+
+ private Vector3 _baseScale;
+
+ private void Awake() => _baseScale = transform.localScale;
+
+ public void SetActive(bool active)
+ {
+ if (background) background.color = active ? activeColor : inactiveColor;
+ if (label) label.fontStyle = active ? TMPro.FontStyles.Bold : TMPro.FontStyles.Normal;
+ transform.localScale = active ? _baseScale * 1.1f : _baseScale;
+ }
+ }
+}
diff --git a/unity-client/Assets/Scripts/UI/Battleground/PhaseTrackerWidget.cs b/unity-client/Assets/Scripts/UI/Battleground/PhaseTrackerWidget.cs
new file mode 100644
index 0000000..304bfab
--- /dev/null
+++ b/unity-client/Assets/Scripts/UI/Battleground/PhaseTrackerWidget.cs
@@ -0,0 +1,80 @@
+using UnityEngine;
+using TMPro;
+
+namespace CommanderAILab.UI
+{
+ ///
+ /// Horizontal strip of 13 phase/step buttons in the BattlefieldCenter panel.
+ /// Subscribes to GameStateManager.OnPhaseChanged.
+ /// Test without backend: press Space in Play Mode (debug binding below).
+ ///
+ public class PhaseTrackerWidget : MonoBehaviour
+ {
+ public enum Phase
+ {
+ Untap = 0,
+ Upkeep,
+ Draw,
+ Main1,
+ BeginCombat,
+ DeclareAttackers,
+ DeclareBlockers,
+ FirstStrikeDamage,
+ Damage,
+ EndCombat,
+ Main2,
+ EndStep,
+ Cleanup
+ }
+
+ [SerializeField] private PhaseStepButton[] stepButtons; // 13 buttons, one per Phase
+ [SerializeField] private TMP_Text activePlayerLabel;
+ [SerializeField] private TMP_Text turnNumberLabel;
+
+ private Phase _currentPhase;
+
+ // ── Lifecycle ───────────────────────────────────────────
+ private void Start()
+ {
+ if (GameStateManager.Instance != null)
+ GameStateManager.Instance.OnPhaseChanged += SetPhase;
+
+ SetPhase(Phase.Untap, 0, 1);
+ }
+
+ private void OnDestroy()
+ {
+ if (GameStateManager.Instance != null)
+ GameStateManager.Instance.OnPhaseChanged -= SetPhase;
+ }
+
+ // ── Public API ─────────────────────────────────────────
+ public void SetPhase(Phase phase, int seatIndex, int turnNumber)
+ {
+ _currentPhase = phase;
+
+ for (int i = 0; i < stepButtons.Length; i++)
+ stepButtons[i]?.SetActive(i == (int)phase);
+
+ if (activePlayerLabel)
+ activePlayerLabel.text = seatIndex == 0 ? "Your Turn" : $"Seat {seatIndex}'s Turn";
+
+ if (turnNumberLabel)
+ turnNumberLabel.text = $"Turn {turnNumber}";
+ }
+
+ public Phase CurrentPhase => _currentPhase;
+
+ // ── Debug (editor only) ──────────────────────────────────
+#if UNITY_EDITOR
+ private void Update()
+ {
+ if (Input.GetKeyDown(KeyCode.Space))
+ {
+ int next = ((int)_currentPhase + 1) % System.Enum.GetValues(typeof(Phase)).Length;
+ SetPhase((Phase)next, 0, GameStateManager.Instance?.TurnNumber ?? 1);
+ }
+ }
+#endif
+ }
+}