Custom TUI Library WIP

This commit is contained in:
2023-08-08 00:27:42 +02:00
parent 7807a82f3f
commit a77d5cc235
22 changed files with 334 additions and 282 deletions

View File

@@ -0,0 +1,16 @@
using System.ComponentModel;
using TerminalUI.Traits;
namespace TerminalUI.Controls;
public interface IView<T> : INotifyPropertyChanged, IDisposableCollection
{
T? DataContext { get; set; }
void Render();
TChild CreateChild<TChild>()
where TChild : IView<T>, new();
TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
where TChild : IView<TDataContext>, new();
}

View File

@@ -0,0 +1,85 @@
using System.Buffers;
using System.Collections.ObjectModel;
using DeclarativeProperty;
namespace TerminalUI.Controls;
public class ListView<TDataContext, TItem> : View<TDataContext>
{
private static readonly ArrayPool<ListViewItem<TItem>> ListViewItemPool = ArrayPool<ListViewItem<TItem>>.Shared;
private readonly List<IDisposable> _itemsDisposables = new();
private Func<IEnumerable<TItem>>? _getItems;
private object? _itemsSource;
private ListViewItem<TItem>[]? _listViewItems;
private int _listViewItemLength;
public object? ItemsSource
{
get => _itemsSource;
set
{
if (_itemsSource == value) return;
_itemsSource = value;
foreach (var disposable in _itemsDisposables)
{
disposable.Dispose();
}
_itemsDisposables.Clear();
if (_itemsSource is IDeclarativeProperty<ObservableCollection<TItem>> observableDeclarativeProperty)
_getItems = () => observableDeclarativeProperty.Value;
else if (_itemsSource is IDeclarativeProperty<ReadOnlyObservableCollection<TItem>> readOnlyObservableDeclarativeProperty)
_getItems = () => readOnlyObservableDeclarativeProperty.Value;
else if (_itemsSource is IDeclarativeProperty<IEnumerable<TItem>> enumerableDeclarativeProperty)
_getItems = () => enumerableDeclarativeProperty.Value;
else if (_itemsSource is ICollection<TItem> collection)
_getItems = () => collection;
else if (_itemsSource is TItem[] array)
_getItems = () => array;
else if (_itemsSource is IEnumerable<TItem> enumerable)
_getItems = () => enumerable.ToArray();
if (_listViewItems is not null)
{
ListViewItemPool.Return(_listViewItems);
_listViewItems = null;
}
OnPropertyChanged();
}
}
public override void Render()
{
if (_getItems is null) return;
var items = _getItems().ToList();
Span<ListViewItem<TItem>> listViewItems = null;
if (_listViewItems is null || _listViewItems.Length != items.Count)
{
var newListViewItems = ListViewItemPool.Rent(items.Count);
for (var i = 0; i < items.Count; i++)
{
var dataContext = items[i];
newListViewItems[i] = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
}
_listViewItems = newListViewItems;
_listViewItemLength = items.Count;
listViewItems = newListViewItems[..items.Count];
}
else
{
listViewItems = _listViewItems[.._listViewItemLength];
}
foreach (var item in listViewItems)
{
item.Render();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace TerminalUI.Controls;
public class ListViewItem<T> : View<T>
{
public override void Render()
{
Console.WriteLine(DataContext?.ToString());
}
}

View File

@@ -0,0 +1,73 @@
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TerminalUI.Controls;
public abstract class View<T> : IView<T>
{
private readonly ConcurrentBag<IDisposable> _disposables = new();
public T? DataContext { get; set; }
public abstract void Render();
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
{
var child = new TChild
{
DataContext = DataContext
};
var mapper = new DataContextMapper<T>(this, d => child.DataContext = d);
AddDisposable(mapper);
child.AddDisposable(mapper);
return child;
}
public TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
where TChild : IView<TDataContext>, new()
{
var child = new TChild
{
DataContext = dataContextMapper(DataContext)
};
var mapper = new DataContextMapper<T>(this, d => child.DataContext = dataContextMapper(d));
AddDisposable(mapper);
child.AddDisposable(mapper);
return child;
}
public void AddDisposable(IDisposable disposable) => _disposables.Add(disposable);
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetField<TProp>(ref TProp field, TProp value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<TProp>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Violates rule
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (var disposable in _disposables)
{
disposable.Dispose();
}
_disposables.Clear();
}
}
}