Basic ItemsControl

This commit is contained in:
PJB3005
2026-05-07 03:42:47 +02:00
parent b1f06ea328
commit 6ff5fc8f9a
6 changed files with 368 additions and 0 deletions
@@ -0,0 +1,130 @@
using System;
using System.Collections.ObjectModel;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Robust.Client.Console.Commands;
internal sealed partial class UITestControl
{
private sealed class TabItemsControl : Control
{
private readonly Random _random = new();
private readonly ObservableCollection<int> _collection = [];
private int _counter = 0;
public TabItemsControl()
{
var addButton = new Button
{
Text = "Add Item",
};
addButton.OnPressed += AddButtonOnOnPressed;
var removeButton = new Button
{
Text = "Remove Item"
};
removeButton.OnPressed += RemoveButtonOnOnChildAdded;
var replaceButton = new Button
{
Text = "Replace Item"
};
replaceButton.OnPressed += ReplaceButtonOnOnPressed;
var moveButton = new Button
{
Text = "Move Item"
};
moveButton.OnPressed += MoveButtonOnOnPressed;
var resetButton = new Button
{
Text = "Reset Item"
};
resetButton.OnPressed += ResetButtonOnOnPressed;
AddChild(new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
HorizontalExpand = true,
Children =
{
addButton,
removeButton,
replaceButton,
moveButton,
resetButton
}
},
new ScrollContainer
{
HorizontalExpand = true,
Children =
{
new ItemsControl
{
ItemsSource = _collection,
}
}
}
}
});
}
private void AddButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
var value = _counter++;
var index = _random.Next(_collection.Count + 1);
Log.Info($"Inserted {value} at {index}");
_collection.Insert(index, value);
}
private void RemoveButtonOnOnChildAdded(BaseButton.ButtonEventArgs obj)
{
if (_collection.Count == 0)
return;
var index = _random.Next(_collection.Count);
Log.Info($"Removed {index} ({_collection[index]})");
_collection.RemoveAt(index);
}
private void ReplaceButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_collection.Count == 0)
return;
var index = _random.Next(_collection.Count);
var value = _counter++;
Log.Info($"Replaced {index} ({_collection[index]}) with {value}");
_collection[index] = value;
}
private void MoveButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
if (_collection.Count == 0)
return;
var oldIndex = _random.Next(_collection.Count);
var newIndex = _random.Next(_collection.Count);
Log.Info($"Moved {oldIndex} ({_collection[oldIndex]}) -> {newIndex}");
_collection.Move(oldIndex, newIndex);
}
private void ResetButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
Log.Info("Reset list");
_collection.Clear();
}
}
}
@@ -159,6 +159,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
_tabContainer.AddChild(TabCursorShapes());
_tabContainer.AddChild(new TabWrapContainer { Name = nameof(Tab.WrapContainer) });
_tabContainer.AddChild(new TabOkLab());
_tabContainer.AddChild(new TabItemsControl());
}
public void OnClosed()
@@ -283,6 +284,8 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
SpriteView = 8,
TabCursorShapes = 9,
WrapContainer = 10,
OkLab = 11,
ItemsControl = 12,
}
}
@@ -0,0 +1,16 @@
using System;
namespace Robust.Client.UserInterface.Controls;
public interface IControlTemplate
{
Control Instantiate(object? data);
}
public sealed class ControlTemplateDelegate(Func<object?, Control> @delegate) : IControlTemplate
{
public Control Instantiate(object? data)
{
return @delegate(data);
}
}
@@ -0,0 +1,16 @@
using System.Collections.Specialized;
namespace Robust.Client.UserInterface.Controls;
public interface IVirtualizingContainer
{
void SetParent(IVirtualizingContainerParent parent);
void ClearParent();
}
public interface IVirtualizingContainerParent
{
int ItemCount { get; }
Control CreateControl(object? item);
event NotifyCollectionChangedEventHandler CollectionChanged;
}
@@ -0,0 +1,160 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
namespace Robust.Client.UserInterface.Controls;
[Virtual]
public class ItemsControl : Control, IVirtualizingContainerParent
{
private static readonly NotifyCollectionChangedEventArgs NotifyReset = new(NotifyCollectionChangedAction.Reset);
private IList _items = Array.Empty<object>();
private Control _panelControl = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical };
private NotifyCollectionChangedEventHandler? _collectionChanged;
private IControlTemplate? _itemTemplate;
public Control PanelControl
{
get => _panelControl;
set
{
if (_panelControl == value)
return;
if (value is { Parent: not null })
throw new ArgumentException("Assigned panel must not be attached to a control.");
{
_panelControl.Orphan();
if (_panelControl is IVirtualizingContainer virt)
virt.ClearParent();
}
_panelControl = value;
{
AddChild(_panelControl);
if (_panelControl is IVirtualizingContainer virt)
{
virt.SetParent(this);
}
else
{
// Trigger rebuild logic on new control.
ItemsOnCollectionChanged(this, NotifyReset);
}
}
}
}
public IControlTemplate? ItemTemplate
{
get => _itemTemplate;
set => _itemTemplate = value;
}
public IEnumerable ItemsSource
{
set
{
if (_items is INotifyCollectionChanged notifyOld)
notifyOld.CollectionChanged -= ItemsOnCollectionChanged;
_items = value switch
{
IList list => list,
IEnumerable<object> enumerable => new List<object>(enumerable),
_ => new List<object>(value.Cast<object>())
};
if (_items is INotifyCollectionChanged notifyNew)
notifyNew.CollectionChanged += ItemsOnCollectionChanged;
ItemsOnCollectionChanged(this, NotifyReset);
}
}
public ItemsControl()
{
AddChild(_panelControl);
}
private void ItemsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
_collectionChanged?.Invoke(sender, e);
if (_panelControl is null or IVirtualizingContainer)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddItems(e.NewStartingIndex, e.NewItems!);
break;
case NotifyCollectionChangedAction.Remove:
RemoveItems(e.OldStartingIndex, e.OldItems!.Count);
break;
case NotifyCollectionChangedAction.Replace:
case NotifyCollectionChangedAction.Move:
// TODO: More intelligent move handling is possible here.
RemoveItems(e.OldStartingIndex, e.OldItems!.Count);
AddItems(e.NewStartingIndex, e.NewItems!);
break;
case NotifyCollectionChangedAction.Reset:
_panelControl.RemoveAllChildren();
AddItems(0, _items);
break;
}
return;
void AddItems(int startIndex, IList items)
{
var insertIndex = startIndex;
foreach (var item in items)
{
var control = CreateControl(item);
_panelControl.AddChild(control);
if (insertIndex != _panelControl.ChildCount)
control.SetPositionInParent(insertIndex);
insertIndex += 1;
}
}
void RemoveItems(int startIndex, int itemCount)
{
for (var i = 0; i < itemCount; i++)
{
_panelControl.RemoveChild(startIndex);
}
}
}
int IVirtualizingContainerParent.ItemCount => _items.Count;
private Control CreateControl(object? item)
{
if (_itemTemplate == null)
{
// Fallback if no template is provided.
var str = item?.ToString() ?? "";
return new Label { Text = str };
}
return _itemTemplate.Instantiate(item);
}
Control IVirtualizingContainerParent.CreateControl(object? item)
{
return CreateControl(item);
}
event NotifyCollectionChangedEventHandler IVirtualizingContainerParent.CollectionChanged
{
add => _collectionChanged += value;
remove => _collectionChanged -= value;
}
}
@@ -0,0 +1,43 @@
using System;
using System.Collections.Specialized;
using Robust.Shared.Utility;
using LayoutOrientation = Robust.Client.UserInterface.Controls.BoxContainer.LayoutOrientation;
namespace Robust.Client.UserInterface.Controls;
public sealed class VirtualizingBoxContainer : Control, IVirtualizingContainer
{
private LayoutOrientation _orientation;
private IVirtualizingContainerParent? _parent;
public LayoutOrientation Orientation
{
get => _orientation;
set
{
_orientation = value;
InvalidateMeasure();
}
}
void IVirtualizingContainer.SetParent(IVirtualizingContainerParent parent)
{
DebugTools.Assert(_parent == null);
_parent = parent;
_parent.CollectionChanged += ParentOnCollectionChanged;
}
void IVirtualizingContainer.ClearParent()
{
DebugTools.Assert(_parent != null);
_parent.CollectionChanged -= ParentOnCollectionChanged;
_parent = null;
}
private void ParentOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
throw new NotImplementedException();
}
}