Fix stop reset, piece selection visuals, add trajectory preview and menu button

Bugfixes:
- Stop now fully rebuilds piece visuals (fixes destroyed pieces not
  reappearing and lingering cargo indicators)
- Piece stock buttons use explicit selected/unselected styling instead
  of ambiguous toggle behavior (teal bg + bright border when selected)
- Color dots next to stock buttons match piece colors

Features:
- Clicking a placed piece highlights its start/end cells with piece color
- Back-to-menu button ("← Menu") in top-left returns to level selector
  with fade transition
- DetailPanel shows "Pion" name and has styled remove button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Samuel Bouchet 2026-04-10 23:21:38 +02:00
parent 43a3e97f28
commit 210be72100
3 changed files with 162 additions and 14 deletions

View file

@ -133,14 +133,35 @@ public partial class Main : Node2D
uiRoot.MouseFilter = Control.MouseFilterEnum.Ignore; uiRoot.MouseFilter = Control.MouseFilterEnum.Ignore;
_uiLayer.AddChild(uiRoot); _uiLayer.AddChild(uiRoot);
// Level title (top-left) // Level title bar (top-left)
var titleBar = new HBoxContainer();
titleBar.SetAnchorsPreset(Control.LayoutPreset.TopLeft);
titleBar.OffsetLeft = 12;
titleBar.OffsetTop = 8;
titleBar.AddThemeConstantOverride("separation", 12);
var backButton = new Button { Text = "← Menu", CustomMinimumSize = new Vector2(70, 28) };
backButton.AddThemeFontSizeOverride("font_size", 11);
var backStyle = new StyleBoxFlat
{
BgColor = new Color("#2A2A2E"),
BorderColor = new Color("#444448"),
BorderWidthBottom = 1, BorderWidthTop = 1, BorderWidthLeft = 1, BorderWidthRight = 1,
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
ContentMarginLeft = 8, ContentMarginRight = 8,
ContentMarginTop = 2, ContentMarginBottom = 2
};
backButton.AddThemeStyleboxOverride("normal", backStyle);
backButton.Pressed += OnBackToMenu;
titleBar.AddChild(backButton);
_levelTitle = new Label { Text = "CHESSISTICS" }; _levelTitle = new Label { Text = "CHESSISTICS" };
_levelTitle.SetAnchorsPreset(Control.LayoutPreset.TopLeft);
_levelTitle.OffsetLeft = 16;
_levelTitle.OffsetTop = 12;
_levelTitle.AddThemeFontSizeOverride("font_size", 20); _levelTitle.AddThemeFontSizeOverride("font_size", 20);
_levelTitle.MouseFilter = Control.MouseFilterEnum.Ignore; _levelTitle.MouseFilter = Control.MouseFilterEnum.Ignore;
uiRoot.AddChild(_levelTitle); titleBar.AddChild(_levelTitle);
uiRoot.AddChild(titleBar);
// --- Side Panel (anchored to right edge) --- // --- Side Panel (anchored to right edge) ---
_sidePanel = new PanelContainer(); _sidePanel = new PanelContainer();
@ -268,12 +289,23 @@ public partial class Main : Node2D
var snap = _sim.GetSnapshot(); var snap = _sim.GetSnapshot();
if (snap.Phase != SimPhase.Edit) return; if (snap.Phase != SimPhase.Edit) return;
_boardView.ClearHighlights();
var coords = new Coords(col, row); var coords = new Coords(col, row);
var piece = snap.Pieces.FirstOrDefault(p => p.StartCell == coords || p.EndCell == coords); var piece = snap.Pieces.FirstOrDefault(p => p.StartCell == coords || p.EndCell == coords);
if (piece != null) if (piece != null)
{
_detailPanel.ShowPiece(piece); _detailPanel.ShowPiece(piece);
// Highlight start and end cells to show trajectory
var pieceColor = PieceView.GetPieceColor(piece.Kind);
var highlightColor = new Color(pieceColor, 0.3f);
_boardView.HighlightCells([piece.StartCell, piece.EndCell], highlightColor);
}
else else
{
_detailPanel.Hide(); _detailPanel.Hide();
}
} }
// --- Level Management --- // --- Level Management ---
@ -491,13 +523,32 @@ public partial class Main : Node2D
_running = false; _running = false;
_simTimer.Stop(); _simTimer.Stop();
_sim.ProcessCommand(new StopSimulationCommand()); _sim.ProcessCommand(new StopSimulationCommand());
_eventAnimator.ResetPiecePositions(_sim.GetSnapshot());
// Full visual rebuild: clear everything and recreate from snapshot
_eventAnimator.ClearAll();
var snap = _sim.GetSnapshot();
foreach (var ps in snap.Pieces)
{
var pieceView = new PieceView();
pieceView.Setup(ps.Id, ps.Kind, ps.StartCell, ps.EndCell, _boardView);
_boardView.AddChild(pieceView);
var color = PieceView.GetPieceColor(ps.Kind);
var trajectView = new TrajectView();
trajectView.Setup(ps.Id,
_boardView.CoordsToPixel(ps.StartCell),
_boardView.CoordsToPixel(ps.EndCell),
color);
_boardView.AddChild(trajectView);
_eventAnimator.RegisterPiece(ps.Id, pieceView, trajectView);
}
_controlBar.UpdateForPhase(SimPhase.Edit); _controlBar.UpdateForPhase(SimPhase.Edit);
_controlBar.ResetTurn(); _controlBar.ResetTurn();
_metricsOverlay.Hide(); _metricsOverlay.Hide();
_inputMapper.SetSnapshot(_sim.GetSnapshot()); _inputMapper.SetSnapshot(snap);
// Reset objective panel
if (_currentLevel != null) if (_currentLevel != null)
_objectivePanel.Setup(_currentLevel.Demands); _objectivePanel.Setup(_currentLevel.Demands);
} }
@ -550,4 +601,18 @@ public partial class Main : Node2D
else else
ShowLevelSelect(); ShowLevelSelect();
} }
private void OnBackToMenu()
{
SfxManager.Instance?.PlayClick();
_running = false;
_simTimer.Stop();
_eventAnimator.ClearAll();
FadeOut(0.2f, () =>
{
ShowLevelSelect();
FadeIn(0.3f);
});
}
} }

