diff --git a/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs index 8220e18708..97c07dd8c9 100644 --- a/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs +++ b/Content.Client/Changeling/UI/ChangelingTransformBoundUserInterface.cs @@ -1,4 +1,7 @@ -using Content.Shared.Changeling.Systems; +using Content.Client.Stylesheets; +using Content.Client.UserInterface.Controls; +using Content.Shared.Changeling.Components; +using Content.Shared.Changeling.Systems; using JetBrains.Annotations; using Robust.Client.UserInterface; @@ -7,28 +10,58 @@ namespace Content.Client.Changeling.UI; [UsedImplicitly] public sealed partial class ChangelingTransformBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { - private ChangelingTransformMenu? _window; + private SimpleRadialMenu? _menu; + private static readonly Color SelectedOptionBackground = StyleNano.ButtonColorGoodDefault.WithAlpha(128); + private static readonly Color SelectedOptionHoverBackground = StyleNano.ButtonColorGoodHovered.WithAlpha(128); protected override void Open() { base.Open(); - _window = this.CreateWindow(); - - _window.OnIdentitySelect += SendIdentitySelect; - - _window.Update(Owner); + _menu = this.CreateWindow(); + Update(); + _menu.OpenOverMouseScreenPosition(); } + public override void Update() { - if (_window == null) + if (_menu == null) return; - _window.Update(Owner); + if (!EntMan.TryGetComponent(Owner, out var lingIdentity)) + return; + + var models = ConvertToButtons(lingIdentity.ConsumedIdentities, lingIdentity?.CurrentIdentity); + + _menu.SetButtons(models); } - public void SendIdentitySelect(NetEntity identityId) + private IEnumerable ConvertToButtons( + IEnumerable identities, + EntityUid? currentIdentity + ) + { + var buttons = new List(); + foreach (var identity in identities) + { + if (!EntMan.TryGetComponent(identity, out var metadata)) + continue; + + var option = new RadialMenuActionOption(SendIdentitySelect, EntMan.GetNetEntity(identity)) + { + IconSpecifier = RadialMenuIconSpecifier.With(identity), + ToolTip = metadata.EntityName, + BackgroundColor = (currentIdentity == identity) ? SelectedOptionBackground : null, + HoverBackgroundColor = (currentIdentity == identity) ? SelectedOptionHoverBackground : null + }; + buttons.Add(option); + } + + return buttons; + } + + private void SendIdentitySelect(NetEntity identityId) { SendPredictedMessage(new ChangelingTransformIdentitySelectMessage(identityId)); } diff --git a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml deleted file mode 100644 index 38ae0ec715..0000000000 --- a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs b/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs deleted file mode 100644 index ebd4e90440..0000000000 --- a/Content.Client/Changeling/UI/ChangelingTransformMenu.xaml.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Numerics; -using Content.Client.UserInterface.Controls; -using Content.Shared.Changeling.Components; -using Robust.Client.AutoGenerated; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; - -namespace Content.Client.Changeling.UI; - -[GenerateTypedNameReferences] -public sealed partial class ChangelingTransformMenu : RadialMenu -{ - [Dependency] private readonly IEntityManager _entity = default!; - public event Action? OnIdentitySelect; - - public ChangelingTransformMenu() - { - RobustXamlLoader.Load(this); - IoCManager.InjectDependencies(this); - } - - public void Update(EntityUid uid) - { - Main.DisposeAllChildren(); - - if (!_entity.TryGetComponent(uid, out var identityComp)) - return; - - foreach (var identityUid in identityComp.ConsumedIdentities) - { - if (!_entity.TryGetComponent(identityUid, out var metadata)) - continue; - - var identityName = metadata.EntityName; - - var button = new ChangelingTransformMenuButton() - { - StyleClasses = { "RadialMenuButton" }, - SetSize = new Vector2(64, 64), - ToolTip = identityName, - }; - - var entView = new SpriteView() - { - SetSize = new Vector2(48, 48), - VerticalAlignment = VAlignment.Center, - HorizontalAlignment = HAlignment.Center, - Stretch = SpriteView.StretchMode.Fill, - }; - entView.SetEntity(identityUid); - button.OnButtonUp += _ => - { - OnIdentitySelect?.Invoke(_entity.GetNetEntity(identityUid)); - Close(); - }; - button.AddChild(entView); - Main.AddChild(button); - } - } -} - -public sealed class ChangelingTransformMenuButton : RadialMenuTextureButtonWithSector; diff --git a/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs index 52ea835f4a..9334c85536 100644 --- a/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs +++ b/Content.Client/Ghost/GhostRoleRadioBoundUserInterface.cs @@ -1,25 +1,58 @@ +using Content.Client.UserInterface.Controls; using Content.Shared.Ghost.Roles; +using Content.Shared.Ghost.Roles.Components; using Robust.Client.UserInterface; using Robust.Shared.Prototypes; namespace Content.Client.Ghost; -public sealed class GhostRoleRadioBoundUserInterface : BoundUserInterface +public sealed class GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey) { - private GhostRoleRadioMenu? _ghostRoleRadioMenu; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - public GhostRoleRadioBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) - { - IoCManager.InjectDependencies(this); - } + private SimpleRadialMenu? _ghostRoleRadioMenu; protected override void Open() { base.Open(); - _ghostRoleRadioMenu = this.CreateWindow(); - _ghostRoleRadioMenu.SetEntity(Owner); - _ghostRoleRadioMenu.SendGhostRoleRadioMessageAction += SendGhostRoleRadioMessage; + _ghostRoleRadioMenu = this.CreateWindow(); + + // The purpose of this radial UI is for ghost role radios that allow you to select + // more than one potential option, such as with kobolds/lizards. + // This means that it won't show anything if SelectablePrototypes is empty. + if (!EntMan.TryGetComponent(Owner, out var comp)) + return; + + var list = ConvertToButtons(comp.SelectablePrototypes); + + _ghostRoleRadioMenu.SetButtons(list); + } + + private IEnumerable ConvertToButtons(List> protoIds) + { + var list = new List(); + foreach (var ghostRoleProtoId in protoIds) + { + // For each prototype we find we want to create a button that uses the name of the ghost role + // as the hover tooltip, and the icon is taken from either the ghost role entityprototype + // or the indicated icon entityprototype. + if (!_prototypeManager.Resolve(ghostRoleProtoId, out var ghostRoleProto)) + continue; + + var option = new RadialMenuActionOption>(SendGhostRoleRadioMessage, ghostRoleProtoId) + { + ToolTip = Loc.GetString(ghostRoleProto.Name), + // pick the icon if it exists, otherwise fallback to the ghost role's entity + IconSpecifier = ghostRoleProto.IconPrototype != null + && _prototypeManager.Resolve(ghostRoleProto.IconPrototype, out var iconProto) + ? RadialMenuIconSpecifier.With(iconProto) + : RadialMenuIconSpecifier.With(ghostRoleProto.EntityPrototype) + }; + list.Add(option); + } + + return list; } private void SendGhostRoleRadioMessage(ProtoId protoId) diff --git a/Content.Client/Ghost/GhostRoleRadioMenu.xaml b/Content.Client/Ghost/GhostRoleRadioMenu.xaml deleted file mode 100644 index c35ee128c5..0000000000 --- a/Content.Client/Ghost/GhostRoleRadioMenu.xaml +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs b/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs deleted file mode 100644 index 718b6c4995..0000000000 --- a/Content.Client/Ghost/GhostRoleRadioMenu.xaml.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Content.Client.UserInterface.Controls; -using Content.Shared.Ghost.Roles; -using Content.Shared.Ghost.Roles.Components; -using Robust.Client.UserInterface; -using Robust.Client.UserInterface.Controls; -using Robust.Client.UserInterface.XAML; -using Robust.Shared.Prototypes; -using System.Numerics; - -namespace Content.Client.Ghost; - -public sealed partial class GhostRoleRadioMenu : RadialMenu -{ - [Dependency] private readonly EntityManager _entityManager = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - - public event Action>? SendGhostRoleRadioMessageAction; - - public EntityUid Entity { get; set; } - - public GhostRoleRadioMenu() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - } - - public void SetEntity(EntityUid uid) - { - Entity = uid; - RefreshUI(); - } - - private void RefreshUI() - { - // The main control that will contain all the clickable options - var main = FindControl("Main"); - - // The purpose of this radial UI is for ghost role radios that allow you to select - // more than one potential option, such as with kobolds/lizards. - // This means that it won't show anything if SelectablePrototypes is empty. - if (!_entityManager.TryGetComponent(Entity, out var comp)) - return; - - foreach (var ghostRoleProtoString in comp.SelectablePrototypes) - { - // For each prototype we find we want to create a button that uses the name of the ghost role - // as the hover tooltip, and the icon is taken from either the ghost role entityprototype - // or the indicated icon entityprototype. - if (!_prototypeManager.TryIndex(ghostRoleProtoString, out var ghostRoleProto)) - continue; - - var button = new GhostRoleRadioMenuButton() - { - SetSize = new Vector2(64, 64), - ToolTip = Loc.GetString(ghostRoleProto.Name), - ProtoId = ghostRoleProto.ID, - }; - - var entProtoView = new EntityPrototypeView() - { - SetSize = new Vector2(48, 48), - VerticalAlignment = VAlignment.Center, - HorizontalAlignment = HAlignment.Center, - Stretch = SpriteView.StretchMode.Fill - }; - - // pick the icon if it exists, otherwise fallback to the ghost role's entity - if (_prototypeManager.Resolve(ghostRoleProto.IconPrototype, out var iconProto)) - entProtoView.SetPrototype(iconProto); - else - entProtoView.SetPrototype(ghostRoleProto.EntityPrototype); - - button.AddChild(entProtoView); - main.AddChild(button); - AddGhostRoleRadioMenuButtonOnClickActions(main); - } - } - - private void AddGhostRoleRadioMenuButtonOnClickActions(Control control) - { - var mainControl = control as RadialContainer; - - if (mainControl == null) - return; - - foreach (var child in mainControl.Children) - { - var castChild = child as GhostRoleRadioMenuButton; - - if (castChild == null) - continue; - - castChild.OnButtonUp += _ => - { - SendGhostRoleRadioMessageAction?.Invoke(castChild.ProtoId); - Close(); - }; - } - } -} - -public sealed class GhostRoleRadioMenuButton : RadialMenuTextureButtonWithSector -{ - public ProtoId ProtoId { get; set; } -} diff --git a/Content.Client/RCD/RCDMenuBoundUserInterface.cs b/Content.Client/RCD/RCDMenuBoundUserInterface.cs index 3c9d5d1e55..6aa32892cf 100644 --- a/Content.Client/RCD/RCDMenuBoundUserInterface.cs +++ b/Content.Client/RCD/RCDMenuBoundUserInterface.cs @@ -51,10 +51,10 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface _menu.OpenOverMouseScreenPosition(); } - private IEnumerable ConvertToButtons(HashSet> prototypes) + private IEnumerable ConvertToButtons(HashSet> prototypes) { - Dictionary> buttonsByCategory = new(); - ValueList topLevelActions = new(); + Dictionary> buttonsByCategory = new(); + ValueList topLevelActions = new(); foreach (var protoId in prototypes) { var prototype = _prototypeManager.Index(protoId); @@ -62,7 +62,7 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface { var topLevelActionOption = new RadialMenuActionOption(HandleMenuOptionClick, prototype) { - Sprite = prototype.Sprite, + IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite), ToolTip = GetTooltip(prototype) }; topLevelActions.Add(topLevelActionOption); @@ -74,26 +74,26 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface if (!buttonsByCategory.TryGetValue(prototype.Category, out var list)) { - list = new List(); + list = new List(); buttonsByCategory.Add(prototype.Category, list); } var actionOption = new RadialMenuActionOption(HandleMenuOptionClick, prototype) { - Sprite = prototype.Sprite, + IconSpecifier = RadialMenuIconSpecifier.With(prototype.Sprite), ToolTip = GetTooltip(prototype) }; list.Add(actionOption); } - var models = new RadialMenuOption[buttonsByCategory.Count + topLevelActions.Count]; + var models = new RadialMenuOptionBase[buttonsByCategory.Count + topLevelActions.Count]; var i = 0; foreach (var (key, list) in buttonsByCategory) { var groupInfo = PrototypesGroupingInfo[key]; models[i] = new RadialMenuNestedLayerOption(list) { - Sprite = groupInfo.Sprite, + IconSpecifier = RadialMenuIconSpecifier.With(groupInfo.Sprite), ToolTip = Loc.GetString(groupInfo.Tooltip) }; i++; diff --git a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs index 77ac13c972..2ada6e4b01 100644 --- a/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs +++ b/Content.Client/Silicons/StationAi/StationAiBoundUserInterface.cs @@ -23,15 +23,15 @@ public sealed class StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : B _menu.Open(); } - private IEnumerable ConvertToButtons(IReadOnlyList actions) + private IEnumerable ConvertToButtons(IReadOnlyList actions) { - var models = new RadialMenuActionOption[actions.Count]; + var models = new RadialMenuActionOptionBase[actions.Count]; for (int i = 0; i < actions.Count; i++) { var action = actions[i]; models[i] = new RadialMenuActionOption(HandleRadialMenuClick, action.Event) { - Sprite = action.Sprite, + IconSpecifier = RadialMenuIconSpecifier.With(action.Sprite), ToolTip = action.Tooltip }; } diff --git a/Content.Client/UserInterface/Controls/RadialMenu.cs b/Content.Client/UserInterface/Controls/RadialMenu.cs index 9734cf2960..959a60ef4f 100644 --- a/Content.Client/UserInterface/Controls/RadialMenu.cs +++ b/Content.Client/UserInterface/Controls/RadialMenu.cs @@ -229,10 +229,10 @@ public class RadialMenu : BaseWindow /// from interactions. /// [Virtual] -public class RadialMenuTextureButtonBase : TextureButton +public abstract class RadialMenuButtonBase : BaseButton { /// - protected RadialMenuTextureButtonBase() + protected RadialMenuButtonBase() { EnableAllKeybinds = true; } @@ -242,7 +242,9 @@ public class RadialMenuTextureButtonBase : TextureButton { if (args.Function == EngineKeyFunctions.UIClick || args.Function == ContentKeyFunctions.AltActivateItemInWorld) + { base.KeyBindUp(args); + } } } @@ -253,8 +255,14 @@ public class RadialMenuTextureButtonBase : TextureButton /// works only if control have parent, and ActiveContainer property is set. /// Also considers all space outside of radial menu buttons as itself for clicking. /// -public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTextureButtonBase +public sealed class RadialMenuContextualCentralTextureButton : TextureButton { + /// + public RadialMenuContextualCentralTextureButton() + { + EnableAllKeybinds = true; + } + public float InnerRadius { get; set; } public Vector2? ParentCenter { get; set; } @@ -271,15 +279,25 @@ public sealed class RadialMenuContextualCentralTextureButton : RadialMenuTexture var innerRadiusSquared = InnerRadius * InnerRadius; - // comparing to squared values is faster then making sqrt + // comparing to squared values is faster, then making sqrt return distSquared < innerRadiusSquared; } + + /// + protected override void KeyBindUp(GUIBoundKeyEventArgs args) + { + if (args.Function == EngineKeyFunctions.UIClick + || args.Function == ContentKeyFunctions.AltActivateItemInWorld) + { + base.KeyBindUp(args); + } + } } /// /// Menu button for outer area of radial menu (covers everything 'outside'). /// -public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase +public sealed class RadialMenuOuterAreaButton : RadialMenuButtonBase { public float OuterRadius { get; set; } @@ -303,7 +321,7 @@ public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase } [Virtual] -public class RadialMenuTextureButton : RadialMenuTextureButtonBase +public class RadialMenuButton : RadialMenuButtonBase { /// /// Upon clicking this button the radial menu will be moved to the layer of this control. @@ -319,9 +337,8 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase /// /// A simple texture button that can move the user to a different layer within a radial menu /// - public RadialMenuTextureButton() + public RadialMenuButton() { - EnableAllKeybinds = true; OnButtonUp += OnClicked; } @@ -391,7 +408,7 @@ public interface IRadialMenuItemWithSector } [Virtual] -public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadialMenuItemWithSector +public class RadialMenuButtonWithSector : RadialMenuButton, IRadialMenuItemWithSector { private Vector2[]? _sectorPointsForDrawing; @@ -500,7 +517,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia /// /// A simple texture button that can move the user to a different layer within a radial menu /// - public RadialMenuTextureButtonWithSector() + public RadialMenuButtonWithSector() { } diff --git a/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs index 31d7eab340..ec7dcbbb5a 100644 --- a/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs +++ b/Content.Client/UserInterface/Controls/SimpleRadialMenu.xaml.cs @@ -7,6 +7,8 @@ using Robust.Client.GameObjects; using Robust.Shared.Timing; using Robust.Client.UserInterface.XAML; using Robust.Client.Input; +using Robust.Client.UserInterface.Controls; +using Robust.Shared.Prototypes; namespace Content.Client.UserInterface.Controls; @@ -30,7 +32,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu _attachMenuToEntity = owner; } - public void SetButtons(IEnumerable models, SimpleRadialMenuSettings? settings = null) + public void SetButtons(IEnumerable models, SimpleRadialMenuSettings? settings = null) { ClearExistingChildrenRadialButtons(); @@ -45,7 +47,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu } private void Fill( - IEnumerable models, + IEnumerable models, SpriteSystem sprites, ICollection rootControlChildren, SimpleRadialMenuSettings settings @@ -77,7 +79,7 @@ public sealed partial class SimpleRadialMenu : RadialMenu } } - private RadialMenuTextureButton RecursiveContainerExtraction( + private RadialMenuButton RecursiveContainerExtraction( SpriteSystem sprites, ICollection rootControlChildren, RadialMenuNestedLayerOption model, @@ -112,8 +114,8 @@ public sealed partial class SimpleRadialMenu : RadialMenu return thisLayerLinkButton; } - private RadialMenuTextureButton ConvertToButton( - RadialMenuOption model, + private RadialMenuButton ConvertToButton( + RadialMenuOptionBase model, SpriteSystem sprites, SimpleRadialMenuSettings settings, bool haveNested @@ -121,29 +123,26 @@ public sealed partial class SimpleRadialMenu : RadialMenu { var button = settings.UseSectors ? ConvertToButtonWithSector(model, settings) - : new RadialMenuTextureButton(); + : new RadialMenuButton(); button.SetSize = new Vector2(64f, 64f); button.ToolTip = model.ToolTip; - if (model.Sprite != null) + var imageControl = model.IconSpecifier switch { - var scale = Vector2.One; + RadialMenuTextureIconSpecifier textureSpecifier => CreateTexture(textureSpecifier.Sprite, sprites), + RadialMenuEntityIconSpecifier entitySpecifier => CreateSpriteView(entitySpecifier.Entity), + RadialMenuEntityPrototypeIconSpecifier entProtoSpecifier => CreateEntityPrototypeView(entProtoSpecifier.ProtoId), + _ => null + }; - var texture = sprites.Frame0(model.Sprite); - if (texture.Width <= 32) - { - scale *= 2; - } + if(imageControl != null) + button.AddChild(imageControl); - button.TextureNormal = texture; - button.Scale = scale; - } - - if (model is RadialMenuActionOption actionOption) + if (model is RadialMenuActionOptionBase actionOption) { button.OnPressed += _ => { actionOption.OnPressed?.Invoke(); - if(!haveNested) + if (!haveNested) Close(); }; } @@ -151,9 +150,53 @@ public sealed partial class SimpleRadialMenu : RadialMenu return button; } - private static RadialMenuTextureButtonWithSector ConvertToButtonWithSector(RadialMenuOption model, SimpleRadialMenuSettings settings) + private Control CreateEntityPrototypeView(EntProtoId protoId) { - var button = new RadialMenuTextureButtonWithSector + var entProtoView = new EntityPrototypeView + { + SetSize = new Vector2(48, 48), + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Stretch = SpriteView.StretchMode.Fill, + }; + entProtoView.SetPrototype(protoId); + return entProtoView; + } + + private static Control CreateSpriteView(EntityUid entityForSpriteView) + { + var entView = new SpriteView + { + SetSize = new Vector2(48, 48), + VerticalAlignment = VAlignment.Center, + HorizontalAlignment = HAlignment.Center, + Stretch = SpriteView.StretchMode.Fill, + }; + entView.SetEntity(entityForSpriteView); + return entView; + } + + private static Control CreateTexture(SpriteSpecifier spriteSpecifier, SpriteSystem sprites) + { + var scale = Vector2.One; + + var texture = sprites.Frame0(spriteSpecifier); + if (texture.Width <= 32) + { + scale *= 2; + } + + var imageControl = new TextureRect() + { + Texture = texture, + TextureScale = scale + }; + return imageControl; + } + + private static RadialMenuButtonWithSector ConvertToButtonWithSector(RadialMenuOptionBase model, SimpleRadialMenuSettings settings) + { + var button = new RadialMenuButtonWithSector { DrawBorder = settings.DisplayBorders, DrawBackground = !settings.NoBackground @@ -228,32 +271,99 @@ public sealed partial class SimpleRadialMenu : RadialMenu } - -public abstract class RadialMenuOption +/// +/// Abstract representation of a way to specify icon in radial menu. +/// +public abstract record RadialMenuIconSpecifier { - public string? ToolTip { get; init; } + /// Use entity prototype viewer. + public static RadialMenuIconSpecifier? With(EntProtoId? protoId) + { + if (protoId is null) + return null; - public SpriteSpecifier? Sprite { get; init; } - public Color? BackgroundColor { get; set; } - public Color? HoverBackgroundColor { get; set; } + return new RadialMenuEntityPrototypeIconSpecifier(protoId.Value); + } + + /// Use simple texture icon. + public static RadialMenuIconSpecifier? With(SpriteSpecifier? sprite) + { + if (sprite == null) + return null; + + return new RadialMenuTextureIconSpecifier(sprite); + } + + /// Use entity sprite viewer. + public static RadialMenuIconSpecifier? With(EntityUid? entity) + { + if (entity == null) + return null; + + return new RadialMenuEntityIconSpecifier(entity.Value); + } } -public abstract class RadialMenuActionOption(Action onPressed) : RadialMenuOption +/// Marker that should be used to display radial menu icon. +public sealed record RadialMenuEntityIconSpecifier(EntityUid Entity) : RadialMenuIconSpecifier; + +/// Marker that should be used to display radial menu icon. +public sealed record RadialMenuTextureIconSpecifier(SpriteSpecifier Sprite) : RadialMenuIconSpecifier; + +/// Marker that should be used to display radial menu icon. +public sealed record RadialMenuEntityPrototypeIconSpecifier(EntProtoId ProtoId) : RadialMenuIconSpecifier; + +/// Container for common options for radial menu button. +public abstract class RadialMenuOptionBase { + /// Tooltip to be displayed when button is hovered. + public string? ToolTip { get; init; } + + /// + /// Color for button background. + /// Is used only with sector radial (). + /// + public Color? BackgroundColor { get; set; } + /// + /// Color for button background when it is hovered. + /// Is used only with sector radial (). + /// + public Color? HoverBackgroundColor { get; set; } + + /// + /// Specifier that describes icon to be used for radial menu button. + /// + public RadialMenuIconSpecifier? IconSpecifier { get; set; } +} + +/// Base type for model of radial menu button with some action on button pressed. +/// +public abstract class RadialMenuActionOptionBase(Action onPressed) : RadialMenuOptionBase +{ + /// Action to be executed on button press. public Action OnPressed { get; } = onPressed; } -public sealed class RadialMenuActionOption(Action onPressed, T data) - : RadialMenuActionOption(onPressed: () => onPressed(data)); +/// Strong-typed model for radial menu button with action, stores provided data to be used upon button press. +public sealed class RadialMenuActionOption(Action onPressed, T data) : RadialMenuActionOptionBase(onPressed: () => onPressed(data)); -public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection nested, float containerRadius = 100) - : RadialMenuOption +/// +/// Model for radial menu button that represents reference for next layer of radial buttons. +/// +/// List of button models for next layer of menu. +/// Radius for radial menu buttons of next layer. +public sealed class RadialMenuNestedLayerOption(IReadOnlyCollection nested, float containerRadius = 100) : RadialMenuOptionBase { + /// Radius for radial menu buttons of next layer. public float? ContainerRadius { get; } = containerRadius; - public IReadOnlyCollection Nested { get; } = nested; + /// List of button models for next layer of menu. + public IReadOnlyCollection Nested { get; } = nested; } +/// +/// Additional settings for radial menu render. +/// public sealed class SimpleRadialMenuSettings { /// diff --git a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs index c1bb5b5630..fdcc3c45a2 100644 --- a/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs +++ b/Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs @@ -132,12 +132,12 @@ public sealed class EmotesUIController : UIController, IOnStateChanged ConvertToButtons(IEnumerable emotePrototypes) + private IEnumerable ConvertToButtons(IEnumerable emotePrototypes) { var whitelistSystem = EntitySystemManager.GetEntitySystem(); var player = _playerManager.LocalSession?.AttachedEntity; - Dictionary> emotesByCategory = new(); + Dictionary> emotesByCategory = new(); foreach (var emote in emotePrototypes) { if(emote.Category == EmoteCategory.Invalid) @@ -157,19 +157,19 @@ public sealed class EmotesUIController : UIController, IOnStateChanged(); + list = new List(); emotesByCategory.Add(emote.Category, list); } var actionOption = new RadialMenuActionOption(HandleRadialButtonClick, emote) { - Sprite = emote.Icon, + IconSpecifier = RadialMenuIconSpecifier.With(emote.Icon), ToolTip = Loc.GetString(emote.Name) }; list.Add(actionOption); } - var models = new RadialMenuOption[emotesByCategory.Count]; + var models = new RadialMenuOptionBase[emotesByCategory.Count]; var i = 0; foreach (var (key, list) in emotesByCategory) { @@ -177,7 +177,7 @@ public sealed class EmotesUIController : UIController, IOnStateChanged - /// Allows a ghost to take this role, spawning a new entity. + /// Allows a ghost to take this role, spawning a new entity. /// [RegisterComponent, EntityCategory("Spawner")] public sealed partial class GhostRoleMobSpawnerComponent : Component @@ -21,9 +21,9 @@ namespace Content.Shared.Ghost.Roles.Components public EntProtoId? Prototype; /// - /// If this ghostrole spawner has multiple selectable ghostrole prototypes. + /// If this ghostrole spawner has multiple selectable ghostrole prototypes. /// [DataField] - public List SelectablePrototypes = []; + public List> SelectablePrototypes = []; } }