EventLoop V1

This commit is contained in:
2023-08-08 12:49:15 +02:00
parent 7b8764ac16
commit 52536b569d
21 changed files with 479 additions and 68 deletions

View File

@@ -0,0 +1,15 @@
using TerminalUI.Traits;
namespace TerminalUI.Controls;
public abstract class ContentView<T>: View<T>, IContentRenderer
{
protected ContentView()
{
ContentRendererMethod = DefaultContentRender;
}
public IView? Content { get; set; }
public Action ContentRendererMethod { get; set; }
private void DefaultContentRender() => Content?.Render();
}

View File

@@ -3,10 +3,26 @@ using TerminalUI.Traits;
namespace TerminalUI.Controls;
public interface IView<T> : INotifyPropertyChanged, IDisposableCollection
public interface IView : INotifyPropertyChanged, IDisposableCollection
{
T? DataContext { get; set; }
object? DataContext { get; set; }
Action RenderMethod { get; set; }
IApplicationContext ApplicationContext { get; init;}
event Action<IView> Disposed;
event Action<IView> RenderRequested;
void Render();
void RequestRerender();
}
public interface IView<T> : IView
{
new T? DataContext { get; set; }
object? IView.DataContext
{
get => DataContext;
set => DataContext = (T?) value;
}
TChild CreateChild<TChild>()
where TChild : IView<T>, new();

View File

@@ -52,12 +52,31 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
}
}
public override void Render()
public Func<ListViewItem<TItem>, IView?> ItemTemplate { get; set; } = DefaultItemTemplate;
protected override void DefaultRenderer()
{
if (_getItems is null) return;
var listViewItems = InstantiateItemViews();
foreach (var item in listViewItems)
{
item.Render();
}
}
private Span<ListViewItem<TItem>> InstantiateItemViews()
{
if (_getItems is null)
{
if (_listViewItemLength != 0)
{
return InstantiateEmptyItemViews();
}
return _listViewItems;
}
var items = _getItems().ToList();
Span<ListViewItem<TItem>> listViewItems = null;
Span<ListViewItem<TItem>> listViewItems;
if (_listViewItems is null || _listViewItems.Length != items.Count)
{
@@ -65,7 +84,10 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
for (var i = 0; i < items.Count; i++)
{
var dataContext = items[i];
newListViewItems[i] = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
var child = CreateChild<ListViewItem<TItem>, TItem>(_ => dataContext);
child.Content = ItemTemplate(child);
ItemTemplate(child);
newListViewItems[i] = child;
}
_listViewItems = newListViewItems;
@@ -77,9 +99,15 @@ public class ListView<TDataContext, TItem> : View<TDataContext>
listViewItems = _listViewItems[.._listViewItemLength];
}
foreach (var item in listViewItems)
{
item.Render();
}
return listViewItems;
}
private Span<ListViewItem<TItem>> InstantiateEmptyItemViews()
{
_listViewItems = ListViewItemPool.Rent(0);
_listViewItemLength = 0;
return _listViewItems;
}
private static IView? DefaultItemTemplate(ListViewItem<TItem> listViewItem) => null;
}

View File

@@ -1,9 +1,21 @@
namespace TerminalUI.Controls;
using TerminalUI.Traits;
public class ListViewItem<T> : View<T>
namespace TerminalUI.Controls;
public class ListViewItem<T> : ContentView<T>
{
public override void Render()
protected override void DefaultRenderer()
{
Console.WriteLine(DataContext?.ToString());
if (ContentRendererMethod is null)
{
throw new NullReferenceException(
nameof(ContentRendererMethod)
+ " is null, cannot render content of "
+ Content?.GetType().Name
+ " with DataContext of "
+ DataContext?.GetType().Name);
}
ContentRendererMethod();
}
}

View File

@@ -0,0 +1,23 @@
using PropertyChanged.SourceGenerator;
using TerminalUI.Extensions;
namespace TerminalUI.Controls;
public partial class TextBlock<T> : View<T>
{
[Notify] private string? _text = string.Empty;
public TextBlock()
{
this.Bind(
this,
dc => dc == null ? string.Empty : dc.ToString(),
tb => tb.Text
);
RerenderProperties.Add(nameof(Text));
}
protected override void DefaultRenderer()
=> Console.Write(Text);
}

View File

@@ -1,20 +1,63 @@
using System.Collections.Concurrent;
using System.Buffers;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PropertyChanged.SourceGenerator;
namespace TerminalUI.Controls;
public abstract class View<T> : IView<T>
public abstract partial class View<T> : IView<T>
{
private readonly ConcurrentBag<IDisposable> _disposables = new();
public T? DataContext { get; set; }
public abstract void Render();
private readonly List<IDisposable> _disposables = new();
[Notify] private T? _dataContext;
public Action 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()
{
RenderMethod = DefaultRenderer;
((INotifyPropertyChanged) this).PropertyChanged += Handle_PropertyChanged;
}
private void Handle_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is not null
&& (e.PropertyName == nameof(IView.DataContext)
|| RerenderProperties.Contains(e.PropertyName)
)
)
{
RenderRequested?.Invoke(this);
}
}
protected abstract void DefaultRenderer();
public void Render()
{
if (RenderMethod is null)
{
throw new NullReferenceException(
nameof(RenderMethod)
+ " is null, cannot render content of "
+ GetType().Name
+ " with DataContext of "
+ DataContext?.GetType().Name);
}
RenderMethod();
}
public void RequestRerender() => RenderRequested?.Invoke(this);
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
{
var child = new TChild
{
DataContext = DataContext
DataContext = DataContext,
ApplicationContext = ApplicationContext
};
var mapper = new DataContextMapper<T>(this, d => child.DataContext = d);
AddDisposable(mapper);
@@ -23,12 +66,13 @@ public abstract class View<T> : IView<T>
return child;
}
public TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
public TChild CreateChild<TChild, TDataContext>(Func<T?, TDataContext?> dataContextMapper)
where TChild : IView<TDataContext>, new()
{
var child = new TChild
{
DataContext = dataContextMapper(DataContext)
DataContext = dataContextMapper(DataContext),
ApplicationContext = ApplicationContext
};
var mapper = new DataContextMapper<T>(this, d => child.DataContext = dataContextMapper(d));
AddDisposable(mapper);
@@ -38,36 +82,36 @@ public abstract class View<T> : IView<T>
}
public void AddDisposable(IDisposable disposable) => _disposables.Add(disposable);
public void RemoveDisposable(IDisposable disposable) => _disposables.Remove(disposable);
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
protected 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
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (var disposable in _disposables)
var arrayPool = ArrayPool<IDisposable>.Shared;
var disposablesCount = _disposables.Count;
var disposables = arrayPool.Rent(disposablesCount);
_disposables.CopyTo(disposables);
for (var i = 0; i < disposablesCount; i++)
{
disposable.Dispose();
disposables[i].Dispose();
}
arrayPool.Return(disposables, true);
_disposables.Clear();
Disposed?.Invoke(this);
}
}
}