Terminal UI V2, advanced binding

This commit is contained in:
2023-08-08 18:28:13 +02:00
parent 52536b569d
commit 2528487ff6
38 changed files with 911 additions and 199 deletions

View File

@@ -1,4 +1,5 @@
using TerminalUI.Traits;
using TerminalUI.Models;
using TerminalUI.Traits;
namespace TerminalUI.Controls;
@@ -9,7 +10,7 @@ public abstract class ContentView<T>: View<T>, IContentRenderer
ContentRendererMethod = DefaultContentRender;
}
public IView? Content { get; set; }
public Action ContentRendererMethod { get; set; }
public Action<Position> ContentRendererMethod { get; set; }
private void DefaultContentRender() => Content?.Render();
private void DefaultContentRender(Position position) => Content?.Render(position);
}

View File

@@ -1,4 +1,5 @@
using System.ComponentModel;
using TerminalUI.Models;
using TerminalUI.Traits;
namespace TerminalUI.Controls;
@@ -6,12 +7,10 @@ namespace TerminalUI.Controls;
public interface IView : INotifyPropertyChanged, IDisposableCollection
{
object? DataContext { get; set; }
Action RenderMethod { get; set; }
IApplicationContext ApplicationContext { get; init;}
Action<Position> RenderMethod { get; set; }
IApplicationContext? ApplicationContext { get; init;}
event Action<IView> Disposed;
event Action<IView> RenderRequested;
void Render();
void RequestRerender();
void Render(Position position);
}
public interface IView<T> : IView

View File

@@ -1,6 +1,7 @@
using System.Buffers;
using System.Collections.ObjectModel;
using DeclarativeProperty;
using TerminalUI.Models;
namespace TerminalUI.Controls;
@@ -9,7 +10,7 @@ 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 Func<IEnumerable<TItem>?>? _getItems;
private object? _itemsSource;
private ListViewItem<TItem>[]? _listViewItems;
private int _listViewItemLength;
@@ -30,11 +31,20 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
_itemsDisposables.Clear();
if (_itemsSource is IDeclarativeProperty<ObservableCollection<TItem>> observableDeclarativeProperty)
{
observableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
_getItems = () => observableDeclarativeProperty.Value;
}
else if (_itemsSource is IDeclarativeProperty<ReadOnlyObservableCollection<TItem>> readOnlyObservableDeclarativeProperty)
{
readOnlyObservableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
_getItems = () => readOnlyObservableDeclarativeProperty.Value;
}
else if (_itemsSource is IDeclarativeProperty<IEnumerable<TItem>> enumerableDeclarativeProperty)
{
enumerableDeclarativeProperty.PropertyChanged += (_, _) => ApplicationContext?.EventLoop.RequestRerender();
_getItems = () => enumerableDeclarativeProperty.Value;
}
else if (_itemsSource is ICollection<TItem> collection)
_getItems = () => collection;
else if (_itemsSource is TItem[] array)
@@ -54,18 +64,20 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
protected override void DefaultRenderer()
protected override void DefaultRenderer(Position position)
{
var listViewItems = InstantiateItemViews();
var deltaY = 0;
foreach (var item in listViewItems)
{
item.Render();
item.Render(position with {PosY = position.PosY + deltaY++});
}
}
private Span<ListViewItem<TItem>> InstantiateItemViews()
{
if (_getItems is null)
var items = _getItems?.Invoke()?.ToList();
if (items is null)
{
if (_listViewItemLength != 0)
{
@@ -74,11 +86,10 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
return _listViewItems;
}
var items = _getItems().ToList();
Span<ListViewItem<TItem>> listViewItems;
if (_listViewItems is null || _listViewItems.Length != items.Count)
if (_listViewItems is null || _listViewItemLength != items.Count)
{
var newListViewItems = ListViewItemPool.Rent(items.Count);
for (var i = 0; i < items.Count; i++)

View File

@@ -1,10 +1,10 @@
using TerminalUI.Traits;
using TerminalUI.Models;
namespace TerminalUI.Controls;
public class ListViewItem<T> : ContentView<T>
{
protected override void DefaultRenderer()
protected override void DefaultRenderer(Position position)
{
if (ContentRendererMethod is null)
{
@@ -16,6 +16,6 @@ public class ListViewItem<T> : ContentView<T>
+ DataContext?.GetType().Name);
}
ContentRendererMethod();
ContentRendererMethod(position);
}
}

View File

@@ -1,11 +1,18 @@
using PropertyChanged.SourceGenerator;
using TerminalUI.Extensions;
using TerminalUI.Models;
namespace TerminalUI.Controls;
public partial class TextBlock<T> : View<T>
{
private record RenderContext(Position Position, string? Text, IColor? Foreground, IColor? Background);
private RenderContext? _renderContext;
[Notify] private string? _text = string.Empty;
[Notify] private IColor? _foreground;
[Notify] private IColor? _background;
public TextBlock()
{
@@ -16,8 +23,35 @@ public partial class TextBlock<T> : View<T>
);
RerenderProperties.Add(nameof(Text));
RerenderProperties.Add(nameof(Foreground));
RerenderProperties.Add(nameof(Background));
}
protected override void DefaultRenderer()
=> Console.Write(Text);
protected override void DefaultRenderer(Position position)
{
var driver = ApplicationContext!.ConsoleDriver;
var renderContext = new RenderContext(position, Text, _foreground, _background);
if (!NeedsRerender(renderContext)) return;
_renderContext = renderContext;
if (Text is null) return;
driver.SetCursorPosition(position);
driver.ResetColor();
if (Foreground is { } foreground)
{
driver.SetForegroundColor(foreground);
}
if (Background is { } background)
{
driver.SetBackgroundColor(background);
}
driver.Write(Text);
}
private bool NeedsRerender(RenderContext renderContext)
=> _renderContext is null || _renderContext != renderContext;
}

View File

@@ -2,6 +2,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PropertyChanged.SourceGenerator;
using TerminalUI.Models;
namespace TerminalUI.Controls;
@@ -9,10 +10,9 @@ public abstract partial class View<T> : IView<T>
{
private readonly List<IDisposable> _disposables = new();
[Notify] private T? _dataContext;
public Action RenderMethod { get; set; }
public IApplicationContext ApplicationContext { get; init; }
public Action<Position> RenderMethod { get; set; }
public IApplicationContext? ApplicationContext { get; init; }
public event Action<IView>? Disposed;
public event Action<IView>? RenderRequested;
protected List<string> RerenderProperties { get; } = new();
protected View()
@@ -29,13 +29,13 @@ public abstract partial class View<T> : IView<T>
)
)
{
RenderRequested?.Invoke(this);
ApplicationContext?.EventLoop.RequestRerender();
}
}
protected abstract void DefaultRenderer();
protected abstract void DefaultRenderer(Position position);
public void Render()
public void Render(Position position)
{
if (RenderMethod is null)
{
@@ -47,11 +47,9 @@ public abstract partial class View<T> : IView<T>
+ DataContext?.GetType().Name);
}
RenderMethod();
RenderMethod(position);
}
public void RequestRerender() => RenderRequested?.Invoke(this);
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
{
var child = new TChild