using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
namespace Content.Client.UserInterface.Controls;
///
/// A Button that requires a second click to actually invoke its OnPressed action.
/// When clicked once it will change rendering modes to be prefixed by
/// and displays on the button instead of .
///
/// After the first click needs to elapse before it can be clicked again to confirm.
/// When the button doesn't get clicked a second time before passes it changes back to its normal state.
///
///
/// Colors for the different states need to be set in the stylesheet
///
public sealed partial class ConfirmButton : Button
{
[Dependency] private IGameTiming _gameTiming = default!;
public const string ConfirmPrefix = "confirm-";
///
/// The time when the button will revert from confirming if left unpressed.
///
private TimeSpan? _nextReset;
///
/// The time when the button should re-enable itself (to avoid debouncing).
///
private TimeSpan? _nextCooldown;
private bool _isConfirming;
private string? _confirmationText;
private string? _text;
///
/// Fired when the button was pressed and confirmed
///
public new event Action? OnPressed;
///
/// Fired when the button has started to confirm and is awaiting a second button press.
///
public event Action? OnConfirming;
///
///
/// Hides the buttons text property to be able to sanely replace the button text with
/// when asking for confirmation
///
public new string? Text
{
get => _text;
set
{
_text = value;
UpdateText();
}
}
///
///
/// Overrides the confirming state of the button when set.
/// Intended for public use, should not be used inside the class due to IsConfirming side-effects.
///
public new bool Disabled
{
get => base.Disabled;
set
{
// Force out of confirming status (may change base.Disabled), then write your new disabled value.
SetIsConfirming(false);
SetDisabled(value);
}
}
///
/// The text displayed on the button when waiting for a second click
///
[ViewVariables(VVAccess.ReadWrite)]
public string ConfirmationText
{
get => _confirmationText ?? Loc.GetString("generic-confirm");
set
{
_confirmationText = value;
UpdateText();
}
}
///
/// The time until the button reverts to normal
///
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan ResetTime { get; set; } = TimeSpan.FromSeconds(2);
///
/// The time until the button accepts a second click. This is to prevent accidentally confirming the button
///
[ViewVariables(VVAccess.ReadWrite)]
public TimeSpan CooldownTime { get; set; } = TimeSpan.FromSeconds(.5);
///
/// A property to get or change whether the button is confirming (awaiting a second press within a time limit) or not
///
[ViewVariables]
public bool IsConfirming
{
get => _isConfirming;
set
{
if (_isConfirming != value)
SetIsConfirming(value);
}
}
public ConfirmButton()
{
IoCManager.InjectDependencies(this);
base.OnPressed += HandleOnPressed;
}
protected override void FrameUpdate(FrameEventArgs args)
{
if (!IsConfirming)
return;
if (_gameTiming.CurTime > _nextReset)
SetIsConfirming(false);
else if (Disabled && _gameTiming.CurTime > _nextCooldown)
SetDisabled(false);
}
protected override void DrawModeChanged()
{
if (IsConfirming)
{
switch (DrawMode)
{
case DrawModeEnum.Normal:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassNormal);
break;
case DrawModeEnum.Pressed:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassPressed);
break;
case DrawModeEnum.Hover:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassHover);
break;
case DrawModeEnum.Disabled:
SetOnlyStylePseudoClass(ConfirmPrefix + StylePseudoClassDisabled);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
else
{
base.DrawModeChanged();
}
}
private void HandleOnPressed(ButtonEventArgs buttonEvent)
{
//Prevent accidental confirmations from double clicking
if (IsConfirming && _nextCooldown > _gameTiming.CurTime)
return;
// Update the state machine before invoking our events.
SetIsConfirming(!IsConfirming);
switch (IsConfirming)
{
case true:
OnConfirming?.Invoke(buttonEvent);
break;
case false:
OnPressed?.Invoke(buttonEvent);
break;
}
}
///
/// Updates the text shown on the button depending on whether the button is confirming or not.
///
private void UpdateText()
{
base.Text = IsConfirming ? ConfirmationText : Text;
}
///
/// Internal setter logi
///
private void SetDisabled(bool value)
{
base.Disabled = value;
}
///
/// Internal setter logic for
/// Sets the text, the timers, and the disabled state.
///
private void SetIsConfirming(bool value)
{
_isConfirming = value;
// Update button visuals.
UpdateText();
DrawModeChanged();
if (value)
{
// Start our timers, disable button until cooldown.
SetDisabled(true);
_nextCooldown = _gameTiming.CurTime + CooldownTime;
_nextReset = _gameTiming.CurTime + ResetTime;
}
else
{
// Clear timers, ensure button is enabled if it was disabled before (previous SetIsConfirming(true) call => valid reset timer)
if (Disabled && _nextReset != null)
SetDisabled(false);
_nextReset = null;
_nextCooldown = null;
}
}
}