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)
{