diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml index 502d7ca515..07b7d8033d 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml @@ -1,4 +1,6 @@ - + diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs index 30d0bfa514..3d1eee1a4a 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using Content.Client.Administration.UI.CustomControls; using Content.Shared.Administration; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; @@ -8,16 +5,22 @@ using Robust.Client.Player; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; +using static Content.Client.Administration.UI.Tabs.PlayerTab.PlayerTabHeader; namespace Content.Client.Administration.UI.Tabs.PlayerTab { [GenerateTypedNameReferences] public sealed partial class PlayerTab : Control { + private const string ArrowUp = "↑"; + private const string ArrowDown = "↓"; + private readonly Color _altColor = Color.FromHex("#292B38"); + private readonly Color _defaultColor = Color.FromHex("#2F2F3B"); private readonly AdminSystem _adminSystem; + private readonly List _players = new(); + + private Header _headerClicked = Header.Username; + private bool _ascending = true; public event Action? OnEntryPressed; @@ -29,6 +32,9 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab _adminSystem.PlayerListChanged += RefreshPlayerList; OverlayButtonOn.OnPressed += _adminSystem.AdminOverlayOn; OverlayButtonOff.OnPressed += _adminSystem.AdminOverlayOff; + + ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor); + ListHeader.OnHeaderClicked += HeaderClicked; } protected override void Dispose(bool disposing) @@ -42,24 +48,20 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab private void RefreshPlayerList(IReadOnlyList players) { - PlayerList.RemoveAllChildren(); + foreach (var control in _players) + { + PlayerList.RemoveChild(control); + } + + _players.Clear(); + var playerManager = IoCManager.Resolve(); PlayerCount.Text = $"Players: {playerManager.PlayerCount}"; - var altColor = Color.FromHex("#292B38"); - var defaultColor = Color.FromHex("#2F2F3B"); - - PlayerList.AddChild(new PlayerTabEntry("Username", - "Character", - "Job", - "Antagonist", - new StyleBoxFlat(altColor), - true)); - PlayerList.AddChild(new HSeparator()); - - // Temporary until we can sort by var sortedPlayers = new List(players); - sortedPlayers.Sort((x, y) => string.Compare(x.Username, y.Username, StringComparison.Ordinal)); + sortedPlayers.Sort(Compare); + + UpdateHeaderSymbols(); var useAltColor = false; foreach (var player in sortedPlayers) @@ -68,14 +70,58 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab player.CharacterName, player.StartingJob, player.Antag ? "YES" : "NO", - new StyleBoxFlat(useAltColor ? altColor : defaultColor), + new StyleBoxFlat(useAltColor ? _altColor : _defaultColor), player.Connected); entry.PlayerUid = player.EntityUid; entry.OnPressed += args => OnEntryPressed?.Invoke(args); PlayerList.AddChild(entry); + _players.Add(entry); useAltColor ^= true; } } + + private void UpdateHeaderSymbols() + { + ListHeader.ResetHeaderText(); + ListHeader.GetHeader(_headerClicked).Text += $" {(_ascending ? ArrowUp : ArrowDown)}"; + } + + private int Compare(PlayerInfo x, PlayerInfo y) + { + if (!_ascending) + { + (x, y) = (y, x); + } + + return _headerClicked switch + { + Header.Username => Compare(x.Username, y.Username), + Header.Character => Compare(x.CharacterName, y.CharacterName), + Header.Job => Compare(x.StartingJob, y.StartingJob), + Header.Antagonist => x.Antag.CompareTo(y.Antag), + _ => 1 + }; + } + + private int Compare(string x, string y) + { + return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); + } + + private void HeaderClicked(Header header) + { + if (_headerClicked == header) + { + _ascending = !_ascending; + } + else + { + _headerClicked = header; + _ascending = true; + } + + RefreshPlayerList(_adminSystem.PlayerList); + } } } diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs index fe4bc0a4b4..1ab6071e03 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs @@ -2,7 +2,6 @@ using Robust.Client.Graphics; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; -using Robust.Shared.GameObjects; namespace Content.Client.Administration.UI.Tabs.PlayerTab; diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml new file mode 100644 index 0000000000..022aeffba7 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml @@ -0,0 +1,36 @@ + + + + + diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml.cs b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml.cs new file mode 100644 index 0000000000..08e414b655 --- /dev/null +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabHeader.xaml.cs @@ -0,0 +1,95 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Input; + +namespace Content.Client.Administration.UI.Tabs.PlayerTab; + +[GenerateTypedNameReferences] +public sealed partial class PlayerTabHeader : ContainerButton +{ + public event Action
? OnHeaderClicked; + + public PlayerTabHeader() + { + RobustXamlLoader.Load(this); + + UsernameLabel.OnKeyBindDown += UsernameClicked; + CharacterLabel.OnKeyBindDown += CharacterClicked; + JobLabel.OnKeyBindDown += JobClicked; + AntagonistLabel.OnKeyBindDown += AntagonistClicked; + } + + public Label GetHeader(Header header) + { + return header switch + { + Header.Username => UsernameLabel, + Header.Character => CharacterLabel, + Header.Job => JobLabel, + Header.Antagonist => AntagonistLabel, + _ => throw new ArgumentOutOfRangeException(nameof(header), header, null) + }; + } + + public void ResetHeaderText() + { + UsernameLabel.Text = Loc.GetString("player-tab-username"); + CharacterLabel.Text = Loc.GetString("player-tab-character"); + JobLabel.Text = Loc.GetString("player-tab-job"); + AntagonistLabel.Text = Loc.GetString("player-tab-antagonist"); + } + + private void HeaderClicked(GUIBoundKeyEventArgs args, Header header) + { + if (args.Function != EngineKeyFunctions.UIClick) + { + return; + } + + OnHeaderClicked?.Invoke(header); + args.Handle(); + } + + private void UsernameClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.Username); + } + + private void CharacterClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.Character); + } + + private void JobClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.Job); + } + + private void AntagonistClicked(GUIBoundKeyEventArgs args) + { + HeaderClicked(args, Header.Antagonist); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) + { + UsernameLabel.OnKeyBindDown += UsernameClicked; + CharacterLabel.OnKeyBindDown += CharacterClicked; + JobLabel.OnKeyBindDown += JobClicked; + AntagonistLabel.OnKeyBindDown += AntagonistClicked; + } + } + + public enum Header + { + Username, + Character, + Job, + Antagonist + } +} diff --git a/Resources/Locale/en-US/administration/ui/tabs/player-tab.ftl b/Resources/Locale/en-US/administration/ui/tabs/player-tab.ftl new file mode 100644 index 0000000000..315ff412f7 --- /dev/null +++ b/Resources/Locale/en-US/administration/ui/tabs/player-tab.ftl @@ -0,0 +1,4 @@ +player-tab-username = Username +player-tab-character = Character +player-tab-job = Job +player-tab-antagonist = Antagonist