Files
tbd-station-14/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs
Brandon Li 545cacbcae StyleNano removal: Palette system and Sheetlets (#29903)
* Apply patch 1777eea9a4..6b32bb2b14

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* make red squiggly line go away

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Add todo list

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Add palette to `TextureButton`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Rename `PalettedButtonSheetlet` to `NTButtonSheetlet` and move useful methods to `ButtonSheetlet`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* migrate `ContextMenu` styles

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Update todo

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* tweak NT colors

* New stylesheet: `InterfaceStylesheet` & `InterfaceTooltipSheetlet`

* Move inheritance of `IButtonConfig` to `NanotransenStylesheet.Buttons`

* move `MenuButtonSheetlet` & actually implement `InterfaceStylesheet` correctly

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* tweak color & update todo

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* chat is this real (update chat palette)

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Update todo

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `SmallButton` and remove some obsolete things from `StyleNano`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* rename `StyleClasses` to `StyleClass` so `Stylesheets.Redux.StyleClasses` syntax is dead

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* replace `ButtonColorGreen` with `Positive`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `Placeholder`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Examine popup buttons

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* move over more things & cleanup `StyleNano` more (under 1000 lines!!!!)

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Remove some more redundant stuff

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Undo style change for chat window

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* paper editing works now

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `OptionButton` styles

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `ListContainer`, move `DefaultWindow` styles (for now) & more cleanup

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* fix `ActionButton` not having highlighting

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* remove imports of `Robust.Client.UserInterface.StylesheetHelpers` & format

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `ButtonBig` and more cleanup

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Move items inheriting from `ISheetletConfig` into their own directory

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Cleanup & move `Label` styles

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Action search box styles

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Moved, stuff is

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* rename `LabelSubtext` to `LabelSubText` & move more stuff (were almost there!!)

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* yap & move over MORE stuff (just like one thing left!!!)

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Change status classes to appropriate existing classes

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* remove remaining references to `StyleNano`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Fix some hardcoding & broken code, `GetFromControl`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Scrollbars!

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* chores

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* clean up `StyleClass.cs`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `ItemListSheetlet` refactor

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* more chores!

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Consistency w/ directory structure

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Move `MainMenuSheetlet`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `ColorPalette`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* whoopsie

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Remove most sheet-specific sheetlets

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* fix warnings, cleanup, & fix scrollbar (this is why we fix warnings boys)

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* yap

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* MASSIVE resharper skill issue

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* actually use `ISheetletConfig`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* have specific sheetlet be specific

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `GetResourceOr`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* cleanup & move / remove `IPalette`s

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* actually do specific stylesheets correctly & fix tooltips

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* cleanup & logging

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* Move `FontKind` and `FontKindExtensions` to their own files

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* rename `InterfaceStylesheet` to `SystemStylesheet`

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* change `ButtonHovered` etc to `PseudoHovered` etc

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* give the palettes fun names

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* `StyleSpace` is no more

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* It should compile now! I am now going to bed (fr) if it fails it fails

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* make squiggly red line go away

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>

* add additional type restrictions to sheetlets

* `CommonStylesheet`

* minor cleanup

* Make `GetSheetletRules` not horrible

* wait this was duplicating style rules. oops!

* move some sheetlets to their associated xamls

* oh wait apparently that was important

* review pass 1

* review pass 2 (font & color stuff)

* review pass 3: remove unused stuff / filename fix

* fix warnings & "replace cast with explicit variable type"

* move `Palette` stuff to its own directory

* tweak colors (they're different now that I actually fixed the OKlab thing)

* review pass 4: little things

* make window close button grey before hovering

* refactor `HLine` to make it less terrible and allow it to be styled

* fix `NanoHeading` (it's been broken for a while whoops) and cleanup hardcoding

* band-aid missing references in `StyleNano`

* move `StyleBox` generating functions out of `IButtonSheetlet` into `StyleBoxHelper`

* remove dictionary field from `IStylesheetManager`

* Add check for unloaded sheetlets

* style tweaks to satisfy OCD

* I somehow missed this: `Caution` styleclass replaced with `negative`, refactor `PowerChargeWindow`

* tweak palettes for like the fourth time

* construct `StyleNano` / `StyleSpace` in `StylesheetManager` and mark them as obsolete

* rename `BackgroundPanel` classes for consistency

* tweak window / `ListContainer`

* oh right you use `///` not `/**`

* font system is bad, make it temporary

* acknowledge Divider funkyness

* remove use of class `Disabled`

* `ColorPalette` allow overriding colors with brace initialization

* review pass again

* tweak disabled button colors

* `StatusPalette` tweaks

* typo

* Make squiggly red line go away

* Delete `Redux`

* Remove all references to `Redux`

* make red less radioactive

* Store stylesheet name inside stylesheet class

* fix merge errors

* use RT's Oklab support instead

* shuffle around `StylesheetManager` fields

* apply stylesheets based off `StylesheetComponent`

* simplify `ColorPalette` construction

* add todo for `SheetletConfigType`

* `OptionButton` has a background color now

* fix disabled buttons

* sigh (red color palette fixed)

* make `ItemList` use primary palette

* Revert "apply stylesheets based off `StylesheetComponent`"

This reverts commit c05b147da845f6e04ff33d1cbd91a18a92c676d7.

* dead code removal

* buttons are green when pressed (we need togglebuttons)

---------

Signed-off-by: Brandon Li <sirbrandonthenerd@gmail.com>
Co-authored-by: Janet Blackquill <uhhadd@gmail.com>
2025-10-19 21:10:44 +00:00

449 lines
14 KiB
C#

using System.Numerics;
using Content.Client.Actions;
using Content.Client.Actions.UI;
using Content.Client.Cooldown;
using Content.Client.Stylesheets;
using Content.Shared.Actions.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Examine;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using static Robust.Client.UserInterface.Controls.TextureRect;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.UserInterface.Systems.Actions.Controls;
public sealed class ActionButton : Control, IEntityControl
{
public const string StyleClassActionHighlightRect = "ActionHighlightRect";
private IEntityManager _entities;
private IPlayerManager _player;
private SpriteSystem? _spriteSys;
private ActionUIController? _controller;
private bool _beingHovered;
private bool _depressed;
private bool _toggled;
public BoundKeyFunction? KeyBind
{
set
{
_keybind = value;
if (_keybind != null)
{
Label.Text = BoundKeyHelper.ShortKeyName(_keybind.Value);
}
}
}
private BoundKeyFunction? _keybind;
public readonly TextureRect Button;
public readonly PanelContainer HighlightRect;
private readonly TextureRect _bigActionIcon;
private readonly TextureRect _smallActionIcon;
public readonly Label Label;
public readonly CooldownGraphic Cooldown;
private readonly SpriteView _smallItemSpriteView;
private readonly SpriteView _bigItemSpriteView;
private Texture? _buttonBackgroundTexture;
public Entity<ActionComponent>? Action { get; private set; }
public bool Locked { get; set; }
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionUnpressed;
public event Action<ActionButton>? ActionFocusExited;
public ActionButton(IEntityManager entities, SpriteSystem? spriteSys = null, ActionUIController? controller = null)
{
// TODO why is this constructor so slooooow. The rest of the code is fine
_entities = entities;
_player = IoCManager.Resolve<IPlayerManager>();
_spriteSys = spriteSys;
_controller = controller;
MouseFilter = MouseFilterMode.Pass;
Button = new TextureRect
{
Name = "Button",
TextureScale = new Vector2(2, 2)
};
HighlightRect = new PanelContainer
{
StyleClasses = { StyleClassActionHighlightRect },
MinSize = new Vector2(32, 32),
Visible = false
};
_bigActionIcon = new TextureRect
{
HorizontalExpand = true,
VerticalExpand = true,
Stretch = StretchMode.Scale,
Visible = false
};
_smallActionIcon = new TextureRect
{
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Bottom,
Stretch = StretchMode.Scale,
Visible = false
};
Label = new Label
{
Name = "Label",
HorizontalAlignment = HAlignment.Left,
VerticalAlignment = VAlignment.Top,
Margin = new Thickness(5, 0, 0, 0)
};
_bigItemSpriteView = new SpriteView
{
Name = "Big Sprite",
HorizontalExpand = true,
VerticalExpand = true,
Scale = new Vector2(2, 2),
SetSize = new Vector2(64, 64),
Visible = false,
OverrideDirection = Direction.South,
};
_smallItemSpriteView = new SpriteView
{
Name = "Small Sprite",
HorizontalAlignment = HAlignment.Right,
VerticalAlignment = VAlignment.Bottom,
Visible = false,
OverrideDirection = Direction.South,
};
// padding to the left of the small icon
var paddingBoxItemIcon = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalExpand = true,
MinSize = new Vector2(64, 64)
};
paddingBoxItemIcon.AddChild(new Control()
{
MinSize = new Vector2(32, 32),
});
paddingBoxItemIcon.AddChild(new Control
{
Children =
{
_smallActionIcon,
_smallItemSpriteView
}
});
Cooldown = new CooldownGraphic {Visible = false};
AddChild(Button);
AddChild(_bigActionIcon);
AddChild(_bigItemSpriteView);
AddChild(HighlightRect);
AddChild(Label);
AddChild(Cooldown);
AddChild(paddingBoxItemIcon);
Button.Modulate = new Color(255, 255, 255, 150);
OnThemeUpdated();
OnKeyBindDown += OnPressed;
OnKeyBindUp += OnUnpressed;
TooltipSupplier = SupplyTooltip;
}
protected override void OnThemeUpdated()
{
base.OnThemeUpdated();
_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
Label.FontColorOverride = Theme.ResolveColorOrSpecified("whiteText");
}
private void OnPressed(GUIBoundKeyEventArgs args)
{
if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
return;
if (args.Function == EngineKeyFunctions.UIRightClick)
Depress(args, true);
ActionPressed?.Invoke(args, this);
}
private void OnUnpressed(GUIBoundKeyEventArgs args)
{
if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
return;
if (args.Function == EngineKeyFunctions.UIRightClick)
Depress(args, false);
ActionUnpressed?.Invoke(args, this);
}
private Control? SupplyTooltip(Control sender)
{
if (!_entities.TryGetComponent(Action, out MetaDataComponent? metadata))
return null;
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
if (_player.LocalEntity is null)
return null;
var ev = new ExaminedEvent(desc, Action.Value, _player.LocalEntity.Value, true, !desc.IsEmpty);
_entities.EventBus.RaiseLocalEvent(Action.Value.Owner, ev);
var newDesc = ev.GetTotalMessage();
return new ActionAlertTooltip(name, newDesc);
}
protected override void ControlFocusExited()
{
ActionFocusExited?.Invoke(this);
}
private void UpdateItemIcon()
{
if (Action?.Comp is not {EntityIcon: { } entity} ||
!_entities.HasComponent<SpriteComponent>(entity))
{
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.SetEntity(null);
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.SetEntity(null);
}
else
{
switch (Action?.Comp.ItemIconStyle)
{
case ItemActionIconStyle.BigItem:
_bigItemSpriteView.Visible = true;
_bigItemSpriteView.SetEntity(entity);
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.SetEntity(null);
break;
case ItemActionIconStyle.BigAction:
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.SetEntity(null);
_smallItemSpriteView.Visible = true;
_smallItemSpriteView.SetEntity(entity);
break;
case ItemActionIconStyle.NoItem:
_bigItemSpriteView.Visible = false;
_bigItemSpriteView.SetEntity(null);
_smallItemSpriteView.Visible = false;
_smallItemSpriteView.SetEntity(null);
break;
}
}
}
private void SetActionIcon(Texture? texture)
{
if (Action?.Comp is not {} action || texture == null)
{
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
else if (action.EntityIcon != null && action.ItemIconStyle == ItemActionIconStyle.BigItem)
{
_smallActionIcon.Texture = texture;
_smallActionIcon.Modulate = action.IconColor;
_smallActionIcon.Visible = true;
_bigActionIcon.Texture = null;
_bigActionIcon.Visible = false;
}
else
{
_bigActionIcon.Texture = texture;
_bigActionIcon.Modulate = action.IconColor;
_bigActionIcon.Visible = true;
_smallActionIcon.Texture = null;
_smallActionIcon.Visible = false;
}
}
public void UpdateIcons()
{
UpdateItemIcon();
UpdateBackground();
if (Action is not {} action)
{
SetActionIcon(null);
return;
}
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
_spriteSys ??= _entities.System<SpriteSystem>();
var icon = action.Comp.Icon;
if (_controller.SelectingTargetFor == action || action.Comp.Toggled)
{
if (action.Comp.IconOn is {} iconOn)
icon = iconOn;
if (action.Comp.BackgroundOn is {} background)
_buttonBackgroundTexture = _spriteSys.Frame0(background);
}
else
{
_buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
}
SetActionIcon(icon != null ? _spriteSys.Frame0(icon) : null);
}
public void UpdateBackground()
{
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
if (Action != null ||
_controller.IsDragging && GetPositionInParent() == Parent?.ChildCount - 1)
{
Button.Texture = _buttonBackgroundTexture;
}
else
{
Button.Texture = null;
}
}
public bool TryReplaceWith(EntityUid actionId, ActionsSystem system)
{
if (Locked)
return false;
UpdateData(actionId, system);
return true;
}
public void UpdateData(EntityUid? actionId, ActionsSystem system)
{
Action = system.GetAction(actionId);
Label.Visible = Action != null;
UpdateIcons();
}
public void ClearData()
{
Action = null;
Cooldown.Visible = false;
Cooldown.Progress = 1;
Label.Visible = false;
UpdateIcons();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateBackground();
Cooldown.Visible = Action?.Comp.Cooldown != null;
if (Action?.Comp is not {} action)
return;
if (action.Cooldown is {} cooldown)
Cooldown.FromTime(cooldown.Start, cooldown.End);
if (_toggled != action.Toggled)
_toggled = action.Toggled;
}
protected override void MouseEntered()
{
base.MouseEntered();
UserInterfaceManager.HoverSound();
_beingHovered = true;
DrawModeChanged();
}
protected override void MouseExited()
{
base.MouseExited();
_beingHovered = false;
DrawModeChanged();
}
/// <summary>
/// Press this button down. If it was depressed and now set to not depressed, will
/// trigger the action.
/// </summary>
public void Depress(GUIBoundKeyEventArgs args, bool depress)
{
// action can still be toggled if it's allowed to stay selected
if (Action?.Comp is not {Enabled: true})
return;
_depressed = depress;
DrawModeChanged();
}
public void DrawModeChanged()
{
_controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
HighlightRect.Visible = _beingHovered && (Action != null || _controller.IsDragging);
// always show the normal empty button style if no action in this slot
if (Action?.Comp is not {} action)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
return;
}
// show a hover only if the action is usable or another action is being dragged on top of this
if (_beingHovered && (_controller.IsDragging || action.Enabled))
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
}
// it's only depress-able if it's usable, so if we're depressed
// show the depressed style
if (_depressed && !_beingHovered)
{
HighlightRect.Visible = false;
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassPressed);
return;
}
// if it's toggled on, always show the toggled on style (currently same as depressed style)
if (action.Toggled || _controller.SelectingTargetFor == Action?.Owner)
{
// when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
SetOnlyStylePseudoClass(action.IconOn != null
? ContainerButton.StylePseudoClassNormal
: ContainerButton.StylePseudoClassPressed);
return;
}
if (!action.Enabled)
{
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
return;
}
SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
}
EntityUid? IEntityControl.UiEntity => Action;
}