ported from perforce
This commit is contained in:
597
DonkeysAndDroids.Godot/CardControl.cs
Normal file
597
DonkeysAndDroids.Godot/CardControl.cs
Normal file
@@ -0,0 +1,597 @@
|
||||
using Godot;
|
||||
using RobotAndDonkey.Game.Cards;
|
||||
using RobotAndDonkey.Game.Cards.Patches;
|
||||
using RobotAndDonkey.Game.Modifiers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RobotAndDonkey.Game;
|
||||
|
||||
public partial class CardControl : Control
|
||||
{
|
||||
public override void _Ready()
|
||||
{
|
||||
m_ArtTextureRect = GetNode<TextureRect>("%ArtTextureRect");
|
||||
m_ModifiersContainer = GetNode<HBoxContainer>("%ModifiersContainer");
|
||||
m_BorderPanel = GetNode<Panel>("%Frame");
|
||||
m_Background = GetNode<Panel>("%Background");
|
||||
m_Label = GetNode<Label>("%Label");
|
||||
|
||||
MouseEntered += OnMouseEntered;
|
||||
MouseExited += OnMouseExited;
|
||||
|
||||
PivotOffset = Size / 2;
|
||||
Scale = new(1, 1);
|
||||
Modulate = new(1, 1, 1);
|
||||
m_DesiredPosition = Position;
|
||||
UpdateState();
|
||||
SnapToDesired();
|
||||
|
||||
if (ModifierIconScene == null)
|
||||
GD.PushWarning($"{nameof(CardControl)} on node '{Name}' has no ModifierIconScene assigned. No modifier icons will be shown.");
|
||||
|
||||
if (m_Card == null)
|
||||
{
|
||||
m_Card = new DetoxiumPrime();
|
||||
m_Card.AddModifier(new UnreliableCardModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new EffectiveModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new RaceConditionModifier(EModifierDuration.Permanent), []);
|
||||
m_Card.AddModifier(new OptimizedModifier(EModifierDuration.Permanent), []);
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
UpdateAnimatedState(delta);
|
||||
}
|
||||
|
||||
private void OnMouseEntered()
|
||||
{
|
||||
if (m_Dragging || GetViewport().GuiIsDragging())
|
||||
return;
|
||||
|
||||
if (!CanDrag && !CanSelect || Disabled)
|
||||
return;
|
||||
|
||||
if (ShouldHoverSelect())
|
||||
Selected = true;
|
||||
|
||||
m_Hover = true;
|
||||
ZIndex = 1;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private bool ShouldHoverSelect()
|
||||
{
|
||||
if (!CanSelect || Disabled || m_CardRow == null)
|
||||
return false;
|
||||
|
||||
if (!m_CardRow.SupportsMultiSelect)
|
||||
return false;
|
||||
|
||||
if (!Input.IsMouseButtonPressed(MouseButton.Left))
|
||||
return false;
|
||||
|
||||
if (CardRow.IsCardPressActive)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnMouseExited()
|
||||
{
|
||||
m_PressedLeftDown = false;
|
||||
if (m_Dragging)
|
||||
return;
|
||||
|
||||
if (!CanDrag && !CanSelect || Disabled)
|
||||
return;
|
||||
|
||||
m_DropBefore = null;
|
||||
m_Hover = false;
|
||||
ZIndex = 0;
|
||||
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
private void UpdateState()
|
||||
{
|
||||
Vector2 newPivot = Size / 2;
|
||||
Vector2 newScale = new Vector2(1, 1);
|
||||
Color newModulate = new Color(1, 1, 1, 1);
|
||||
if (Selected)
|
||||
{
|
||||
newPivot.Y = Size.Y;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
}
|
||||
|
||||
if (m_Hover)
|
||||
{
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate = new(1.25f, 1.25f, 1.25f);
|
||||
}
|
||||
|
||||
if (m_DropBefore == false)
|
||||
{
|
||||
newPivot.X = Size.X;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate.A = 0.5f;
|
||||
}
|
||||
else if (m_DropBefore == true)
|
||||
{
|
||||
newPivot.X = 0;
|
||||
newScale = new(1.25f, 1.25f);
|
||||
newModulate.A = 0.5f;
|
||||
}
|
||||
if (m_Dragging)
|
||||
{
|
||||
newModulate.A = 0.25f;
|
||||
}
|
||||
|
||||
if (m_DesiredModulate == newModulate && m_DesiredScale == newScale && m_DesiredPivotOffset == newPivot)
|
||||
return;
|
||||
|
||||
m_DesiredPivotOffset = newPivot;
|
||||
m_DesiredScale = newScale;
|
||||
m_DesiredModulate = newModulate;
|
||||
EmitSignal(SignalName.CardStateChanged, this);
|
||||
}
|
||||
|
||||
public void Configure(Card card, CardRow cardRow)
|
||||
{
|
||||
m_Card = card;
|
||||
m_CardRow = cardRow;
|
||||
Refresh();
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
RefreshRarity();
|
||||
RefreshArtTexture();
|
||||
SetDescription(m_Card.Name, m_Card.ToolTip);
|
||||
SetModifiers(m_Card.Modifiers);
|
||||
}
|
||||
|
||||
private void RefreshArtTexture()
|
||||
{
|
||||
SetArtTexture(GD.Load<Texture2D>(m_Card.Id switch
|
||||
{
|
||||
ECard.Bitflip => "res://images/bitflip.png",
|
||||
ECard.ShortCircuit => "res://images/short-circuit.png",
|
||||
ECard.Slipstream => "res://images/slipstream.png",
|
||||
ECard.Latency => "res://images/latency.png",
|
||||
ECard.Rain => "res://images/rain.png",
|
||||
ECard.Drought => "res://images/drought.png",
|
||||
ECard.Pest => "res://images/pest.png",
|
||||
ECard.Gravity => "res://images/gravity.png",
|
||||
ECard.HeatWave => "res://images/heat-wave.png",
|
||||
ECard.SolarFlare => "res://images/solar-flare.png",
|
||||
ECard.LightningStorm => "res://images/lightning-storm.png",
|
||||
ECard.MeteorStorm => "res://images/meteor-storm.png",
|
||||
ECard.WindStorm => "res://images/wind-storm.png",
|
||||
ECard.Move => "res://images/move.png",
|
||||
ECard.TurnLeft => "res://images/turn-left.png",
|
||||
ECard.TurnRight => "res://images/turn-right.png",
|
||||
ECard.Interact => "res://images/interact.png",
|
||||
ECard.NoOp => "res://images/no-op.png",
|
||||
ECard.Potentiate => "res://images/potentiate.png",
|
||||
ECard.Optimize => "res://images/optimize.png",
|
||||
ECard.Streamline => "res://images/streamline.png",
|
||||
ECard.Persist => "res://images/persist.png",
|
||||
ECard.Remember => "res://images/remember.png",
|
||||
ECard.Reason => "res://images/reason.png",
|
||||
ECard.DetoxiumPrime => "res://images/detoxium-prime.png",
|
||||
ECard.FlyingDisk => "res://images/flying-disk.png",
|
||||
ECard.AluminumHat => "res://images/aluminum-hat.png",
|
||||
ECard.EMField => "res://images/em-field.png",
|
||||
ECard.AtomicClock => "res://images/atomic-clock.png",
|
||||
ECard.Jump => "res://images/jump.png",
|
||||
ECard.Repeat => "res://images/repeat.png",
|
||||
ECard.Rest => "res://images/rest.png",
|
||||
ECard.Stabilize => "res://images/stabilize.png",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
}));
|
||||
}
|
||||
|
||||
private void RefreshRarity()
|
||||
{
|
||||
var color = m_Card.Rarity switch
|
||||
{
|
||||
ERarity.Common => new(1, 1, 1),
|
||||
ERarity.Magic => new(0.3f, 1, 0.3f),
|
||||
ERarity.Uncommon => new(0.4f, 0.6f, 1.5f),
|
||||
ERarity.Rare => new(0.8f, 0.3f, 1),
|
||||
ERarity.Legendary => new Color(1, 0.5f, 0.2f),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
SetRarity(color);
|
||||
}
|
||||
|
||||
public void SetRarity(Color color)
|
||||
{
|
||||
if (m_BorderPanel == null || m_Background == null)
|
||||
return;
|
||||
|
||||
m_BorderPanel.Modulate = color;
|
||||
m_Background.Modulate = color;
|
||||
}
|
||||
|
||||
public void SetArtTexture(Texture2D texture)
|
||||
{
|
||||
if (m_ArtTextureRect == null)
|
||||
return;
|
||||
|
||||
m_ArtTextureRect.Texture = texture;
|
||||
}
|
||||
|
||||
public void SetDescription(string displayName, string description)
|
||||
{
|
||||
if (m_Label != null)
|
||||
m_Label.Text = displayName;
|
||||
|
||||
var header = string.IsNullOrEmpty(displayName) ? string.Empty : displayName.Trim();
|
||||
var body = string.IsNullOrEmpty(description) ? string.Empty : description.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(header) && !string.IsNullOrEmpty(body))
|
||||
TooltipText = $"{header}\n{body}";
|
||||
else if (!string.IsNullOrEmpty(header))
|
||||
TooltipText = header;
|
||||
else
|
||||
TooltipText = body;
|
||||
}
|
||||
|
||||
public void SetModifiers(IReadOnlyList<Modifier> modifiers)
|
||||
{
|
||||
if (m_ModifiersContainer == null)
|
||||
return;
|
||||
|
||||
foreach (var child in m_ModifiersContainer.GetChildren())
|
||||
{
|
||||
child.QueueFree();
|
||||
}
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (modifier.DebuffSources.Count > 0)
|
||||
continue;
|
||||
|
||||
var icon = ModifierIconScene.Instantiate<ModifierIcon>();
|
||||
icon.Configure(modifier);
|
||||
m_ModifiersContainer.AddChild(icon);
|
||||
}
|
||||
}
|
||||
|
||||
public void AnimateCard(Tween tween)
|
||||
{
|
||||
}
|
||||
|
||||
public void SetDesiredPosition(Vector2 desiredPosition)
|
||||
{
|
||||
m_DesiredPosition = desiredPosition;
|
||||
}
|
||||
|
||||
public void SnapToDesired()
|
||||
{
|
||||
Position = m_DesiredPosition;
|
||||
PivotOffset = m_DesiredPivotOffset;
|
||||
Scale = m_DesiredScale;
|
||||
Modulate = m_DesiredModulate;
|
||||
}
|
||||
|
||||
private void UpdateAnimatedState(double delta)
|
||||
{
|
||||
var weight = GetBlendWeight(delta);
|
||||
if (weight >= 1f)
|
||||
{
|
||||
SnapToDesired();
|
||||
return;
|
||||
}
|
||||
|
||||
Position = Position.Lerp(m_DesiredPosition, weight);
|
||||
PivotOffset = PivotOffset.Lerp(m_DesiredPivotOffset, weight);
|
||||
Scale = Scale.Lerp(m_DesiredScale, weight);
|
||||
Modulate = Modulate.Lerp(m_DesiredModulate, weight);
|
||||
}
|
||||
|
||||
private float GetBlendWeight(double delta)
|
||||
{
|
||||
var duration = GetAnimationDuration() * 0.05f;
|
||||
if (duration <= 0f)
|
||||
return 1f;
|
||||
|
||||
var weight = 1f - Mathf.Exp(-(float)delta / duration);
|
||||
return Mathf.Clamp(weight, 0f, 1f);
|
||||
}
|
||||
|
||||
private float GetAnimationDuration()
|
||||
{
|
||||
if (m_CardRow != null)
|
||||
return m_CardRow.AnimationDuration;
|
||||
|
||||
return 0.15f;
|
||||
}
|
||||
|
||||
public override void _GuiInput(InputEvent @event)
|
||||
{
|
||||
if (@event is not InputEventMouseButton mouseButton || mouseButton.ButtonIndex != MouseButton.Left)
|
||||
return;
|
||||
|
||||
AcceptEvent();
|
||||
if (Disabled)
|
||||
return;
|
||||
|
||||
if (mouseButton.Pressed)
|
||||
{
|
||||
m_PressedLeftDown = true;
|
||||
CardRow.NotifyCardPress(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_PressedLeftDown && CanSelect)
|
||||
Selected = !Selected;
|
||||
|
||||
m_PressedLeftDown = false;
|
||||
CardRow.NotifyCardPress(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override Variant _GetDragData(Vector2 atPosition)
|
||||
{
|
||||
if (!CanDrag || Disabled)
|
||||
return default;
|
||||
|
||||
m_Dragging = true;
|
||||
UpdateState();
|
||||
UpdateDragPreviewForDropTarget(null);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
switch ((long)what)
|
||||
{
|
||||
case NotificationDragBegin:
|
||||
m_DropBefore = null;
|
||||
UpdateState();
|
||||
break;
|
||||
case NotificationDragEnd:
|
||||
m_Dragging = false;
|
||||
m_Hover = false;
|
||||
m_DropBefore = null;
|
||||
m_DragPreviewRoot = null;
|
||||
m_DragPreviewCards = null;
|
||||
UpdateState();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool _CanDropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (!CanDrag || Disabled)
|
||||
return false;
|
||||
|
||||
var dataObject = data.AsGodotObject();
|
||||
if (dataObject is not CardControl sourceControl || sourceControl == this)
|
||||
return false;
|
||||
|
||||
if (GetParent<CardRow>() is { } cardRow)
|
||||
{
|
||||
if (!cardRow._CanDropData(atPosition, data))
|
||||
return false;
|
||||
}
|
||||
|
||||
sourceControl.UpdateDragPreviewForDropTarget(this);
|
||||
var size = Size.X / 2;
|
||||
var before = atPosition.X < size;
|
||||
if (before != m_DropBefore)
|
||||
{
|
||||
GD.Print($"Can drop before={before} {m_Card.Id}");
|
||||
m_DropBefore = before;
|
||||
UpdateState();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void UpdateDragPreviewForDropTarget(CardControl targetControl)
|
||||
{
|
||||
var draggedCards = GetDraggedCardsForPreview();
|
||||
if (ShouldForceSinglePreview(targetControl, draggedCards))
|
||||
SetDragPreviewCards([this]);
|
||||
else
|
||||
SetDragPreviewCards(draggedCards);
|
||||
}
|
||||
|
||||
private IReadOnlyList<CardControl> GetDraggedCardsForPreview()
|
||||
{
|
||||
if (m_CardRow == null || !m_CardRow.SupportsMultiDrag)
|
||||
return [this];
|
||||
|
||||
var selectedCards = m_CardRow.SelectedCards;
|
||||
if (selectedCards.Count <= 1)
|
||||
return [this];
|
||||
|
||||
foreach (var selectedCard in selectedCards)
|
||||
{
|
||||
if (selectedCard.CardId == Card.CardId)
|
||||
return selectedCards.Select(c => m_CardRow.CardToControl[c.CardId]).ToList();
|
||||
}
|
||||
|
||||
return [this];
|
||||
}
|
||||
|
||||
private bool ShouldForceSinglePreview(CardControl targetControl, IReadOnlyList<CardControl> draggedCards)
|
||||
{
|
||||
if (targetControl == null)
|
||||
return false;
|
||||
|
||||
if (draggedCards.Count <= 1)
|
||||
return false;
|
||||
|
||||
if (!targetControl.Selected)
|
||||
return false;
|
||||
|
||||
foreach (var draggedCard in draggedCards)
|
||||
{
|
||||
if (draggedCard == targetControl)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetDragPreviewCards(IReadOnlyList<CardControl> cards)
|
||||
{
|
||||
if (m_DragPreviewCards != null && m_DragPreviewCards.SequenceEqual(cards))
|
||||
return;
|
||||
|
||||
m_DragPreviewCards = cards.ToList();
|
||||
|
||||
foreach (var card in m_CardRow.CardToControl.Values)
|
||||
{
|
||||
card.m_Dragging = false;
|
||||
card.UpdateState();
|
||||
}
|
||||
|
||||
if (m_DragPreviewRoot != null)
|
||||
{
|
||||
foreach (var child in m_DragPreviewRoot.GetChildren())
|
||||
child.QueueFree();
|
||||
}
|
||||
|
||||
if (cards.Count == 0)
|
||||
return;
|
||||
|
||||
if (cards.Count == 1)
|
||||
{
|
||||
m_DragPreviewRoot?.QueueFree();
|
||||
m_DragPreviewRoot = null;
|
||||
var cardControl = cards[0];
|
||||
var previewCard = (CardControl)cardControl.Duplicate();
|
||||
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
||||
previewCard.Configure(cardControl.Card, null);
|
||||
previewCard.Selected = cardControl.Selected;
|
||||
previewCard.UpdateState();
|
||||
previewCard.SetDesiredPosition(previewCard.Position);
|
||||
previewCard.SnapToDesired();
|
||||
SetDragPreview(previewCard);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_DragPreviewRoot == null)
|
||||
{
|
||||
m_DragPreviewRoot = new();
|
||||
m_DragPreviewRoot.MouseFilter = MouseFilterEnum.Ignore;
|
||||
}
|
||||
|
||||
var spacing = m_CardRow?.CardSpacing ?? 0f;
|
||||
var offset = Mathf.Max(6f, spacing * 2f);
|
||||
var offsetVector = new Vector2(offset, offset);
|
||||
|
||||
for (var index = 0; index < cards.Count; index++)
|
||||
{
|
||||
var cardControl = cards[index];
|
||||
cardControl.m_Dragging = true;
|
||||
cardControl.UpdateState();
|
||||
var previewCard = (CardControl)cardControl.Duplicate();
|
||||
previewCard.SetAnchorsPreset(LayoutPreset.TopLeft);
|
||||
previewCard.Configure(cardControl.Card, null);
|
||||
previewCard.Position = new(offsetVector.X * index, offsetVector.Y * index);
|
||||
previewCard.Size = Size;
|
||||
previewCard.UpdateState();
|
||||
previewCard.SetDesiredPosition(previewCard.Position);
|
||||
previewCard.SnapToDesired();
|
||||
previewCard.ZIndex = index + 1;
|
||||
m_DragPreviewRoot.AddChild(previewCard);
|
||||
}
|
||||
|
||||
m_DragPreviewRoot.Size = Size + offsetVector * Math.Max(0, cards.Count - 1);
|
||||
SetDragPreview(m_DragPreviewRoot);
|
||||
}
|
||||
|
||||
public override void _DropData(Vector2 atPosition, Variant data)
|
||||
{
|
||||
if (!CanDrag)
|
||||
return;
|
||||
|
||||
var obj = data.AsGodotObject();
|
||||
if (obj is CardControl source)
|
||||
{
|
||||
var size = Size.X / 2;
|
||||
var before = atPosition.X < size;
|
||||
EmitSignal(SignalName.CardDroppedOn, source, this, before);
|
||||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public bool CanDrag { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool CanSelect { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Export]
|
||||
public bool Selected
|
||||
{
|
||||
get => m_Selected;
|
||||
set
|
||||
{
|
||||
if (m_Selected == value)
|
||||
return;
|
||||
|
||||
m_Selected = value;
|
||||
EmitSignal(SignalName.CardSelectionChanged, this, m_Selected);
|
||||
UpdateState();
|
||||
|
||||
if (m_Selected && m_CardRow != null)
|
||||
{
|
||||
Tooltip.Instance.Describe(Card);
|
||||
CardRow.SelectionContext = m_CardRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CardRow CardRow => m_CardRow;
|
||||
|
||||
public Card Card => m_Card;
|
||||
|
||||
public Vector2 DesiredScale => m_DesiredScale;
|
||||
|
||||
public Vector2 DesiredPivotOffset => m_DesiredPivotOffset;
|
||||
|
||||
[Signal]
|
||||
public delegate void CardDroppedOnEventHandler(CardControl source, CardControl target, bool before);
|
||||
|
||||
[Signal]
|
||||
public delegate void CardSelectionChangedEventHandler(CardControl card, bool selected);
|
||||
|
||||
[Signal]
|
||||
public delegate void CardStateChangedEventHandler(CardControl card);
|
||||
|
||||
[Export]
|
||||
public PackedScene ModifierIconScene { get; set; }
|
||||
|
||||
private TextureRect m_ArtTextureRect;
|
||||
private Panel m_Background;
|
||||
private Panel m_BorderPanel;
|
||||
private HBoxContainer m_ModifiersContainer;
|
||||
private Card m_Card;
|
||||
private bool m_PressedLeftDown;
|
||||
private bool m_Dragging;
|
||||
private bool m_Selected;
|
||||
private bool m_Hover;
|
||||
private bool? m_DropBefore;
|
||||
private CardRow m_CardRow;
|
||||
private Label m_Label;
|
||||
private HBoxContainer m_DragPreviewRoot;
|
||||
private IReadOnlyList<CardControl> m_DragPreviewCards;
|
||||
private Vector2 m_DesiredPosition;
|
||||
private Vector2 m_DesiredPivotOffset;
|
||||
private Vector2 m_DesiredScale;
|
||||
private Color m_DesiredModulate;
|
||||
}
|
||||
Reference in New Issue
Block a user