diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml index 333184f1c0..ede0ad3ee5 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml @@ -1,7 +1,7 @@ + Title="{Loc ban-panel-title}" MinSize="410 500"> diff --git a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs index 3c7322d473..46090a6f3d 100644 --- a/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs +++ b/Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs @@ -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 _roleCheckboxes = new(); + // Role group name -> the role buttons themselves. + private readonly Dictionary> _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(); - foreach (var proto in prototypeManager.EnumeratePrototypes()) + var departmentJobs = _protoMan.EnumeratePrototypes() + .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().Select(p => p.ID), Color.Red); + var antagRoles = _protoMan.EnumeratePrototypes() + .OrderBy(x => x.ID); + CreateRoleGroup("Antagonist", Color.Red, antagRoles); } - private void CreateRoleGroup(string roleName, IEnumerable roleList, Color color) + /// + /// 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... + /// + private void CreateRoleGroup(string groupName, Color color, IEnumerable 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) { - foreach (var child in innerContainer.Children) + AddRoleCheckbox(groupName, role.ID, innerContainer, roleGroupCheckbox); + } + + outerContainer.AddChild(innerContainer); + + RolesContainer.AddChild(new PanelContainer + { + 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) + /// + /// Adds a checkbutton specifically for one "role" in a "group" + /// E.g. it would add the Chief Medical Officer "role" into the "Medical" group. + /// + 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(role, out var jobPrototype) && _protoMan.TryIndex(jobPrototype.Icon, out var iconProto)) + { + var jobIconTexture = new TextureRect + { + Texture = _entMan.System().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 index 0000000000..97109fdc7f --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/role-bans.ftl @@ -0,0 +1,3 @@ +role-bans-ban-group = Ban All +role-bans-expand-roles = Show Roles +role-bans-contract-roles = Hide Roles