Add Context Menu to Bwoink Window (#9374)
* Clean up EntityListDisplay * Rename EntityListDisplay to ListContainer * Rename stuff * Rework ListContainer * Add tests * Replace IControlData with record ListData * Make _data non-nullable * Fix test record items being duplicates * Split filter method from populate * Rename UpdateList to DirtyList * Replace _count with _data.Count * Clarify local variable toRemove * Cleanup * Add data selection to ListContainer * Add selection test * Fix comments and test name * Fix ListContainer layout hiding items when scaled * Add test for ListContainer top item * Toggle fix * Ensure buttons are re-generated * Update unread count on select * a * Fix toggle test Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
using Content.Client.Administration.UI.Tabs.AdminTab;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
@@ -14,6 +14,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Administration.UI
|
||||
@@ -48,13 +49,29 @@ namespace Content.Client.Administration.UI
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
}
|
||||
|
||||
foreach (var li in ChannelSelector.PlayerItemList)
|
||||
li.Text = FormatTabTitle(li);
|
||||
ChannelSelector.PlayerListContainer.DirtyList();
|
||||
};
|
||||
|
||||
ChannelSelector.DecoratePlayer += (PlayerInfo pl, ItemList.Item li) =>
|
||||
ChannelSelector.OverrideText += (info, text) =>
|
||||
{
|
||||
li.Text = FormatTabTitle(li, pl);
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(info.Connected ? '●' : '○');
|
||||
sb.Append(' ');
|
||||
if (_bwoinkSystem.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
|
||||
{
|
||||
if (panel.Unread < 11)
|
||||
sb.Append(new Rune('➀' + (panel.Unread-1)));
|
||||
else
|
||||
sb.Append(new Rune(0x2639)); // ☹
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
if (info.Antag)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
sb.AppendFormat("\"{0}\"", text);
|
||||
|
||||
return sb.ToString();
|
||||
};
|
||||
|
||||
ChannelSelector.Comparison = (a, b) =>
|
||||
@@ -121,18 +138,16 @@ namespace Content.Client.Administration.UI
|
||||
|
||||
public void OnBwoink(NetUserId channel)
|
||||
{
|
||||
ChannelSelector.RefreshDecorators();
|
||||
ChannelSelector.Sort();
|
||||
ChannelSelector.PopulateList();
|
||||
}
|
||||
|
||||
public void SelectChannel(NetUserId channel)
|
||||
{
|
||||
var pi = ChannelSelector
|
||||
.PlayerItemList
|
||||
.FirstOrDefault(i => ((PlayerInfo) i.Metadata!).SessionId == channel);
|
||||
|
||||
if (pi is not null)
|
||||
pi.Selected = true;
|
||||
if (!ChannelSelector.PlayerInfo.TryFirstOrDefault(
|
||||
i => i.SessionId == channel, out var info))
|
||||
return;
|
||||
ChannelSelector.PopulateList();
|
||||
ChannelSelector.PlayerListContainer.Select(new PlayerListData(info));
|
||||
}
|
||||
|
||||
private void FixButtons()
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
xmlns:controls="using:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical">
|
||||
<Control MinSize="0 5" />
|
||||
<LineEdit Name="FilterLineEdit"
|
||||
MinSize="100 0"
|
||||
HorizontalExpand="True"
|
||||
PlaceHolder="{Loc Filter}"/>
|
||||
<ItemList Name="PlayerItemList"
|
||||
Access="Public"
|
||||
SelectMode="Single"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
MinSize="100 100" />
|
||||
<PanelContainer Name="BackgroundPanel"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
<controls:ListContainer Name="PlayerListContainer"
|
||||
Access="Public"
|
||||
Toggle="True"
|
||||
Group="True"
|
||||
MinSize="100 0"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.Administration.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Input;
|
||||
|
||||
namespace Content.Client.Administration.UI.CustomControls
|
||||
{
|
||||
@@ -15,81 +16,107 @@ namespace Content.Client.Administration.UI.CustomControls
|
||||
public sealed partial class PlayerListControl : BoxContainer
|
||||
{
|
||||
private readonly AdminSystem _adminSystem;
|
||||
private readonly VerbSystem _verbSystem;
|
||||
|
||||
private List<PlayerInfo> _playerList = new();
|
||||
private readonly List<PlayerInfo> _sortedPlayerList = new();
|
||||
|
||||
public event Action<PlayerInfo?>? OnSelectionChanged;
|
||||
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
|
||||
|
||||
public Action<PlayerInfo, ItemList.Item>? DecoratePlayer;
|
||||
public Func<PlayerInfo, string, string>? OverrideText;
|
||||
public Comparison<PlayerInfo>? Comparison;
|
||||
|
||||
public PlayerListControl()
|
||||
{
|
||||
_adminSystem = EntitySystem.Get<AdminSystem>();
|
||||
_verbSystem = EntitySystem.Get<VerbSystem>();
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
// Fill the Option data
|
||||
PopulateList();
|
||||
PlayerItemList.OnItemSelected += PlayerItemListOnOnItemSelected;
|
||||
PlayerItemList.OnItemDeselected += PlayerItemListOnOnItemDeselected;
|
||||
FilterLineEdit.OnTextChanged += FilterLineEditOnOnTextEntered;
|
||||
PlayerListContainer.ItemPressed += PlayerListItemPressed;
|
||||
PlayerListContainer.GenerateItem += GenerateButton;
|
||||
PopulateList(_adminSystem.PlayerList);
|
||||
FilterLineEdit.OnTextChanged += _ => FilterList();
|
||||
_adminSystem.PlayerListChanged += PopulateList;
|
||||
|
||||
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
|
||||
}
|
||||
|
||||
private void FilterLineEditOnOnTextEntered(LineEdit.LineEditEventArgs obj)
|
||||
private void PlayerListItemPressed(BaseButton.ButtonEventArgs args, ListData data)
|
||||
{
|
||||
PopulateList();
|
||||
}
|
||||
|
||||
private void PlayerItemListOnOnItemSelected(ItemList.ItemListSelectedEventArgs obj)
|
||||
{
|
||||
var selectedPlayer = (PlayerInfo) obj.ItemList[obj.ItemIndex].Metadata!;
|
||||
OnSelectionChanged?.Invoke(selectedPlayer);
|
||||
}
|
||||
|
||||
private void PlayerItemListOnOnItemDeselected(ItemList.ItemListDeselectedEventArgs obj)
|
||||
{
|
||||
OnSelectionChanged?.Invoke(null);
|
||||
}
|
||||
|
||||
public void RefreshDecorators()
|
||||
{
|
||||
foreach (var item in PlayerItemList)
|
||||
if (data is not PlayerListData {Info: var selectedPlayer})
|
||||
return;
|
||||
if (args.Event.Function == EngineKeyFunctions.UIClick)
|
||||
{
|
||||
DecoratePlayer?.Invoke((PlayerInfo) item.Metadata!, item);
|
||||
OnSelectionChanged?.Invoke(selectedPlayer);
|
||||
|
||||
// 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);
|
||||
}
|
||||
else if (args.Event.Function == ContentKeyFunctions.OpenContextMenu)
|
||||
{
|
||||
_verbSystem.VerbMenu.OpenVerbMenu(selectedPlayer.EntityUid);
|
||||
}
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
private void FilterList()
|
||||
{
|
||||
if(Comparison != null)
|
||||
PlayerItemList.Sort((a, b) => Comparison((PlayerInfo) a.Metadata!, (PlayerInfo) b.Metadata!));
|
||||
}
|
||||
|
||||
private void PopulateList(IReadOnlyList<PlayerInfo> _ = null!)
|
||||
{
|
||||
PlayerItemList.Clear();
|
||||
|
||||
foreach (var info in _adminSystem.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()))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(FilterLineEdit.Text)
|
||||
&& !displayName.ToLowerInvariant().Contains(FilterLineEdit.Text.Trim().ToLowerInvariant()))
|
||||
continue;
|
||||
}
|
||||
|
||||
var item = new ItemList.Item(PlayerItemList)
|
||||
{
|
||||
Metadata = info,
|
||||
Text = displayName
|
||||
};
|
||||
DecoratePlayer?.Invoke(info, item);
|
||||
PlayerItemList.Add(item);
|
||||
_sortedPlayerList.Add(info);
|
||||
}
|
||||
|
||||
Sort();
|
||||
if (Comparison != null)
|
||||
_sortedPlayerList.Sort((a, b) => Comparison(a, b));
|
||||
|
||||
PlayerListContainer.PopulateList(_sortedPlayerList.Select(info => new PlayerListData(info)).ToList());
|
||||
}
|
||||
|
||||
public void PopulateList(IReadOnlyList<PlayerInfo>? players = null)
|
||||
{
|
||||
players ??= _adminSystem.PlayerList;
|
||||
|
||||
_playerList = players.ToList();
|
||||
FilterList();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
button.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
ClipText = true,
|
||||
Text = GetText(info)
|
||||
}
|
||||
}
|
||||
});
|
||||
button.EnableAllKeybinds = true;
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
}
|
||||
}
|
||||
|
||||
public record PlayerListData(PlayerInfo Info) : ListData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user