From 4f80dfda0d2e874ea37cdd82ebcbc27a51b0603d Mon Sep 17 00:00:00 2001 From: ShadowCommander <10494922+ShadowCommander@users.noreply.github.com> Date: Thu, 13 Jan 2022 06:28:17 -0800 Subject: [PATCH] Verb confirmation (#6137) --- .../ContextMenu/UI/ContextMenuElement.xaml | 3 +- .../ContextMenu/UI/ContextMenuElement.xaml.cs | 9 +++-- Content.Client/Stylesheets/StyleNano.cs | 29 +++++++++++++++ .../Verbs/UI/ConfirmationMenuElement.cs | 34 ++++++++++++++++++ Content.Client/Verbs/UI/VerbMenuElement.cs | 7 ++++ Content.Client/Verbs/UI/VerbMenuPresenter.cs | 35 ++++++++++++++++--- .../Administration/AdminVerbSystem.cs | 3 ++ Content.Shared/Verbs/Verb.cs | 9 +++-- 8 files changed, 117 insertions(+), 12 deletions(-) create mode 100644 Content.Client/Verbs/UI/ConfirmationMenuElement.cs diff --git a/Content.Client/ContextMenu/UI/ContextMenuElement.xaml b/Content.Client/ContextMenu/UI/ContextMenuElement.xaml index 6b82394ab6..05f02ddbd2 100644 --- a/Content.Client/ContextMenu/UI/ContextMenuElement.xaml +++ b/Content.Client/ContextMenu/UI/ContextMenuElement.xaml @@ -11,13 +11,14 @@ /// Convenience property to set label text. /// - public string Text { set => Label.SetMessage(FormattedMessage.FromMarkupPermissive(value.Trim())); } + public virtual string Text { set => Label.SetMessage(FormattedMessage.FromMarkupPermissive(value.Trim())); } public ContextMenuElement(string? text = null) { @@ -55,9 +56,6 @@ namespace Content.Client.ContextMenu.UI if (text != null) Text = text; - - ExpansionIndicator.Texture = IoCManager.Resolve() - .GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"); } protected override void Dispose(bool disposing) @@ -95,7 +93,8 @@ namespace Content.Client.ContextMenu.UI if (_subMenu?.Visible ?? true) return; - RemoveStylePseudoClass(StylePseudoClassHover); + if (HasStylePseudoClass(StylePseudoClassHover)) + RemoveStylePseudoClass(StylePseudoClassHover); } } } diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs index 1d653adce4..8a344160b2 100644 --- a/Content.Client/Stylesheets/StyleNano.cs +++ b/Content.Client/Stylesheets/StyleNano.cs @@ -446,6 +446,9 @@ namespace Content.Client.Stylesheets }; insetBack.SetPatchMargin(StyleBox.Margin.All, 10); + var contextMenuExpansionTexture = resCache.GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"); + var verbMenuConfirmationTexture = resCache.GetTexture("/Textures/Interface/VerbIcons/group.svg.192dpi.png"); + Stylesheet = new Stylesheet(BaseRules.Concat(new[] { // Window title. @@ -623,6 +626,32 @@ namespace Content.Client.Stylesheets Element().Class(VerbMenuElement.StyleClassVerbOtherText) .Prop(Label.StylePropertyFont, notoSans12), + Element().Class(ContextMenuElement.StyleClassContextMenuExpansionTexture) + .Prop(TextureRect.StylePropertyTexture, contextMenuExpansionTexture), + + Element().Class(VerbMenuElement.StyleClassVerbMenuConfirmationTexture) + .Prop(TextureRect.StylePropertyTexture, verbMenuConfirmationTexture), + + // Context menu confirm buttons + Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) + .Prop(ContainerButton.StylePropertyStyleBox, buttonContext), + + Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) + .Pseudo(ContainerButton.StylePseudoClassNormal) + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDefault), + + Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) + .Pseudo(ContainerButton.StylePseudoClassHover) + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionHovered), + + Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) + .Pseudo(ContainerButton.StylePseudoClassPressed) + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionPressed), + + Element().Class(ConfirmationMenuElement.StyleClassConfirmationContextMenuButton) + .Pseudo(ContainerButton.StylePseudoClassDisabled) + .Prop(Control.StylePropertyModulateSelf, ButtonColorCautionDisabled), + // Thin buttons (No padding nor vertical margin) Element().Class(StyleClassStorageButton) .Prop(ContainerButton.StylePropertyStyleBox, buttonStorage), diff --git a/Content.Client/Verbs/UI/ConfirmationMenuElement.cs b/Content.Client/Verbs/UI/ConfirmationMenuElement.cs new file mode 100644 index 0000000000..35ede2f118 --- /dev/null +++ b/Content.Client/Verbs/UI/ConfirmationMenuElement.cs @@ -0,0 +1,34 @@ +using Content.Client.ContextMenu.UI; +using Content.Shared.Verbs; +using Robust.Shared.Maths; +using Robust.Shared.Utility; + +namespace Content.Client.Verbs.UI; + +public partial class ConfirmationMenuElement : ContextMenuElement +{ + public const string StyleClassConfirmationContextMenuButton = "confirmationContextMenuButton"; + + public readonly Verb Verb; + public readonly VerbType Type; + + public override string Text + { + set + { + var message = new FormattedMessage(); + message.PushColor(Color.White); + message.AddMarkupPermissive(value.Trim()); + Label.SetMessage(message); + } + } + + public ConfirmationMenuElement(Verb verb, string? text, VerbType type) : base(text) + { + Verb = verb; + Type = type; + Icon.Visible = false; + + SetOnlyStyleClass(StyleClassConfirmationContextMenuButton); + } +} diff --git a/Content.Client/Verbs/UI/VerbMenuElement.cs b/Content.Client/Verbs/UI/VerbMenuElement.cs index ab6259f4dd..f8b6ccc00b 100644 --- a/Content.Client/Verbs/UI/VerbMenuElement.cs +++ b/Content.Client/Verbs/UI/VerbMenuElement.cs @@ -16,6 +16,7 @@ namespace Content.Client.Verbs.UI public const string StyleClassVerbActivationText = "ActivationVerb"; public const string StyleClassVerbAlternativeText = "AlternativeVerb"; public const string StyleClassVerbOtherText = "OtherVerb"; + public const string StyleClassVerbMenuConfirmationTexture = "verbMenuConfirmationTexture"; public const float VerbTooltipDelay = 0.5f; @@ -62,6 +63,12 @@ namespace Content.Client.Verbs.UI TooltipDelay = VerbTooltipDelay; Disabled = verb.Disabled; Verb = verb; + + if (verb.ConfirmationPopup) + { + ExpansionIndicator.SetOnlyStyleClass(StyleClassVerbMenuConfirmationTexture); + ExpansionIndicator.Visible = true; + } } public VerbMenuElement(VerbCategory category, VerbType verbType) : this(category.Text, category.Icon, verbType) { } diff --git a/Content.Client/Verbs/UI/VerbMenuPresenter.cs b/Content.Client/Verbs/UI/VerbMenuPresenter.cs index 9410720719..10f3254bd3 100644 --- a/Content.Client/Verbs/UI/VerbMenuPresenter.cs +++ b/Content.Client/Verbs/UI/VerbMenuPresenter.cs @@ -170,7 +170,14 @@ namespace Content.Client.Verbs.UI return; if (element is not VerbMenuElement verbElement) + { + if (element is not ConfirmationMenuElement confElement) + return; + + args.Handle(); + ExecuteVerb(confElement.Verb, confElement.Type); return; + } args.Handle(); var verb = verbElement.Verb; @@ -178,25 +185,45 @@ namespace Content.Client.Verbs.UI if (verb == null) { // The user probably clicked on a verb category. - // We will act as if they clicked on the first verb in that category. + // If there's only one verb in the category, then it will act as if they clicked on that verb. + // Otherwise it opens the category menu. if (verbElement.SubMenu == null || verbElement.SubMenu.ChildCount == 0) return; if (verbElement.SubMenu.MenuBody.ChildCount != 1 - || verbElement.SubMenu.MenuBody.Children.First() is not VerbMenuElement verbCategoryElement) + || verbElement.SubMenu.MenuBody.Children.First() is not VerbMenuElement verbMenuElement) { OpenSubMenu(verbElement); return; } - verb = verbCategoryElement.Verb; + verb = verbMenuElement.Verb; if (verb == null) return; } - _verbSystem.ExecuteVerb(CurrentTarget, verb, verbElement.Type); + if (verb.ConfirmationPopup) + { + if (verbElement.SubMenu == null) + { + var popupElement = new ConfirmationMenuElement(verb, "Confirm", verbElement.Type); + verbElement.SubMenu = new ContextMenuPopup(this, verbElement); + AddElement(verbElement.SubMenu, popupElement); + } + + OpenSubMenu(verbElement); + } + else + { + ExecuteVerb(verb, verbElement.Type); + } + } + + private void ExecuteVerb(Verb verb, VerbType verbType) + { + _verbSystem.ExecuteVerb(CurrentTarget, verb, verbType); if (verb.CloseMenu) _verbSystem.CloseAllMenus(); } diff --git a/Content.Server/Administration/AdminVerbSystem.cs b/Content.Server/Administration/AdminVerbSystem.cs index 9b3efc44aa..37c2712fdb 100644 --- a/Content.Server/Administration/AdminVerbSystem.cs +++ b/Content.Server/Administration/AdminVerbSystem.cs @@ -116,6 +116,7 @@ namespace Content.Server.Administration } }; verb.Impact = LogImpact.Extreme; // if you're just outright killing a person, I guess that deserves to be extreme? + verb.ConfirmationPopup = true; args.Verbs.Add(verb); } } @@ -136,6 +137,7 @@ namespace Content.Server.Administration verb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png"; verb.Act = () => EntityManager.DeleteEntity(args.Target); verb.Impact = LogImpact.Medium; + verb.ConfirmationPopup = true; args.Verbs.Add(verb); } @@ -166,6 +168,7 @@ namespace Content.Server.Administration player.ContentData()?.Mind?.TransferTo(args.Target, ghostCheckOverride: true); }; verb.Impact = LogImpact.High; + verb.ConfirmationPopup = true; args.Verbs.Add(verb); } diff --git a/Content.Shared/Verbs/Verb.cs b/Content.Shared/Verbs/Verb.cs index 58c1b95227..544f4c1cec 100644 --- a/Content.Shared/Verbs/Verb.cs +++ b/Content.Shared/Verbs/Verb.cs @@ -95,7 +95,7 @@ namespace Content.Shared.Verbs public bool Disabled; /// - /// Optional informative message. + /// Optional informative message. /// /// /// This will be shown as a tooltip when hovering over this verb in the context menu. Additionally, iF a @@ -135,6 +135,11 @@ namespace Content.Shared.Verbs /// public LogImpact Impact = LogImpact.Low; + /// + /// Whether this verb requires confirmation before being executed. + /// + public bool ConfirmationPopup = false; + /// /// Compares two verbs based on their , , , /// and . @@ -165,7 +170,7 @@ namespace Content.Shared.Verbs { return string.Compare(Category?.Text, otherVerb.Category?.Text, StringComparison.CurrentCulture); } - + // Then try use alphabetical verb text. if (Text != otherVerb.Text) {