using System; using System.Collections.Generic; using Robust.Shared.ViewVariables; namespace Robust.Client.UserInterface.Controls { /// /// Base class for a generic UI button. /// /// /// /// public abstract class BaseButton : Control { private bool _attemptingPress; private bool _beingHovered; private bool _disabled; private bool _pressed; private bool _enableAllKeybinds; private ButtonGroup _group; private bool _toggleMode; /// /// Specifies the group this button belongs to. /// /// /// Of multiple buttons in the same group, only one can be pressed (radio buttons). /// public ButtonGroup Group { get => _group; set { // Remove from old group. _group?.Buttons.Remove(this); _group = value; if (_group == null) { return; } _group.Buttons.Add(this); ToggleMode = true; // Set us to pressed if we're the first button. Pressed = value.Buttons.Count == 0; } } /// /// Controls mode of operation in relation to press/release events. /// [ViewVariables] public ActionMode Mode { get; set; } = ActionMode.Release; /// /// Whether the button is disabled. /// If a button is disabled, it appears greyed out and cannot be interacted with. /// [ViewVariables] public bool Disabled { get => _disabled; set { var old = _disabled; _disabled = value; if (old != value) { DrawModeChanged(); } } } /// /// Whether the button is currently toggled down. Only applies when is true. /// [ViewVariables] public bool Pressed { get => _pressed; set { if (_pressed == value) { return; } if (!value && Group != null) { throw new InvalidOperationException("Cannot directly unset a grouped button. Set another button in the group instead."); } _pressed = value; if (Group != null) { UnsetOtherGroupButtons(); } DrawModeChanged(); } } /// /// Whether a button enables Keybinds without GUIBoundKeyEventArgs.CanFocus to trigger the button. /// public bool EnableAllKeybinds { get => _enableAllKeybinds; set => _enableAllKeybinds = value; } /// /// If true, this button functions as a toggle, not as a regular push button. /// [ViewVariables] public bool ToggleMode { get => _toggleMode; set { if (Group != null && !value) { throw new InvalidOperationException("Cannot disable toggle mode on a button in a group."); } _toggleMode = value; } } /// /// If true, this button is currently being hovered over by the mouse. /// [ViewVariables] public bool IsHovered => _beingHovered; /// /// Draw mode used for styling of buttons. /// [ViewVariables] public DrawModeEnum DrawMode { get { if (Disabled) { return DrawModeEnum.Disabled; } else if (Pressed || _attemptingPress) { return DrawModeEnum.Pressed; } else if (IsHovered) { return DrawModeEnum.Hover; } else { return DrawModeEnum.Normal; } } } /// /// Fired when the button is pushed down by the mouse. /// public event Action OnButtonDown; /// /// Fired when the button is released by the mouse. /// public event Action OnButtonUp; /// /// Fired when the button is "pressed". When this happens depends on . /// public event Action OnPressed; /// /// If is set, fired when the button is toggled up or down. /// public event Action OnToggled; protected virtual void DrawModeChanged() { } protected internal override void KeyBindDown(GUIBoundKeyEventArgs args) { base.KeyBindDown(args); if (Disabled || (!_enableAllKeybinds && !args.CanFocus)) { return; } var buttonEventArgs = new ButtonEventArgs(this, args); OnButtonDown?.Invoke(buttonEventArgs); var drawMode = DrawMode; if (Mode == ActionMode.Release) { _attemptingPress = true; } else { if (ToggleMode) { // Can't un press a radio button directly. if (Group == null || !_pressed) { _pressed = !_pressed; OnPressed?.Invoke(buttonEventArgs); OnToggled?.Invoke(new ButtonToggledEventArgs(Pressed, this, args)); UnsetOtherGroupButtons(); } } else { _attemptingPress = true; OnPressed?.Invoke(buttonEventArgs); } } if (drawMode != DrawMode) { DrawModeChanged(); } } protected internal override void KeyBindUp(GUIBoundKeyEventArgs args) { base.KeyBindUp(args); if (Disabled || (!_enableAllKeybinds && !args.CanFocus)) { return; } var buttonEventArgs = new ButtonEventArgs(this, args); OnButtonUp?.Invoke(buttonEventArgs); var drawMode = DrawMode; if (Mode == ActionMode.Release && _attemptingPress && this == UserInterfaceManagerInternal.MouseGetControl(args.PointerLocation.Position)) { // Can't un press a radio button directly. if (Group == null || !_pressed) { if (ToggleMode) { _pressed = !_pressed; } OnPressed?.Invoke(buttonEventArgs); if (ToggleMode && args.CanFocus) { OnToggled?.Invoke(new ButtonToggledEventArgs(Pressed, this, args)); UnsetOtherGroupButtons(); } } } _attemptingPress = false; if (drawMode != DrawMode) { DrawModeChanged(); } } private void UnsetOtherGroupButtons() { if (_group == null) { return; } foreach (var button in _group.Buttons) { if (button != this && button.Pressed) { button._pressed = false; button.DrawModeChanged(); } } } protected internal override void MouseEntered() { base.MouseEntered(); var drawMode = DrawMode; _beingHovered = true; if (drawMode != DrawMode) { DrawModeChanged(); } } protected internal override void MouseExited() { base.MouseExited(); var drawMode = DrawMode; _beingHovered = false; if (drawMode != DrawMode) { DrawModeChanged(); } } public enum DrawModeEnum { Normal = 0, Pressed = 1, Hover = 2, Disabled = 3 } public class ButtonEventArgs : EventArgs { /// /// The button this event originated from. /// public BaseButton Button { get; } public GUIBoundKeyEventArgs Event { get; } public ButtonEventArgs(BaseButton button, GUIBoundKeyEventArgs args) { Button = button; Event = args; } } /// /// Fired when a is toggled. /// public class ButtonToggledEventArgs : ButtonEventArgs { /// /// The new pressed state of the button. /// public bool Pressed { get; } public ButtonToggledEventArgs(bool pressed, BaseButton button, GUIBoundKeyEventArgs args) : base(button, args) { Pressed = pressed; } } /// /// For use with . /// public enum ActionMode { /// /// fires when the mouse button causing them is pressed down. /// Press = 0, /// /// fires when the mouse button causing them is released. /// This is the default and most intuitive method. /// Release = 1 } } /// /// Represents a group of buttons. /// /// /// Of all buttons in a group, only one can be pressed down. /// Yes, it's for radio buttons. /// public sealed class ButtonGroup { internal readonly List Buttons = new List(); } }