EventLoop V1
This commit is contained in:
15
src/Library/TerminalUI/Controls/ContentView.cs
Normal file
15
src/Library/TerminalUI/Controls/ContentView.cs
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
23
src/Library/TerminalUI/Controls/TextBlock.cs
Normal file
23
src/Library/TerminalUI/Controls/TextBlock.cs
Normal 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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user