]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Make role ban pannel pretty (#37952)
authorbeck-thompson <107373427+beck-thompson@users.noreply.github.com>
Sat, 21 Jun 2025 17:54:11 +0000 (10:54 -0700)
committerGitHub <noreply@github.com>
Sat, 21 Jun 2025 17:54:11 +0000 (10:54 -0700)
* Make role ban pannel pretty

* Removed unused depencency

* refactor: wider panel (no jumping due to scroll in english lang) minor readability improvements

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Content.Client/Administration/UI/BanPanel/BanPanel.xaml
Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
Resources/Locale/en-US/administration/ui/role-bans.ftl [new file with mode: 0644]

index 333184f1c0e40282c3efd9cc655f748dff6667e2..ede0ad3ee506075a74dbbff395e81ef928018037 100644 (file)
@@ -1,7 +1,7 @@
 <DefaultWindow
     xmlns="https://spacestation14.io"
     xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
-    Title="{Loc ban-panel-title}" MinSize="350 500">
+    Title="{Loc ban-panel-title}" MinSize="410 500">
     <BoxContainer Orientation="Vertical">
         <TabContainer Name="Tabs" VerticalExpand="True">
             <!-- Basic info -->
index 3c7322d4739703eb1a19f92cd9f5da974bbfe003..46090a6f3d04bca79885d87dc5df28bf4e7c7170 100644 (file)
@@ -1,12 +1,14 @@
 using System.Linq;
 using System.Net;
 using System.Net.Sockets;
+using System.Numerics;
 using Content.Client.Administration.UI.CustomControls;
 using Content.Shared.Administration;
 using Content.Shared.CCVar;
 using Content.Shared.Database;
 using Content.Shared.Roles;
 using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
@@ -31,14 +33,21 @@ public sealed partial class BanPanel : DefaultWindow
     private uint Multiplier { get; set; }
     private bool HasBanFlag { get; set; }
     private TimeSpan? ButtonResetOn { get; set; }
+
     // This is less efficient than just holding a reference to the root control and enumerating children, but you
     // have to know how the controls are nested, which makes the code more complicated.
-    private readonly List<CheckBox> _roleCheckboxes = new();
+    // Role group name -> the role buttons themselves.
+    private readonly Dictionary<string, List<Button>> _roleCheckboxes = new();
     private readonly ISawmill _banpanelSawmill;
 
     [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly IConfigurationManager _cfg = default!;
     [Dependency] private readonly ILogManager _logManager = default!;
+    [Dependency] private readonly IEntityManager _entMan = default!;
+    [Dependency] private readonly IPrototypeManager _protoMan = default!;
+
+    private const string ExpandedArrow = "▼";
+    private const string ContractedArrow = "▶";
 
     private enum TabNumbers
     {
@@ -144,47 +153,90 @@ public sealed partial class BanPanel : DefaultWindow
 
         ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
 
-        var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
-        foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
+        var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
+                                      .OrderBy(x => x.Weight);
+        foreach (var proto in departmentJobs)
         {
-            CreateRoleGroup(proto.ID, proto.Roles.Select(p =>  p.Id), proto.Color);
+            var roles = proto.Roles.Select(x => _protoMan.Index(x))
+                             .OrderBy(x => x.ID);
+            CreateRoleGroup(proto.ID, proto.Color, roles);
         }
 
-        CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
+        var antagRoles = _protoMan.EnumeratePrototypes<AntagPrototype>()
+                                  .OrderBy(x => x.ID);
+        CreateRoleGroup("Antagonist", Color.Red, antagRoles);
     }
 
-    private void CreateRoleGroup(string roleName, IEnumerable<string> roleList, Color color)
+    /// <summary>
+    /// Creates a "Role group" which stores information and logic for one "group" of roll bans.
+    /// For example, all antags are one group, logi is a group, medical is a group, etc...
+    /// </summary>
+    private void CreateRoleGroup<T>(string groupName, Color color, IEnumerable<T> roles) where T : class, IPrototype
     {
         var outerContainer = new BoxContainer
         {
-            Name = $"{roleName}GroupOuterBox",
+            Name = $"{groupName}GroupOuterBox",
             HorizontalExpand = true,
             VerticalExpand = true,
             Orientation = BoxContainer.LayoutOrientation.Vertical,
-            Margin = new Thickness(4)
+            Margin = new Thickness(4),
         };
-        var departmentCheckbox = new CheckBox
+
+        // Stores stuff like ban all and expand buttons.
+        var roleGroupHeader = new BoxContainer
         {
-            Name = $"{roleName}GroupCheckbox",
-            Text = roleName,
-            Modulate = color,
-            HorizontalAlignment = HAlignment.Left
+            Orientation = BoxContainer.LayoutOrientation.Horizontal,
         };
-        outerContainer.AddChild(departmentCheckbox);
-        var innerContainer = new BoxContainer
+
+        // Stores the role checkboxes themselves.
+        var innerContainer = new GridContainer
         {
-            Name = $"{roleName}GroupInnerBox",
+            Name = $"{groupName}GroupInnerBox",
             HorizontalExpand = true,
-            Orientation = BoxContainer.LayoutOrientation.Horizontal
+            Columns = 2,
+            Visible = false,
+            Margin = new Thickness(15, 5, 0, 5),
         };
-        departmentCheckbox.OnToggled += args =>
+
+        var roleGroupCheckbox = CreateRoleGroupHeader(groupName, roleGroupHeader, color, innerContainer);
+
+        outerContainer.AddChild(roleGroupHeader);
+
+        // Add the roles themselves
+        foreach (var role in roles)
+        {
+            AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox);
+        }
+
+        outerContainer.AddChild(innerContainer);
+
+        RolesContainer.AddChild(new PanelContainer
         {
-            foreach (var child in innerContainer.Children)
+            PanelOverride = new StyleBoxFlat
             {
-                if (child is CheckBox c)
-                {
-                    c.Pressed = args.Pressed;
-                }
+                BackgroundColor = color
+            }
+        });
+        RolesContainer.AddChild(outerContainer);
+        RolesContainer.AddChild(new HSeparator());
+    }
+
+    private Button CreateRoleGroupHeader(string groupName, BoxContainer header, Color color, GridContainer innerContainer)
+    {
+        var roleGroupCheckbox = new Button
+        {
+            Name = $"{groupName}GroupCheckbox",
+            Text = "Ban all",
+            Margin = new Thickness(0, 0, 5, 0),
+            ToggleMode = true,
+        };
+
+        // When this is toggled, toggle all buttons in this group so they match.
+        roleGroupCheckbox.OnToggled += args =>
+        {
+            foreach (var role in _roleCheckboxes[groupName])
+            {
+                role.Pressed = args.Pressed;
             }
 
             if (args.Pressed)
@@ -199,15 +251,12 @@ public sealed partial class BanPanel : DefaultWindow
             }
             else
             {
-                foreach (var childContainer in RolesContainer.Children)
+                foreach (var roleButtons in _roleCheckboxes.Values)
                 {
-                    if (childContainer is Container)
+                    foreach (var button in roleButtons)
                     {
-                        foreach (var child in childContainer.Children)
-                        {
-                            if (child is CheckBox { Pressed: true })
-                                return;
-                        }
+                        if (button.Pressed)
+                            return;
                     }
                 }
 
@@ -220,38 +269,72 @@ public sealed partial class BanPanel : DefaultWindow
                 SeverityOption.SelectId((int) newSeverity);
             }
         };
-        outerContainer.AddChild(innerContainer);
-        foreach (var role in roleList)
+
+        var hideButton = new Button
         {
-            AddRoleCheckbox(role, innerContainer, departmentCheckbox);
-        }
-        RolesContainer.AddChild(new PanelContainer
+            Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
+            ToggleMode = true,
+        };
+        hideButton.OnPressed += args =>
         {
-            PanelOverride = new StyleBoxFlat
-            {
-                BackgroundColor = color
-            }
+            innerContainer.Visible = args.Button.Pressed;
+            ((Button)args.Button).Text = args.Button.Pressed
+                ? Loc.GetString("role-bans-contract-roles") + " " + ExpandedArrow
+                : Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow;
+        };
+        header.AddChild(new Label
+        {
+            Text = groupName,
+            Modulate = color,
+            Margin = new Thickness(0, 0, 5, 0),
         });
-        RolesContainer.AddChild(outerContainer);
-        RolesContainer.AddChild(new HSeparator());
+        header.AddChild(roleGroupCheckbox);
+        header.AddChild(hideButton);
+        return roleGroupCheckbox;
     }
 
