Console DialogService
This commit is contained in:
194
src/Library/TerminalUI/Controls/ItemsControl.cs
Normal file
194
src/Library/TerminalUI/Controls/ItemsControl.cs
Normal file
@@ -0,0 +1,194 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using ObservableComputations;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Traits;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public sealed partial class ItemsControl<TDataContext, TItem>
|
||||
: View<ItemsControl<TDataContext, TItem>, TDataContext>, IVisibilityChangeHandler
|
||||
{
|
||||
private readonly List<IView> _forceRerenderChildren = new();
|
||||
private readonly object _forceRerenderChildrenLock = new();
|
||||
private readonly List<IDisposable> _itemsDisposables = new();
|
||||
private readonly Dictionary<IView, Size> _requestedSizes = new();
|
||||
private IList<IView<TItem>> _children = new List<IView<TItem>>();
|
||||
private object? _itemsSource;
|
||||
[Notify] private Orientation _orientation = Orientation.Vertical;
|
||||
|
||||
public Func<IView<TItem>?> ItemTemplate { get; set; } = DefaultItemTemplate;
|
||||
|
||||
public IReadOnlyList<IView<TItem>> Children => _children.AsReadOnly();
|
||||
|
||||
public object? ItemsSource
|
||||
{
|
||||
get => _itemsSource;
|
||||
set
|
||||
{
|
||||
if (_itemsSource == value) return;
|
||||
_itemsSource = value;
|
||||
|
||||
foreach (var disposable in _itemsDisposables)
|
||||
{
|
||||
disposable.Dispose();
|
||||
}
|
||||
|
||||
_itemsDisposables.Clear();
|
||||
|
||||
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
|
||||
{
|
||||
_children = observableDeclarative
|
||||
.Selecting(i => CreateItem(i))
|
||||
.OfTypeComputing<IView<TItem>>();
|
||||
}
|
||||
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
|
||||
{
|
||||
_children = readOnlyObservableDeclarative
|
||||
.Selecting(i => CreateItem(i))
|
||||
.OfTypeComputing<IView<TItem>>();
|
||||
}
|
||||
else if (_itemsSource is ICollection<TItem> collection)
|
||||
_children = collection.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||
else if (_itemsSource is TItem[] array)
|
||||
_children = array.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||
else if (_itemsSource is IEnumerable<TItem> enumerable)
|
||||
_children = enumerable.Select(CreateItem).OfType<IView<TItem>>().ToList();
|
||||
else if (value is null)
|
||||
{
|
||||
_children = new List<IView<TItem>>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(Children));
|
||||
}
|
||||
}
|
||||
|
||||
public ItemsControl()
|
||||
{
|
||||
RerenderProperties.Add(nameof(ItemsSource));
|
||||
RerenderProperties.Add(nameof(Orientation));
|
||||
}
|
||||
|
||||
|
||||
protected override Size CalculateSize()
|
||||
{
|
||||
_requestedSizes.Clear();
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
|
||||
foreach (var child in _children)
|
||||
{
|
||||
if (!child.IsVisible) continue;
|
||||
|
||||
var childSize = child.GetRequestedSize();
|
||||
_requestedSizes.Add(child, childSize);
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
width = Math.Max(width, childSize.Width);
|
||||
height += childSize.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
width += childSize.Width;
|
||||
height = Math.Max(height, childSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return new Size((int) width, (int) height);
|
||||
}
|
||||
|
||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
var neededRerender = false;
|
||||
IReadOnlyList<IView> forceRerenderChildren;
|
||||
lock (_forceRerenderChildrenLock)
|
||||
{
|
||||
forceRerenderChildren = _forceRerenderChildren.ToList();
|
||||
_forceRerenderChildren.Clear();
|
||||
}
|
||||
|
||||
var delta = 0;
|
||||
foreach (var child in _children)
|
||||
{
|
||||
if (!child.IsVisible) continue;
|
||||
|
||||
if (!_requestedSizes.TryGetValue(child, out var childSize)) throw new Exception("Child size not found");
|
||||
|
||||
|
||||
var childPosition = Orientation == Orientation.Vertical
|
||||
? position with {Y = position.Y + delta}
|
||||
: position with {X = position.X + delta};
|
||||
|
||||
childSize = Orientation == Orientation.Vertical
|
||||
? childSize with {Width = size.Width}
|
||||
: childSize with {Height = size.Height};
|
||||
|
||||
var endX = position.X + size.Width;
|
||||
var endY = position.Y + size.Height;
|
||||
|
||||
if (childPosition.X > endX || childPosition.Y > endY) break;
|
||||
if (childPosition.X + childSize.Width > endX)
|
||||
{
|
||||
childSize = childSize with {Width = endX - childPosition.X};
|
||||
}
|
||||
|
||||
if (childPosition.Y + childSize.Height > endY)
|
||||
{
|
||||
childSize = childSize with {Height = endY - childPosition.Y};
|
||||
}
|
||||
|
||||
if (forceRerenderChildren.Contains(child))
|
||||
{
|
||||
var rerenderContext = new RenderContext(
|
||||
renderContext.ConsoleDriver,
|
||||
true,
|
||||
renderContext.Foreground,
|
||||
renderContext.Background,
|
||||
renderContext.Statistics
|
||||
);
|
||||
neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender;
|
||||
}
|
||||
else
|
||||
{
|
||||
neededRerender = child.Render(renderContext, childPosition, childSize) || neededRerender;
|
||||
}
|
||||
|
||||
delta += Orientation == Orientation.Vertical
|
||||
? childSize.Height
|
||||
: childSize.Width;
|
||||
}
|
||||
|
||||
return neededRerender;
|
||||
}
|
||||
|
||||
private IView<TItem>? CreateItem(TItem dataContext)
|
||||
{
|
||||
var newItem = ItemTemplate();
|
||||
AddChild(newItem, _ => dataContext);
|
||||
return newItem;
|
||||
}
|
||||
|
||||
private static IView<TItem>? DefaultItemTemplate() => null;
|
||||
|
||||
public void ChildVisibilityChanged(IView child)
|
||||
{
|
||||
var viewToForceRerender = child;
|
||||
while (viewToForceRerender.VisualParent != null && viewToForceRerender.VisualParent != this)
|
||||
{
|
||||
viewToForceRerender = viewToForceRerender.VisualParent;
|
||||
}
|
||||
|
||||
if (viewToForceRerender.VisualParent != this) return;
|
||||
|
||||
lock (_forceRerenderChildrenLock)
|
||||
{
|
||||
_forceRerenderChildren.Add(viewToForceRerender);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user