using Content.Client.Station;
+using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
- [Dependency] private readonly EntityManager _entityManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List<ObjectsTabEntry> _objects = new();
- private List<ObjectsTabSelection> _selections = new();
+ private readonly List<ObjectsTabSelection> _selections = new();
+ private bool _ascending = false; // Set to false for descending order by default
+ private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
+ private readonly Color _altColor = Color.FromHex("#292B38");
+ private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
- public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
+ public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
- // Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
- // OR
- // I can do this.
- private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
-
- private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
+ private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
+ private TimeSpan _nextUpdate;
public ObjectsTab()
{
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
+ ListHeader.OnHeaderClicked += HeaderClicked;
+ SearchList.SearchBar = SearchLineEdit;
+ SearchList.GenerateItem += GenerateButton;
+ SearchList.DataFilterCondition += DataFilterCondition;
+
+ RefreshObjectList();
+ // Set initial selection and refresh the list to apply the initial sort order
+ var defaultSelection = ObjectsTabSelection.Grids;
+ ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
+ RefreshObjectList(defaultSelection); // Refresh the list with the default selection
+
+ // Initialize the next update time
+ _nextUpdate = TimeSpan.Zero;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_timing.CurTime < _nextUpdate)
+ return;
+
+ _nextUpdate = _timing.CurTime + _updateFrequency;
+
RefreshObjectList();
}
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
- foreach (var control in _objects)
+ entities.Sort((a, b) =>
{
- ObjectList.RemoveChild(control);
- }
+ var valueA = GetComparableValue(a, _headerClicked);
+ var valueB = GetComparableValue(b, _headerClicked);
+ return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
+ });
- _objects.Clear();
-
- foreach (var (name, nent) in entities)
+ var listData = new List<ObjectsListData>();
+ for (int index = 0; index < entities.Count; index++)
{
- var ctrl = new ObjectsTabEntry(name, nent);
- _objects.Add(ctrl);
- ObjectList.AddChild(ctrl);
- ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
+ var info = entities[index];
+ listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
}
+
+ SearchList.PopulateList(listData);
}
- protected override void FrameUpdate(FrameEventArgs args)
+ private void GenerateButton(ListData data, ListContainerButton button)
{
- base.FrameUpdate(args);
-
- if (_timing.CurTime < _nextUpdate)
+ if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
return;
- // I do not care for precision.
- _nextUpdate = _timing.CurTime + _updateFrequency;
+ var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
+ button.ToolTip = $"{info.Name}, {info.Entity}";
+
+ // Add key binding event handler
+ entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
+
+ button.AddChild(entry);
+ }
+
+ private bool DataFilterCondition(string filter, ListData listData)
+ {
+ if (listData is not ObjectsListData { FilteringString: var filteringString })
+ return false;
+
+ // If the filter is empty, do not filter out any entries
+ if (string.IsNullOrEmpty(filter))
+ return true;
+
+ return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
+ }
+
+ private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
+ {
+ return header switch
+ {
+ ObjectsTabHeader.Header.ObjectName => entity.Name,
+ ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
+ _ => entity.Name
+ };
+ }
+
+ private void HeaderClicked(ObjectsTabHeader.Header header)
+ {
+ if (_headerClicked == header)
+ {
+ _ascending = !_ascending;
+ }
+ else
+ {
+ _headerClicked = header;
+ _ascending = true;
+ }
+ ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
RefreshObjectList();
}
}
}
+public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData;
--- /dev/null
+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.ObjectsTab
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class ObjectsTabHeader : Control
+ {
+ public event Action<Header>? OnHeaderClicked;
+
+ private const string ArrowUp = "↑";
+ private const string ArrowDown = "↓";
+
+ public ObjectsTabHeader()
+ {
+ RobustXamlLoader.Load(this);
+
+ ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
+ EntityIDLabel.OnKeyBindDown += EntityIDClicked;
+ }
+
+ public Label GetHeader(Header header)
+ {
+ return header switch
+ {
+ Header.ObjectName => ObjectNameLabel,
+ Header.EntityID => EntityIDLabel,
+ _ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
+ };
+ }
+
+ public void ResetHeaderText()
+ {
+ ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
+ EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
+ }
+
+ public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
+ {
+ ResetHeaderText();
+ var arrow = ascending ? ArrowUp : ArrowDown;
+ GetHeader(headerClicked).Text += $" {arrow}";
+ }
+
+ private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
+ {
+ if (args.Function != EngineKeyFunctions.UIClick)
+ {
+ return;
+ }
+
+ OnHeaderClicked?.Invoke(header);
+ args.Handle();
+ }
+
+ private void ObjectNameClicked(GUIBoundKeyEventArgs args)
+ {
+ HeaderClicked(args, Header.ObjectName);
+ }
+
+ private void EntityIDClicked(GUIBoundKeyEventArgs args)
+ {
+ HeaderClicked(args, Header.EntityID);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
+ EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
+ }
+ }
+
+ public enum Header
+ {
+ ObjectName,
+ EntityID
+ }
+ }
+}