-    private void AddRoleCheckbox(string role, Control container, CheckBox header)
+    /// <summary>
+    /// Adds a checkbutton specifically for one "role" in a "group"
+    /// E.g. it would add the Chief Medical Officer "role" into the "Medical" group.
+    /// </summary>
+    private void AddRoleCheckbox(string group, string role, GridContainer roleGroupInnerContainer, Button roleGroupCheckbox)
     {
-        var roleCheckbox = new CheckBox
+        var roleCheckboxContainer = new BoxContainer();
+        var roleCheckButton = new Button
         {
             Name = $"{role}RoleCheckbox",
-            Text = role
+            Text = role,
+            ToggleMode = true,
         };
-        roleCheckbox.OnToggled += args =>
+        roleCheckButton.OnToggled += args =>
         {
-            if (args is { Pressed: true, Button.Parent: { } } && args.Button.Parent.Children.Where(e => e is CheckBox).All(e => ((CheckBox) e).Pressed))
-                header.Pressed = args.Pressed;
+            // Checks the role group checkbox if all the children are pressed
+            if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
+                roleGroupCheckbox.Pressed = args.Pressed;
             else
-                header.Pressed = false;
+                roleGroupCheckbox.Pressed = false;
         };
-        container.AddChild(roleCheckbox);
-        _roleCheckboxes.Add(roleCheckbox);
+
+        // This is adding the icon before the role name
+        // Yeah, this is sus, but having to split the functions up and stuff is worse imo.
+        if (_protoMan.TryIndex<JobPrototype>(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto))
+        {
+            var jobIconTexture = new TextureRect
+            {
+                Texture = _entMan.System<SpriteSystem>().Frame0(iconProto.Icon),
+                TextureScale = new Vector2(2.5f, 2.5f),
+                Stretch = TextureRect.StretchMode.KeepCentered,
+                Margin = new Thickness(5, 0, 0, 0),
+            };
+            roleCheckboxContainer.AddChild(jobIconTexture);
+        }
+
+        roleCheckboxContainer.AddChild(roleCheckButton);
+
+        roleGroupInnerContainer.AddChild(roleCheckboxContainer);
+
+        _roleCheckboxes.TryAdd(group, []);
+        _roleCheckboxes[group].Add(roleCheckButton);
     }
 
     public void UpdateBanFlag(bool newFlag)
@@ -469,7 +552,13 @@ public sealed partial class BanPanel : DefaultWindow
             if (_roleCheckboxes.Count == 0)
                 throw new DebugAssertException("RoleCheckboxes was empty");
 
-            rolesList.AddRange(_roleCheckboxes.Where(c => c is { Pressed: true, Text: { } }).Select(c => c.Text!));
+            foreach (var button in _roleCheckboxes.Values.SelectMany(departmentButtons => departmentButtons))
+            {
+                if (button is { Pressed: true, Text: not null })
+                {
+                    rolesList.Add(button.Text);
+                }
+            }
 
             if (rolesList.Count == 0)
             {
diff --git a/Resources/Locale/en-US/administration/ui/role-bans.ftl b/Resources/Locale/en-US/administration/ui/role-bans.ftl
new file mode 100644 (file)
index 0000000..97109fd
--- /dev/null
@@ -0,0 +1,3 @@
+role-bans-ban-group = Ban All
+role-bans-expand-roles = Show Roles
+role-bans-contract-roles = Hide Roles