Commit pending working-tree changes (local edits, line endings)

This commit is contained in:
cthangover committed 2026-10-21 12:31:47 +02:00
1 parent 589ee46395
commit 65e632a66f
19 files changed
+1710 -1710

No files matched your search

+267 -267
View File
@@ -1,269 +1,269 @@
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.Characters;
using Cthangover.Core.Scenes;
using Cthangover.Core.UI;
using Cthangover.Core.UI.Theming;
using Godot;
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.Characters;
using Cthangover.Core.Scenes;
using Cthangover.Core.UI;
using Cthangover.Core.UI.Theming;
using Godot;
using Cthangover.Mods.Interface.Base.Lists;
using Cthangover.Mods.Interface.View;
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
/// and a right-aligned enemy panel.
/// </summary>
public partial class BattleCardPanel : ModWidget
{
private const int MAX_CARDS_ROW = 10;
private const float BASE_CARD_HEIGHT = 280f;
[Export] private Control content;
/// <summary>
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
/// </summary>
[Export] public string AlignType { get; set; } = "Left";
[Export] private float cellOffset = 10f;
/// <summary>
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
/// </summary>
public List<CharacterCardNode> CardList { get; } = new();
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
/// <summary>
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
/// </summary>
public bool UseRows { get; set; }
/// <summary>
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
/// to record the defeated character in <see cref="BattleSceneContext"/>.
/// </summary>
public event System.Action<CharacterCardNode> OnCardDead;
/// <summary>Builds the card grid container rows for one battle side.</summary>
protected override void Construct()
{
CthangoverTheme.Apply(this);
content ??= GetNodeOrNull<Control>("Content");
}
/// <summary>
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
/// the health-change handler. Returns the created card list.
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
/// </summary>
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
{
CardList.Clear();
activeTweens.Clear();
int idx = 0;
foreach (var card in cards)
{
CardList.Add(Create(card, isPlayer, cardScale));
idx++;
}
Redraw(animate: false);
return CardList;
}
/// <summary>
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
/// sets its scale, initializes it with card data, applies team colors, and hooks
/// the health-change event. Called by <see cref="Init"/> for each character.
/// </summary>
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
{
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
behaviour.EnsureConstructed();
if (content != null)
content.AddChild(behaviour);
else
AddChild(behaviour);
behaviour.Scale = new Vector2(cardScale, cardScale);
behaviour.Init(card);
behaviour.IsPlayer = isPlayer;
behaviour.SetTeamColors();
if (card != null)
card.Attributes.Health.OnChange += CheckHealthAttribute;
return behaviour;
}
private void CheckHealthAttribute(float value, float basevalue)
{
if (value > 0)
return;
foreach (var cardItem in CardList.ToList())
{
if (cardItem.Card.Attributes.Health.Value <= 0)
Dead(cardItem);
}
}
/// <summary>
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
/// </summary>
public void ClearAll()
{
foreach (var card in CardList.ToList())
{
if (GodotObject.IsInstanceValid(card))
card.QueueFree();
}
CardList.Clear();
activeTweens.Clear();
Redraw(animate: false);
}
/// <summary>
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
/// changes the sort order.
/// </summary>
public void Redraw(bool animate = true)
{
for (int i = 0; i < CardList.Count; i++)
{
var card = CardList[i];
if (card == null)
continue;
var (row, col) = GetRowCol(i);
SetCardPosition(card, i, row, col, animate);
}
}
private (int row, int col) GetRowCol(int index)
{
if (!UseRows || index < MAX_CARDS_ROW)
return (0, index);
if (index < MAX_CARDS_ROW * 2)
return (1, index - MAX_CARDS_ROW);
return (2, index - MAX_CARDS_ROW * 2);
}
private int CalcBaseZIndex(int row, int col)
{
return row * 20 + col;
}
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
{
var panelSize = Size;
float effectiveWidth = card.Size.X * card.Scale.X;
float effectiveHeight = card.Size.Y * card.Scale.Y;
float y = row * (effectiveHeight + cellOffset);
if (AlignType == "Left")
return new Vector2((cellOffset + effectiveWidth) * col, y);
else
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
}
/// <summary>
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
/// </summary>
public int GetCurrentRowCount(int enemyCount)
{
if (!UseRows)
return 1;
if (enemyCount <= MAX_CARDS_ROW)
return 1;
if (enemyCount <= MAX_CARDS_ROW * 2)
return 2;
return 3;
}
/// <summary>
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
/// </summary>
public int GetMaxCardsPerRow()
{
return MAX_CARDS_ROW;
}
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
{
if (card == null)
return;
var targetPos = CalcTargetPosition(card, row, col);
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
prevTween.Kill();
if (animate)
{
var tween = CreateTween();
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
tween.TweenProperty(card, "position", targetPos, 0.3f);
activeTweens[card] = tween;
}
else
{
card.Position = targetPos;
activeTweens.Remove(card);
}
var baseZ = CalcBaseZIndex(row, col);
card.SetBaseZIndex(baseZ);
if (content != null)
content.MoveChild(card, index);
else
MoveChild(card, index);
card.UpdateInfo();
}
/// <summary>
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
/// Triggered by the health-change handler when a character's health reaches zero.
/// </summary>
public void Dead(CharacterCardNode characterCardNode)
{
if (characterCardNode == null || characterCardNode.IsDead)
return;
characterCardNode.IsDead = true;
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
characterCardNode.AddChild(animator);
animator.StartAnimation(characterCardNode, () =>
{
OnCardDead?.Invoke(characterCardNode);
CardList.Remove(characterCardNode);
activeTweens.Remove(characterCardNode);
Redraw();
});
}
}
}
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// Container panel that manages a list of <see cref="CharacterCardNode"/> instances for one side
/// of the battle (player or enemy). Handles card creation, multi-row layout when enemy count exceeds
/// <c>MAX_CARDS_ROW</c> (10 per row), smooth animated repositioning, health-triggered death,
/// and the <see cref="CardDeathAnimation"/> dissolve sequence.
/// Two instances are created by <see cref="CardBattleCore.Init"/>: a left-aligned player panel
/// and a right-aligned enemy panel.
/// </summary>
public partial class BattleCardPanel : ModWidget
{
private const int MAX_CARDS_ROW = 10;
private const float BASE_CARD_HEIGHT = 280f;
[Export] private Control content;
/// <summary>
/// Controls card alignment within the panel. <c>"Left"</c> for the player side,
/// <c>"Right"</c> for the enemy side. Affects <see cref="CalcTargetPosition"/>.
/// </summary>
[Export] public string AlignType { get; set; } = "Left";
[Export] private float cellOffset = 10f;
/// <summary>
/// All character cards currently managed by this panel. Populated by <see cref="Init"/>
/// and modified by <see cref="Dead"/> (removal) and <see cref="ClearAll"/> (full clear).
/// </summary>
public List<CharacterCardNode> CardList { get; } = new();
private Dictionary<CharacterCardNode, Tween> activeTweens = new();
/// <summary>
/// When <c>true</c>, cards are laid out in multiple rows (up to 3) instead of a single row.
/// Set by <see cref="CardBattleCore.Start"/> when enemy count exceeds 6.
/// </summary>
public bool UseRows { get; set; }
/// <summary>
/// Fired when a card's death animation completes. <see cref="CardBattleCore"/> subscribes
/// to record the defeated character in <see cref="BattleSceneContext"/>.
/// </summary>
public event System.Action<CharacterCardNode> OnCardDead;
/// <summary>Builds the card grid container rows for one battle side.</summary>
protected override void Construct()
{
CthangoverTheme.Apply(this);
content ??= GetNodeOrNull<Control>("Content");
}
/// <summary>
/// Clears the current card list and creates new <see cref="CharacterCardNode"/> instances
/// for each <paramref name="cards"/> entry. Sets player/enemy team colors and subscribes
/// the health-change handler. Returns the created card list.
/// Called by <see cref="CardBattleCore.Start"/> with the full character roster for each side.
/// </summary>
public List<CharacterCardNode> Init(IEnumerable<Character> cards, bool isPlayer, float cardScale)
{
CardList.Clear();
activeTweens.Clear();
int idx = 0;
foreach (var card in cards)
{
CardList.Add(Create(card, isPlayer, cardScale));
idx++;
}
Redraw(animate: false);
return CardList;
}
/// <summary>
/// Instantiates a single <see cref="CharacterCardNode"/>, adds it to the content panel,
/// sets its scale, initializes it with card data, applies team colors, and hooks
/// the health-change event. Called by <see cref="Init"/> for each character.
/// </summary>
public CharacterCardNode Create(Character card, bool isPlayer, float cardScale)
{
var behaviour = (CharacterCardNode)TscnScenes.LoadAndBuild("scenes/character_card.tscn");
behaviour.EnsureConstructed();
if (content != null)
content.AddChild(behaviour);
else
AddChild(behaviour);
behaviour.Scale = new Vector2(cardScale, cardScale);
behaviour.Init(card);
behaviour.IsPlayer = isPlayer;
behaviour.SetTeamColors();
if (card != null)
card.Attributes.Health.OnChange += CheckHealthAttribute;
return behaviour;
}
private void CheckHealthAttribute(float value, float basevalue)
{
if (value > 0)
return;
foreach (var cardItem in CardList.ToList())
{
if (cardItem.Card.Attributes.Health.Value <= 0)
Dead(cardItem);
}
}
/// <summary>
/// Frees all card nodes from the scene tree, clears the card list, and triggers a redraw.
/// Called by <see cref="CardBattleCore.OnBattleCleared"/> when the battle scene is cleaned up.
/// </summary>
public void ClearAll()
{
foreach (var card in CardList.ToList())
{
if (GodotObject.IsInstanceValid(card))
card.QueueFree();
}
CardList.Clear();
activeTweens.Clear();
Redraw(animate: false);
}
/// <summary>
/// Recalculates and applies positions for all cards in the list. When <paramref name="animate"/>
/// is <c>true</c>, cards tween to their new positions with a cubic ease-out; otherwise they
/// snap instantly. Called after cards are added, removed, or when a selection/unselection
/// changes the sort order.
/// </summary>
public void Redraw(bool animate = true)
{
for (int i = 0; i < CardList.Count; i++)
{
var card = CardList[i];
if (card == null)
continue;
var (row, col) = GetRowCol(i);
SetCardPosition(card, i, row, col, animate);
}
}
private (int row, int col) GetRowCol(int index)
{
if (!UseRows || index < MAX_CARDS_ROW)
return (0, index);
if (index < MAX_CARDS_ROW * 2)
return (1, index - MAX_CARDS_ROW);
return (2, index - MAX_CARDS_ROW * 2);
}
private int CalcBaseZIndex(int row, int col)
{
return row * 20 + col;
}
private Vector2 CalcTargetPosition(CharacterCardNode card, int row, int col)
{
var panelSize = Size;
float effectiveWidth = card.Size.X * card.Scale.X;
float effectiveHeight = card.Size.Y * card.Scale.Y;
float y = row * (effectiveHeight + cellOffset);
if (AlignType == "Left")
return new Vector2((cellOffset + effectiveWidth) * col, y);
else
return new Vector2(panelSize.X - (cellOffset + effectiveWidth) * (col + 1), y);
}
/// <summary>
/// Calculates how many rows are needed to display <paramref name="enemyCount"/> enemies
/// when <see cref="UseRows"/> is enabled. Returns 1, 2, or 3.
/// Called by <see cref="CardBattleCore.CalculateCardScale"/> to determine maximum card scale.
/// </summary>
public int GetCurrentRowCount(int enemyCount)
{
if (!UseRows)
return 1;
if (enemyCount <= MAX_CARDS_ROW)
return 1;
if (enemyCount <= MAX_CARDS_ROW * 2)
return 2;
return 3;
}
/// <summary>
/// Returns the maximum cards per row constant (<c>MAX_CARDS_ROW = 10</c>).
/// Used by <see cref="CardBattleCore.CalculateCardScale"/> for scale calculations.
/// </summary>
public int GetMaxCardsPerRow()
{
return MAX_CARDS_ROW;
}
private void SetCardPosition(CharacterCardNode card, int index, int row, int col, bool animate)
{
if (card == null)
return;
var targetPos = CalcTargetPosition(card, row, col);
if (activeTweens.TryGetValue(card, out var prevTween) && prevTween != null && prevTween.IsValid())
prevTween.Kill();
if (animate)
{
var tween = CreateTween();
tween.SetEase(Tween.EaseType.Out).SetTrans(Tween.TransitionType.Cubic);
tween.TweenProperty(card, "position", targetPos, 0.3f);
activeTweens[card] = tween;
}
else
{
card.Position = targetPos;
activeTweens.Remove(card);
}
var baseZ = CalcBaseZIndex(row, col);
card.SetBaseZIndex(baseZ);
if (content != null)
content.MoveChild(card, index);
else
MoveChild(card, index);
card.UpdateInfo();
}
/// <summary>
/// Marks the card as dead and starts the <see cref="CardDeathAnimation"/> dissolve sequence.
/// Once the animation completes, the card is removed from <see cref="CardList"/>,
/// <see cref="OnCardDead"/> is fired, and the remaining cards are redrawn.
/// Triggered by the health-change handler when a character's health reaches zero.
/// </summary>
public void Dead(CharacterCardNode characterCardNode)
{
if (characterCardNode == null || characterCardNode.IsDead)
return;
characterCardNode.IsDead = true;
var animator = new Cthangover.CardBattle.UI.CardDeathAnimation();
characterCardNode.AddChild(animator);
animator.StartAnimation(characterCardNode, () =>
{
OnCardDead?.Invoke(characterCardNode);
CardList.Remove(characterCardNode);
activeTweens.Remove(characterCardNode);
Redraw();
});
}
}
}
+101 -101
View File
@@ -1,101 +1,101 @@
using System;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.UI;
using Godot;
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
/// </summary>
public partial class CardDeathAnimation : Node
{
private CharacterCardNode card;
private Vector2 center;
private Action onComplete;
private static Shader dissolveShader;
/// <summary>
/// Begins the death animation sequence. Calculates a target position at the viewport center
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
/// </summary>
public void StartAnimation(CharacterCardNode card, Action onComplete)
{
this.card = card;
this.onComplete = onComplete;
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
center = card.GetParent<Control>() is Control parent
? viewportCenter - parent.GlobalPosition
: viewportCenter - card.GlobalPosition + card.Position;
RunAnimation();
}
private void RunAnimation()
{
float duration = 0.8f;
Vector2 endPos = center;
StartFadeOutRecursive(card, duration);
if (dissolveShader == null)
dissolveShader = Shaders.Resolve("scene_transition");
if (dissolveShader != null)
{
foreach (var img in card.AllImages)
{
var mat = new ShaderMaterial { Shader = dissolveShader };
mat.SetShaderParameter("noise_scale", 5f);
mat.SetShaderParameter("smoothness", 0.25f);
mat.SetShaderParameter("distortion", 0.092f);
mat.SetShaderParameter("glow_intensity", 0f);
mat.SetShaderParameter("hue_shift", 0f);
mat.SetShaderParameter("invert_direction", false);
mat.SetShaderParameter("progress", 0f);
img.Material = mat;
var matTween = CreateTween();
matTween.TweenMethod(
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
0f, 1f, duration);
}
}
var tween = CreateTween();
tween.SetParallel(true);
tween.TweenProperty(card, "position", endPos, duration);
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
tween.TweenProperty(card, "modulate:a", 0f, duration);
tween.Finished += () =>
{
onComplete?.Invoke();
QueueFree();
};
}
private static void StartFadeOutRecursive(Node node, float duration)
{
foreach (Node child in node.GetChildren())
{
if (child is Label label)
{
var t = child.CreateTween();
t.TweenProperty(label, "modulate:a", 0f, duration);
}
StartFadeOutRecursive(child, duration);
}
}
}
}
using System;
using Cthangover.Core.Mods;
using Cthangover.Core.Mods.Resolvers;
using Cthangover.Core.UI;
using Godot;
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// Plays a death animation on a <see cref="CharacterCardNode"/> when its health reaches zero.
/// The card simultaneously fades out, shrinks toward the screen center, spins 360 degrees,
/// and applies a dissolve shader to all its <see cref="CharacterCardNode.AllImages"/> textures.
/// The dissolve shader (<c>"scene_transition"</c>) is resolved once via <see cref="ModManager"/>
/// and cached statically. Created and started by <see cref="BattleCardPanel.Dead"/>.
/// </summary>
public partial class CardDeathAnimation : Node
{
private CharacterCardNode card;
private Vector2 center;
private Action onComplete;
private static Shader dissolveShader;
/// <summary>
/// Begins the death animation sequence. Calculates a target position at the viewport center
/// (relative to the card's parent), starts fading all child <see cref="Label"/> nodes,
/// applies the dissolve shader material to all card images, and tweens position/rotation/alpha.
/// On completion, invokes <paramref name="onComplete"/> and frees this node.
/// </summary>
public void StartAnimation(CharacterCardNode card, Action onComplete)
{
this.card = card;
this.onComplete = onComplete;
var viewportCenter = card.GetViewport().GetVisibleRect().Size / 2;
center = card.GetParent<Control>() is Control parent
? viewportCenter - parent.GlobalPosition
: viewportCenter - card.GlobalPosition + card.Position;
RunAnimation();
}
private void RunAnimation()
{
float duration = 0.8f;
Vector2 endPos = center;
StartFadeOutRecursive(card, duration);
if (dissolveShader == null)
dissolveShader = Shaders.Resolve("scene_transition");
if (dissolveShader != null)
{
foreach (var img in card.AllImages)
{
var mat = new ShaderMaterial { Shader = dissolveShader };
mat.SetShaderParameter("noise_scale", 5f);
mat.SetShaderParameter("smoothness", 0.25f);
mat.SetShaderParameter("distortion", 0.092f);
mat.SetShaderParameter("glow_intensity", 0f);
mat.SetShaderParameter("hue_shift", 0f);
mat.SetShaderParameter("invert_direction", false);
mat.SetShaderParameter("progress", 0f);
img.Material = mat;
var matTween = CreateTween();
matTween.TweenMethod(
Callable.From<float>(t => mat.SetShaderParameter("progress", t)),
0f, 1f, duration);
}
}
var tween = CreateTween();
tween.SetParallel(true);
tween.TweenProperty(card, "position", endPos, duration);
tween.TweenProperty(card, "rotation", Mathf.DegToRad(360f), duration);
tween.TweenProperty(card, "modulate:a", 0f, duration);
tween.Finished += () =>
{
onComplete?.Invoke();
QueueFree();
};
}
private static void StartFadeOutRecursive(Node node, float duration)
{
foreach (Node child in node.GetChildren())
{
if (child is Label label)
{
var t = child.CreateTween();
t.TweenProperty(label, "modulate:a", 0f, duration);
}
StartFadeOutRecursive(child, duration);
}
}
}
}
+47 -47
View File
@@ -1,48 +1,48 @@
using Cthangover.Core.UI;
using Cthangover.Core.UI.Theming;
using Godot;
using Cthangover.Core.UI;
using Cthangover.Core.UI.Theming;
using Godot;
using Cthangover.Mods.Interface.Base.Lists;
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// A styled button widget that allows the player to manually end their turn.
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
/// </summary>
public partial class EndTurnButton : ModWidget
{
private Button _button;
/// <summary>
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
/// </summary>
public event System.Action OnPressed;
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
protected override void Construct()
{
CthangoverTheme.Apply(this);
_button = GetNode<Button>("BtnEndTurn");
CthangoverTheme.ApplyPrimary(_button);
Size = CustomMinimumSize;
_button.Pressed += RaisePressed;
}
private void RaisePressed() => OnPressed?.Invoke();
/// <summary>
/// Shows or hides the underlying <see cref="Button"/> control.
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
/// </summary>
public void SetVisible(bool visible)
{
if (_button != null)
_button.Visible = visible;
}
}
}
namespace Cthangover.CardBattle.UI
{
/// <summary>
/// A styled button widget that allows the player to manually end their turn.
/// Displayed during the player's turn and hidden during enemy turns and battle cleanup.
/// Fires <see cref="OnPressed"/> when clicked, which <see cref="CardBattleCore"/> binds
/// to <c>OnPlayerTurnEnd</c>. The button text is translated via the <c>"battle/end_turn"</c> key.
/// </summary>
public partial class EndTurnButton : ModWidget
{
private Button _button;
/// <summary>
/// Fired when the button is pressed. Subscribed by <see cref="CardBattleCore.OnEndTurnPressed"/>.
/// </summary>
public event System.Action OnPressed;
/// <summary>Builds the end-turn button and binds its pressed event.</summary>
protected override void Construct()
{
CthangoverTheme.Apply(this);
_button = GetNode<Button>("BtnEndTurn");
CthangoverTheme.ApplyPrimary(_button);
Size = CustomMinimumSize;
_button.Pressed += RaisePressed;
}
private void RaisePressed() => OnPressed?.Invoke();
/// <summary>
/// Shows or hides the underlying <see cref="Button"/> control.
/// Called by <see cref="CardBattleCore"/> during turn transitions and battle cleanup.
/// </summary>
public void SetVisible(bool visible)
{
if (_button != null)
_button.Visible = visible;
}
}
}