diff --git a/.envrc b/.envrc index 7fd05db3e5..d2ab6182d8 100644 --- a/.envrc +++ b/.envrc @@ -1,4 +1,4 @@ -if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then - source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" +if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=" fi -use flake +use nix diff --git a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs index 6025c3b551..b5c480ff71 100644 --- a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs +++ b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs @@ -32,7 +32,7 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype { if (!prototypeManager.TryIndex(access, out var accessLevel)) { - logMill.Error($"Unable to find accesslevel for {access}"); + logMill.Error($"Unable to find access level for {access}"); continue; } @@ -66,11 +66,11 @@ public void UpdateState(AccessOverriderBoundUserInterfaceState state) if (state.MissingPrivilegesList != null && state.MissingPrivilegesList.Any()) { - List missingPrivileges = new List(); + var missingPrivileges = new List(); foreach (string tag in state.MissingPrivilegesList) { - string privilege = Loc.GetString(_prototypeManager.Index(tag)?.Name ?? "generic-unknown"); + var privilege = Loc.GetString(_prototypeManager.Index(tag)?.Name ?? "generic-unknown"); missingPrivileges.Add(privilege); } @@ -83,20 +83,20 @@ public void UpdateState(AccessOverriderBoundUserInterfaceState state) foreach (var (accessName, button) in _accessButtons) { button.Disabled = !interfaceEnabled; - if (interfaceEnabled) - { - button.Pressed = state.TargetAccessReaderIdAccessList?.Contains(accessName) ?? false; - button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true; - } + if (!interfaceEnabled) + return; + + button.Pressed = state.TargetAccessReaderIdAccessList?.Contains>(accessName) ?? false; + button.Disabled = (!state.AllowedModifyAccessList?.Contains>(accessName)) ?? true; } } - private void SubmitData() - { + private void SubmitData() => _owner.SubmitData( - // Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair - _accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId(x.Key)).ToList()); - } + _accessButtons.Where(x => x.Value.Pressed) + .Select(x => new ProtoId(x.Key)) + .ToList() + ); } } diff --git a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs index f8d06f758f..6f6c1c8f6e 100644 --- a/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs +++ b/Content.Client/Administration/UI/Bwoink/BwoinkWindow.xaml.cs @@ -16,18 +16,25 @@ public BwoinkWindow() Bwoink.ChannelSelector.OnSelectionChanged += sel => { - if (sel is not null) + if (sel is null) { - Title = $"{sel.CharacterName} / {sel.Username}"; + Title = Loc.GetString("bwoink-none-selected"); + return; + } + + Title = $"{sel.CharacterName} / {sel.Username}"; - if (sel.OverallPlaytime != null) - { - Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; - } + if (sel.OverallPlaytime != null) + { + Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}"; } }; - OnOpen += () => Bwoink.PopulateList(); + OnOpen += () => + { + Bwoink.ChannelSelector.StopFiltering(); + Bwoink.PopulateList(); + }; } } } diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs index fdf935d7c0..b09cd727ef 100644 --- a/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs +++ b/Content.Client/Administration/UI/CustomControls/PlayerListControl.xaml.cs @@ -4,147 +4,155 @@ using Content.Client.Verbs.UI; using Content.Shared.Administration; using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Input; +using Robust.Shared.Utility; -namespace Content.Client.Administration.UI.CustomControls +namespace Content.Client.Administration.UI.CustomControls; + +[GenerateTypedNameReferences] +public sealed partial class PlayerListControl : BoxContainer { - [GenerateTypedNameReferences] - public sealed partial class PlayerListControl : BoxContainer - { - private readonly AdminSystem _adminSystem; + private readonly AdminSystem _adminSystem; - private List _playerList = new(); - private readonly List _sortedPlayerList = new(); + private readonly IEntityManager _entManager; + private readonly IUserInterfaceManager _uiManager; + + private PlayerInfo? _selectedPlayer; - public event Action? OnSelectionChanged; - public IReadOnlyList PlayerInfo => _playerList; + private List _playerList = new(); + private readonly List _sortedPlayerList = new(); - public Func? OverrideText; - public Comparison? Comparison; + public Comparison? Comparison; + public Func? OverrideText; - private IEntityManager _entManager; - private IUserInterfaceManager _uiManager; + public PlayerListControl() + { + _entManager = IoCManager.Resolve(); + _uiManager = IoCManager.Resolve(); + _adminSystem = _entManager.System(); + RobustXamlLoader.Load(this); + // Fill the Option data + PlayerListContainer.ItemPressed += PlayerListItemPressed; + PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown; + PlayerListContainer.GenerateItem += GenerateButton; + PlayerListContainer.NoItemSelected += PlayerListNoItemSelected; + PopulateList(_adminSystem.PlayerList); + FilterLineEdit.OnTextChanged += _ => FilterList(); + _adminSystem.PlayerListChanged += PopulateList; + BackgroundPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 40) }; + } - private PlayerInfo? _selectedPlayer; + public IReadOnlyList PlayerInfo => _playerList; - public PlayerListControl() - { - _entManager = IoCManager.Resolve(); - _uiManager = IoCManager.Resolve(); - _adminSystem = _entManager.System(); - RobustXamlLoader.Load(this); - // Fill the Option data - PlayerListContainer.ItemPressed += PlayerListItemPressed; - PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown; - PlayerListContainer.GenerateItem += GenerateButton; - PopulateList(_adminSystem.PlayerList); - FilterLineEdit.OnTextChanged += _ => FilterList(); - _adminSystem.PlayerListChanged += PopulateList; - BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)}; - } + public event Action? OnSelectionChanged; - private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data) - { - if (args == null || data is not PlayerListData {Info: var selectedPlayer}) - return; + private void PlayerListNoItemSelected() + { + _selectedPlayer = null; + OnSelectionChanged?.Invoke(null); + } - if (selectedPlayer == _selectedPlayer) - return; + private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data) + { + if (args == null || data is not PlayerListData { Info: var selectedPlayer }) + return; - if (args.Event.Function != EngineKeyFunctions.UIClick) - return; + if (selectedPlayer == _selectedPlayer) + return; - OnSelectionChanged?.Invoke(selectedPlayer); - _selectedPlayer = selectedPlayer; + if (args.Event.Function != EngineKeyFunctions.UIClick) + return; - // update label text. Only required if there is some override (e.g. unread bwoink count). - if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label) - label.Text = GetText(selectedPlayer); - } + OnSelectionChanged?.Invoke(selectedPlayer); + _selectedPlayer = selectedPlayer; - private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data) - { - if (args == null || data is not PlayerListData { Info: var selectedPlayer }) - return; + // update label text. Only required if there is some override (e.g. unread bwoink count). + if (OverrideText != null && args.Button.Children.FirstOrDefault()?.Children?.FirstOrDefault() is Label label) + label.Text = GetText(selectedPlayer); + } - if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null) - return; + private void PlayerListItemKeyBindDown(GUIBoundKeyEventArgs? args, ListData? data) + { + if (args == null || data is not PlayerListData { Info: var selectedPlayer }) + return; - _uiManager.GetUIController().OpenVerbMenu(selectedPlayer.NetEntity.Value, true); - args.Handle(); - } + if (args.Function != EngineKeyFunctions.UIRightClick || selectedPlayer.NetEntity == null) + return; - public void StopFiltering() - { - FilterLineEdit.Text = string.Empty; - } + _uiManager.GetUIController().OpenVerbMenu(selectedPlayer.NetEntity.Value, true); + args.Handle(); + } - private void FilterList() + public void StopFiltering() + { + FilterLineEdit.Text = string.Empty; + } + + private void FilterList() + { + _sortedPlayerList.Clear(); + foreach (var info in _playerList) { - _sortedPlayerList.Clear(); - foreach (var info in _playerList) - { - var displayName = $"{info.CharacterName} ({info.Username})"; - if (info.IdentityName != info.CharacterName) - displayName += $" [{info.IdentityName}]"; - if (!string.IsNullOrEmpty(FilterLineEdit.Text) - && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant())) - continue; - _sortedPlayerList.Add(info); - } - - if (Comparison != null) - _sortedPlayerList.Sort((a, b) => Comparison(a, b)); - - PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList()); - if (_selectedPlayer != null) - PlayerListContainer.Select(new PlayerListData(_selectedPlayer)); + var displayName = $"{info.CharacterName} ({info.Username})"; + if (info.IdentityName != info.CharacterName) + displayName += $" [{info.IdentityName}]"; + if (!string.IsNullOrEmpty(FilterLineEdit.Text) + && !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant())) + continue; + _sortedPlayerList.Add(info); } - public void PopulateList(IReadOnlyList? players = null) - { - players ??= _adminSystem.PlayerList; + if (Comparison != null) + _sortedPlayerList.Sort((a, b) => Comparison(a, b)); - _playerList = players.ToList(); - if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer)) - _selectedPlayer = null; + // Ensure pinned players are always at the top + _sortedPlayerList.Sort((a, b) => a.IsPinned != b.IsPinned && a.IsPinned ? -1 : 1); - FilterList(); - } + PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList()); + if (_selectedPlayer != null) + PlayerListContainer.Select(new PlayerListData(_selectedPlayer)); + } - private string GetText(PlayerInfo info) - { - var text = $"{info.CharacterName} ({info.Username})"; - if (OverrideText != null) - text = OverrideText.Invoke(info, text); - return text; - } + public void PopulateList(IReadOnlyList? players = null) + { + players ??= _adminSystem.PlayerList; - private void GenerateButton(ListData data, ListContainerButton button) - { - if (data is not PlayerListData { Info: var info }) - return; - - button.AddChild(new BoxContainer - { - Orientation = LayoutOrientation.Vertical, - Children = - { - new Label - { - ClipText = true, - Text = GetText(info) - } - } - }); - - button.AddStyleClass(ListContainer.StyleClassListContainerButton); - } + _playerList = players.ToList(); + if (_selectedPlayer != null && !_playerList.Contains(_selectedPlayer)) + _selectedPlayer = null; + + FilterList(); } - public record PlayerListData(PlayerInfo Info) : ListData; + + private string GetText(PlayerInfo info) + { + var text = $"{info.CharacterName} ({info.Username})"; + if (OverrideText != null) + text = OverrideText.Invoke(info, text); + return text; + } + + private void GenerateButton(ListData data, ListContainerButton button) + { + if (data is not PlayerListData { Info: var info }) + return; + + var entry = new PlayerListEntry(); + entry.Setup(info, OverrideText); + entry.OnPinStatusChanged += _ => + { + FilterList(); + }; + + button.AddChild(entry); + button.AddStyleClass(ListContainer.StyleClassListContainerButton); + } } + +public record PlayerListData(PlayerInfo Info) : ListData; diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml new file mode 100644 index 0000000000..af13ccc0e0 --- /dev/null +++ b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml @@ -0,0 +1,6 @@ + + diff --git a/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs new file mode 100644 index 0000000000..cd6a56ea71 --- /dev/null +++ b/Content.Client/Administration/UI/CustomControls/PlayerListEntry.xaml.cs @@ -0,0 +1,58 @@ +using Content.Client.Stylesheets; +using Content.Shared.Administration; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Utility; + +namespace Content.Client.Administration.UI.CustomControls; + +[GenerateTypedNameReferences] +public sealed partial class PlayerListEntry : BoxContainer +{ + public PlayerListEntry() + { + RobustXamlLoader.Load(this); + } + + public event Action? OnPinStatusChanged; + + public void Setup(PlayerInfo info, Func? overrideText) + { + Update(info, overrideText); + PlayerEntryPinButton.OnPressed += HandlePinButtonPressed(info); + } + + private Action HandlePinButtonPressed(PlayerInfo info) + { + return args => + { + info.IsPinned = !info.IsPinned; + UpdatePinButtonTexture(info.IsPinned); + OnPinStatusChanged?.Invoke(info); + }; + } + + private void Update(PlayerInfo info, Func? overrideText) + { + PlayerEntryLabel.Text = overrideText?.Invoke(info, $"{info.CharacterName} ({info.Username})") ?? + $"{info.CharacterName} ({info.Username})"; + + UpdatePinButtonTexture(info.IsPinned); + } + + private void UpdatePinButtonTexture(bool isPinned) + { + if (isPinned) + { + PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonUnpinned); + PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonPinned); + } + else + { + PlayerEntryPinButton?.RemoveStyleClass(StyleNano.StyleClassPinButtonPinned); + PlayerEntryPinButton?.AddStyleClass(StyleNano.StyleClassPinButtonUnpinned); + } + } +} diff --git a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml index 3071bf8358..25a96df1d3 100644 --- a/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml +++ b/Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTab.xaml @@ -1,21 +1,19 @@  + xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls" + xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"> -