View file

@ -1,5 +1,6 @@
using Godot; using Godot;
using Chessistics.Engine.Model; using Chessistics.Engine.Model;
using Chessistics.Scripts.Pieces;
namespace Chessistics.Scripts.UI; namespace Chessistics.Scripts.UI;
@ -14,17 +15,41 @@ public partial class DetailPanel : PanelContainer
public override void _Ready() public override void _Ready()
{ {
var style = new StyleBoxFlat
{
BgColor = new Color(0.14f, 0.14f, 0.16f, 0.95f),
BorderColor = new Color("#444448"),
BorderWidthTop = 1,
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
ContentMarginLeft = 12, ContentMarginRight = 12,
ContentMarginTop = 8, ContentMarginBottom = 8
};
AddThemeStyleboxOverride("panel", style);
var vbox = new VBoxContainer(); var vbox = new VBoxContainer();
vbox.AddThemeConstantOverride("separation", 4);
var title = new Label { Text = "DETAIL" }; var title = new Label { Text = "DETAIL" };
title.AddThemeFontSizeOverride("font_size", 14); title.AddThemeFontSizeOverride("font_size", 13);
title.AddThemeColorOverride("font_color", new Color("#B8942A"));
vbox.AddChild(title); vbox.AddChild(title);
_infoLabel = new Label { Text = "" }; _infoLabel = new Label { Text = "" };
_infoLabel.AddThemeFontSizeOverride("font_size", 11); _infoLabel.AddThemeFontSizeOverride("font_size", 11);
_infoLabel.AddThemeColorOverride("font_color", new Color("#CCCCCC"));
vbox.AddChild(_infoLabel); vbox.AddChild(_infoLabel);
_removeButton = new Button { Text = "Retirer" }; _removeButton = new Button { Text = "Retirer", CustomMinimumSize = new Vector2(80, 26) };
_removeButton.AddThemeFontSizeOverride("font_size", 11);
var btnStyle = new StyleBoxFlat
{
BgColor = new Color("#5A2A2A"),
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
ContentMarginLeft = 8, ContentMarginRight = 8,
ContentMarginTop = 2, ContentMarginBottom = 2
};
_removeButton.AddThemeStyleboxOverride("normal", btnStyle);
_removeButton.Pressed += () => EmitSignal(SignalName.RemoveRequested, _currentPieceId); _removeButton.Pressed += () => EmitSignal(SignalName.RemoveRequested, _currentPieceId);
vbox.AddChild(_removeButton); vbox.AddChild(_removeButton);
@ -37,6 +62,7 @@ public partial class DetailPanel : PanelContainer
_currentPieceId = piece.Id; _currentPieceId = piece.Id;
var kindName = piece.Kind switch var kindName = piece.Kind switch
{ {
PieceKind.Pawn => "Pion",
PieceKind.Rook => "Tour II", PieceKind.Rook => "Tour II",
PieceKind.Bishop => "Fou II", PieceKind.Bishop => "Fou II",
PieceKind.Knight => "Cavalier", PieceKind.Knight => "Cavalier",

View file

@ -2,6 +2,7 @@ using Godot;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Chessistics.Engine.Model; using Chessistics.Engine.Model;
using Chessistics.Scripts.Pieces;
namespace Chessistics.Scripts.UI; namespace Chessistics.Scripts.UI;
@ -15,6 +16,13 @@ public partial class PieceStockPanel : VBoxContainer
public PieceKind? SelectedKind => _selectedKind; public PieceKind? SelectedKind => _selectedKind;
private static readonly Color NormalBg = new("#2A2A2E");
private static readonly Color SelectedBg = new("#3D6B8E");
private static readonly Color HoverBg = new("#353538");
private static readonly Color DisabledBg = new("#1E1E20");
private static readonly Color BorderNormal = new("#444448");
private static readonly Color BorderSelected = new("#5A9ECC");
public void Setup(IReadOnlyList<PieceStock> stock) public void Setup(IReadOnlyList<PieceStock> stock)
{ {
foreach (var child in GetChildren()) foreach (var child in GetChildren())
@ -24,7 +32,7 @@ public partial class PieceStockPanel : VBoxContainer
var title = new Label { Text = "PIECES" }; var title = new Label { Text = "PIECES" };
title.AddThemeFontSizeOverride("font_size", 16); title.AddThemeFontSizeOverride("font_size", 16);
title.AddThemeColorOverride("font_color", new Color("#FFD700")); title.AddThemeColorOverride("font_color", new Color("#B8942A"));
AddChild(title); AddChild(title);
AddChild(new HSeparator()); AddChild(new HSeparator());
@ -32,16 +40,31 @@ public partial class PieceStockPanel : VBoxContainer
foreach (var entry in stock) foreach (var entry in stock)
{ {
var hbox = new HBoxContainer(); var hbox = new HBoxContainer();
hbox.AddThemeConstantOverride("separation", 8);
// Color dot matching piece color
var dot = new ColorRect
{
CustomMinimumSize = new Vector2(10, 10),
Size = new Vector2(10, 10),
Color = PieceView.GetPieceColor(entry.Kind),
MouseFilter = Control.MouseFilterEnum.Ignore
};
var dotCenter = new CenterContainer { CustomMinimumSize = new Vector2(14, 32) };
dotCenter.AddChild(dot);
hbox.AddChild(dotCenter);
var button = new Button var button = new Button
{ {
Text = GetPieceName(entry.Kind), Text = GetPieceName(entry.Kind),
CustomMinimumSize = new Vector2(120, 32), CustomMinimumSize = new Vector2(120, 32),
ToggleMode = true ToggleMode = false // We manage selection state ourselves
}; };
ApplyButtonStyle(button, false);
var countLabel = new Label { Text = $"x{entry.Count}" }; var countLabel = new Label { Text = $"x{entry.Count}" };
countLabel.AddThemeFontSizeOverride("font_size", 14); countLabel.AddThemeFontSizeOverride("font_size", 13);
countLabel.AddThemeColorOverride("font_color", new Color("#999999"));
var kind = entry.Kind; var kind = entry.Kind;
button.Pressed += () => OnPieceButtonPressed(kind); button.Pressed += () => OnPieceButtonPressed(kind);
@ -72,11 +95,45 @@ public partial class PieceStockPanel : VBoxContainer
{ {
foreach (var (k, (button, _, remaining)) in _entries) foreach (var (k, (button, _, remaining)) in _entries)
{ {
button.ButtonPressed = k == _selectedKind; bool selected = k == _selectedKind;
button.Disabled = remaining <= 0; button.Disabled = remaining <= 0;
ApplyButtonStyle(button, selected);
} }
} }
private static void ApplyButtonStyle(Button button, bool selected)
{
var bg = selected ? SelectedBg : NormalBg;
var border = selected ? BorderSelected : BorderNormal;
var normal = MakeStyle(bg, border);
var hover = MakeStyle(selected ? SelectedBg.Lightened(0.1f) : HoverBg, border);
var disabled = MakeStyle(DisabledBg, new Color("#2A2A2E"));
button.AddThemeStyleboxOverride("normal", normal);
button.AddThemeStyleboxOverride("hover", hover);
button.AddThemeStyleboxOverride("pressed", MakeStyle(bg.Darkened(0.15f), border));
button.AddThemeStyleboxOverride("disabled", disabled);
button.AddThemeFontSizeOverride("font_size", 12);
button.AddThemeColorOverride("font_color", selected ? Colors.White : new Color("#CCCCCC"));
button.AddThemeColorOverride("font_disabled_color", new Color("#555555"));
}
private static StyleBoxFlat MakeStyle(Color bg, Color border)
{
return new StyleBoxFlat
{
BgColor = bg,
BorderColor = border,
BorderWidthBottom = 1, BorderWidthTop = 1,
BorderWidthLeft = 1, BorderWidthRight = 1,
CornerRadiusTopLeft = 4, CornerRadiusTopRight = 4,
CornerRadiusBottomLeft = 4, CornerRadiusBottomRight = 4,
ContentMarginLeft = 8, ContentMarginRight = 8,
ContentMarginTop = 4, ContentMarginBottom = 4
};
}
public void UpdateCount(PieceKind kind, int remaining) public void UpdateCount(PieceKind kind, int remaining)
{ {
if (!_entries.TryGetValue(kind, out var entry)) return; if (!_entries.TryGetValue(kind, out var entry)) return;