Make role ban pannel pretty (#37952)

* 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>
This commit is contained in:
beck-thompson
2025-06-21 10:54:11 -07:00
committed by GitHub
parent 8d2f46ae34
commit a15387bcfd
3 changed files with 144 additions and 52 deletions

View File

@@ -1,7 +1,7 @@
<DefaultWindow <DefaultWindow
xmlns="https://spacestation14.io" xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" 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"> <BoxContainer Orientation="Vertical">
<TabContainer Name="Tabs" VerticalExpand="True"> <TabContainer Name="Tabs" VerticalExpand="True">
<!-- Basic info --> <!-- Basic info -->

View File

@@ -1,12 +1,14 @@
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using System.Numerics;
using Content.Client.Administration.UI.CustomControls; using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Roles; using Content.Shared.Roles;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics; using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
@@ -31,14 +33,21 @@ public sealed partial class BanPanel : DefaultWindow
private uint Multiplier { get; set; } private uint Multiplier { get; set; }
private bool HasBanFlag { get; set; } private bool HasBanFlag { get; set; }
private TimeSpan? ButtonResetOn { 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 // 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. // 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; private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = 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 private enum TabNumbers
{ {
@@ -144,47 +153,90 @@ public sealed partial class BanPanel : DefaultWindow
ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason")); ReasonTextEdit.Placeholder = new Rope.Leaf(Loc.GetString("ban-panel-reason"));
var prototypeManager = IoCManager.Resolve<IPrototypeManager>(); var departmentJobs = _protoMan.EnumeratePrototypes<DepartmentPrototype>()
foreach (var proto in prototypeManager.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 var outerContainer = new BoxContainer
{ {
Name = $"{roleName}GroupOuterBox", Name = $"{groupName}GroupOuterBox",
HorizontalExpand = true, HorizontalExpand = true,
VerticalExpand = true, VerticalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical, 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", Orientation = BoxContainer.LayoutOrientation.Horizontal,
Text = roleName,
Modulate = color,
HorizontalAlignment = HAlignment.Left
}; };
outerContainer.AddChild(departmentCheckbox);
var innerContainer = new BoxContainer // Stores the role checkboxes themselves.
var innerContainer = new GridContainer
{ {
Name = $"{roleName}GroupInnerBox", Name = $"{groupName}GroupInnerBox",
HorizontalExpand = true, 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) BackgroundColor = color
{ }
c.Pressed = args.Pressed; });
} 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) if (args.Pressed)
@@ -199,15 +251,12 @@ public sealed partial class BanPanel : DefaultWindow
} }
else 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 (button.Pressed)
{ return;
if (child is CheckBox { Pressed: true })
return;
}
} }
} }
@@ -220,38 +269,72 @@ public sealed partial class BanPanel : DefaultWindow
SeverityOption.SelectId((int) newSeverity); SeverityOption.SelectId((int) newSeverity);
} }
}; };
outerContainer.AddChild(innerContainer);
foreach (var role in roleList) var hideButton = new Button
{ {
AddRoleCheckbox(role, innerContainer, departmentCheckbox); Text = Loc.GetString("role-bans-expand-roles") + " " + ContractedArrow,
} ToggleMode = true,
RolesContainer.AddChild(new PanelContainer };
hideButton.OnPressed += args =>
{ {
PanelOverride = new StyleBoxFlat innerContainer.Visible = args.Button.Pressed;
{ ((Button)args.Button).Text = args.Button.Pressed
BackgroundColor = color ? 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); header.AddChild(roleGroupCheckbox);
RolesContainer.AddChild(new HSeparator()); 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", 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)) // Checks the role group checkbox if all the children are pressed
header.Pressed = args.Pressed; if (args.Pressed && _roleCheckboxes[group].All(e => e.Pressed))
roleGroupCheckbox.Pressed = args.Pressed;
else 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) public void UpdateBanFlag(bool newFlag)
@@ -469,7 +552,13 @@ public sealed partial class BanPanel : DefaultWindow
if (_roleCheckboxes.Count == 0) if (_roleCheckboxes.Count == 0)
throw new DebugAssertException("RoleCheckboxes was empty"); 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) if (rolesList.Count == 0)
{ {

View File

@@ -0,0 +1,3 @@
role-bans-ban-group = Ban All
role-bans-expand-roles = Show Roles
role-bans-contract-roles = Hide Roles