Terminal UI V2, advanced binding
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user