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

@@ -1,5 +1,6 @@
using FileTime.App.Core.Services;
using FileTime.ConsoleUI.App.KeyInputHandling;
using TerminalUI;
namespace FileTime.ConsoleUI.App;
@@ -9,6 +10,7 @@ public class App : IApplication
private readonly IConsoleAppState _consoleAppState;
//private readonly IAppKeyService<Key> _appKeyService;
private readonly MainWindow _mainWindow;
private readonly IApplicationContext _applicationContext;
private readonly IKeyInputHandlerService _keyInputHandlerService;
public App(
@@ -16,20 +18,23 @@ public class App : IApplication
IKeyInputHandlerService keyInputHandlerService,
IConsoleAppState consoleAppState,
//IAppKeyService<Key> appKeyService,
MainWindow mainWindow)
MainWindow mainWindow,
IApplicationContext applicationContext)
{
_lifecycleService = lifecycleService;
_keyInputHandlerService = keyInputHandlerService;
_consoleAppState = consoleAppState;
//_appKeyService = appKeyService;
_mainWindow = mainWindow;
_applicationContext = applicationContext;
}
public void Run()
{
Console.WriteLine("Loading...");
Task.Run(async () => await _lifecycleService.InitStartupHandlersAsync()).Wait();
_mainWindow.Initialize();
_applicationContext.EventLoop.Run();
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using DeclarativeProperty;
using FileTime.App.Core.ViewModels;
using FileTime.ConsoleUI.App.Extensions;
using TerminalUI;
using TerminalUI.Controls;
using TerminalUI.Extensions;
@@ -11,23 +10,39 @@ namespace FileTime.ConsoleUI.App;
public class MainWindow
{
private readonly IConsoleAppState _consoleAppState;
private readonly IApplicationContext _applicationContext;
private const int ParentColumnWidth = 20;
public MainWindow(IConsoleAppState consoleAppState)
public MainWindow(IConsoleAppState consoleAppState, IApplicationContext applicationContext)
{
_consoleAppState = consoleAppState;
_applicationContext = applicationContext;
}
public void Initialize()
{
ListView<IAppState, IItemViewModel> selectedItemsView = new();
ListView<IAppState, IItemViewModel> selectedItemsView = new()
{
ApplicationContext = _applicationContext
};
selectedItemsView.DataContext = _consoleAppState;
selectedItemsView.ItemTemplate = item =>
{
var textBlock = item.CreateChild<TextBlock<IItemViewModel>>();
textBlock.Bind(
textBlock,
dc => dc == null ? string.Empty : dc.DisplayNameText,
tb => tb.Text
);
return textBlock;
};
selectedItemsView.Bind(
selectedItemsView,
appState => appState.SelectedTab.Map(t => t == null ? null : t.CurrentItems).Switch(),
v => v.ItemsSource);
selectedItemsView.Render();
selectedItemsView.RequestRerender();
}
}

View File

@@ -5,6 +5,7 @@ using FileTime.ConsoleUI.App.Services;
using FileTime.Core.Interactions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using TerminalUI;
namespace FileTime.ConsoleUI.App;
@@ -20,6 +21,7 @@ public static class Startup
services.TryAddSingleton<IKeyInputHandlerService, KeyInputHandlerService>();
services.AddSingleton<CustomLoggerSink>();
services.TryAddSingleton<IApplicationContext, ApplicationContext>();
return services;
}
}

View File

@@ -19,6 +19,7 @@
<ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Library\CircularBuffer\CircularBuffer.csproj" />
<ProjectReference Include="..\..\Library\Defer\Defer.csproj" />
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />

View File

@@ -1,8 +1,8 @@
using System.Collections.ObjectModel;
using CircularBuffer;
using DeclarativeProperty;
using DynamicData;
using FileTime.App.Core.Services;
using FileTime.Core.Collections;
using FileTime.Core.Helper;
using FileTime.Core.Models;
using FileTime.Core.Timeline;

View File

@@ -119,6 +119,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.ConsoleUI.App.Abst
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalUI", "Library\TerminalUI\TerminalUI.csproj", "{2F01FC4C-D942-48B0-B61C-7C5BEAED4787}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CircularBuffer", "Library\CircularBuffer\CircularBuffer.csproj", "{AF4FE804-12D9-46E2-A584-BFF6D4509766}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -321,6 +323,10 @@ Global
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787}.Release|Any CPU.Build.0 = Release|Any CPU
{AF4FE804-12D9-46E2-A584-BFF6D4509766}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF4FE804-12D9-46E2-A584-BFF6D4509766}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF4FE804-12D9-46E2-A584-BFF6D4509766}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF4FE804-12D9-46E2-A584-BFF6D4509766}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -376,6 +382,7 @@ Global
{826AFD32-E36B-48BA-BC1E-1476B393CF24} = {A5291117-3001-498B-AC8B-E14F71F72570}
{81F44BBB-6F89-41B4-89F1-4A3204843DB5} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
{2F01FC4C-D942-48B0-B61C-7C5BEAED4787} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
{AF4FE804-12D9-46E2-A584-BFF6D4509766} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}

View File

@@ -0,0 +1,12 @@
namespace TerminalUI;
public class ApplicationContext : IApplicationContext
{
public IEventLoop EventLoop { get; init; }
public bool IsRunning { get; set; }
public ApplicationContext()
{
EventLoop = new EventLoop(this);
}
}

View File

@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using TerminalUI.Controls;
using TerminalUI.Traits;
@@ -8,41 +9,103 @@ namespace TerminalUI;
public class Binding<TDataContext, TResult> : IDisposable
{
private readonly Func<TDataContext, TResult> _dataContextMapper;
private IView<TDataContext> _view;
private IView<TDataContext> _dataSourceView;
private object? _propertySource;
private PropertyInfo _targetProperty;
private readonly List<string> _rerenderProperties;
private readonly IDisposableCollection? _propertySourceDisposableCollection;
private INotifyPropertyChanged? _dataSourceLastDataContext;
public Binding(
IView<TDataContext> view,
Func<TDataContext, TResult> dataContextMapper,
object? propertySource,
PropertyInfo targetProperty
IView<TDataContext> dataSourceView,
Expression<Func<TDataContext?, TResult>> dataContextExpression,
object? propertySource,
PropertyInfo targetProperty,
IEnumerable<string>? rerenderProperties = null
)
{
_view = view;
_dataContextMapper = dataContextMapper;
ArgumentNullException.ThrowIfNull(dataSourceView);
ArgumentNullException.ThrowIfNull(dataContextExpression);
ArgumentNullException.ThrowIfNull(targetProperty);
_dataSourceView = dataSourceView;
_dataContextMapper = dataContextExpression.Compile();
_propertySource = propertySource;
_targetProperty = targetProperty;
view.PropertyChanged += View_PropertyChanged;
_targetProperty.SetValue(_propertySource, _dataContextMapper(_view.DataContext));
if(propertySource is IDisposableCollection disposableCollection)
disposableCollection.AddDisposable(this);
view.AddDisposable(this);
_rerenderProperties = rerenderProperties?.ToList() ?? new List<string>();
FindReactiveProperties(dataContextExpression);
dataSourceView.PropertyChanged += View_PropertyChanged;
var initialValue = _dataContextMapper(_dataSourceView.DataContext);
_targetProperty.SetValue(_propertySource, initialValue);
if (propertySource is IDisposableCollection propertySourceDisposableCollection)
{
propertySourceDisposableCollection.AddDisposable(this);
_propertySourceDisposableCollection = propertySourceDisposableCollection;
}
if (_dataSourceView.DataContext is INotifyPropertyChanged dataSourcePropertyChanged)
{
_dataSourceLastDataContext = dataSourcePropertyChanged;
dataSourcePropertyChanged.PropertyChanged += DataContext_PropertyChanged;
}
dataSourceView.AddDisposable(this);
}
private void FindReactiveProperties(Expression expression)
{
if (expression is LambdaExpression lambdaExpression)
{
FindReactiveProperties(lambdaExpression.Body);
}
else if (expression is ConditionalExpression conditionalExpression)
{
FindReactiveProperties(conditionalExpression.IfFalse);
FindReactiveProperties(conditionalExpression.IfTrue);
}
else if (expression is MemberExpression {Member: PropertyInfo dataContextPropertyInfo})
{
_rerenderProperties.Add(dataContextPropertyInfo.Name);
}
//TODO: Handle other expression types
}
private void View_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != nameof(IView<TDataContext>.DataContext)) return;
_targetProperty.SetValue(_propertySource, _dataContextMapper(_view.DataContext));
if (_dataSourceLastDataContext is not null)
{
_dataSourceLastDataContext.PropertyChanged -= DataContext_PropertyChanged;
}
if (_dataSourceView.DataContext is INotifyPropertyChanged dataSourcePropertyChanged)
{
_dataSourceLastDataContext = dataSourcePropertyChanged;
dataSourcePropertyChanged.PropertyChanged += DataContext_PropertyChanged;
}
UpdateTargetProperty();
}
private void DataContext_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == null
|| !_rerenderProperties.Contains(e.PropertyName)) return;
UpdateTargetProperty();
}
private void UpdateTargetProperty()
=> _targetProperty.SetValue(_propertySource, _dataContextMapper(_dataSourceView.DataContext));
public void Dispose()
{
_view.PropertyChanged -= View_PropertyChanged;
_view = null!;
_propertySourceDisposableCollection?.RemoveDisposable(this);
_dataSourceView.RemoveDisposable(this);
_dataSourceView.PropertyChanged -= View_PropertyChanged;
_dataSourceView = null!;
_propertySource = null!;
_targetProperty = null!;
}

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);
}
}
}

View File

@@ -0,0 +1,130 @@
using System.Buffers;
using System.Collections.Concurrent;
using TerminalUI.Controls;
namespace TerminalUI;
public class EventLoop : IEventLoop
{
private readonly IApplicationContext _applicationContext;
private readonly object _lock = new();
private readonly ArrayPool<IView> _viewPool = ArrayPool<IView>.Shared;
private readonly ConcurrentBag<IView> _viewsToRenderInstantly = new();
private readonly LinkedList<IView> _viewsToRender = new();
public EventLoop(IApplicationContext applicationContext)
{
_applicationContext = applicationContext;
}
public void Run()
{
_applicationContext.IsRunning = true;
while (_applicationContext.IsRunning)
{
Render();
Thread.Sleep(10);
}
}
public void Render()
{
IView[]? viewsToRenderCopy = null;
IView[]? viewsAlreadyRendered = null;
try
{
int viewsToRenderCopyCount;
IView[]? viewsToRenderInstantly;
lock (_lock)
{
CleanViewsToRender();
viewsToRenderCopyCount = _viewsToRender.Count;
viewsToRenderCopy = _viewPool.Rent(_viewsToRender.Count);
_viewsToRender.CopyTo(viewsToRenderCopy, 0);
viewsToRenderInstantly = _viewsToRenderInstantly.ToArray();
_viewsToRenderInstantly.Clear();
}
viewsAlreadyRendered = _viewPool.Rent(viewsToRenderCopy.Length + viewsToRenderInstantly.Length);
var viewsAlreadyRenderedIndex = 0;
foreach (var view in viewsToRenderInstantly)
{
if (Contains(viewsAlreadyRendered, view, viewsAlreadyRenderedIndex)) continue;
view.Render();
viewsAlreadyRendered[viewsAlreadyRenderedIndex++] = view;
}
for (var i = 0; i < viewsToRenderCopyCount; i++)
{
var view = viewsToRenderCopy[i];
if (Contains(viewsAlreadyRendered, view, viewsAlreadyRenderedIndex)) continue;
view.Render();
viewsAlreadyRendered[viewsAlreadyRenderedIndex++] = view;
}
}
finally
{
if (viewsToRenderCopy is not null)
_viewPool.Return(viewsToRenderCopy);
if (viewsAlreadyRendered is not null)
_viewPool.Return(viewsAlreadyRendered);
}
}
private void CleanViewsToRender()
{
IView[]? viewsAlreadyProcessed = null;
try
{
viewsAlreadyProcessed = _viewPool.Rent(_viewsToRender.Count);
var viewsAlreadyProcessedIndex = 0;
var currentItem = _viewsToRender.First;
for (var i = 0; i < _viewsToRender.Count && currentItem is not null; i++)
{
if (Contains(viewsAlreadyProcessed, currentItem.Value, viewsAlreadyProcessedIndex))
{
var itemToRemove = currentItem;
currentItem = currentItem.Next;
_viewsToRender.Remove(itemToRemove);
continue;
}
viewsAlreadyProcessed[viewsAlreadyProcessedIndex++] = currentItem.Value;
}
}
finally
{
if (viewsAlreadyProcessed is not null)
{
_viewPool.Return(viewsAlreadyProcessed);
}
}
}
private static bool Contains(IView[] views, IView view, int max)
{
for (var i = 0; i < max; i++)
{
if (views[i] == view) return true;
}
return false;
}
public void AddViewToRender(IView view)
{
lock (_lock)
{
_viewsToRender.AddLast(view);
}
}
}

View File

@@ -6,18 +6,22 @@ namespace TerminalUI.Extensions;
public static class Binding
{
public static Binding<TDataContext, TResult> Bind<TDataContext, TResult, TView>(
public static Binding<TDataContext, TResult> Bind<TView, TDataContext, TResult>(
this TView targetView,
IView<TDataContext> view,
Expression<Func<TDataContext, TResult>> dataContextExpression,
Expression<Func<TView, TResult>> propertyExpression)
IView<TDataContext> dataSourceView,
Expression<Func<TDataContext?, TResult>> dataContextExpression,
Expression<Func<TView, TResult>> propertyExpression,
IEnumerable<string>? rerenderProperties = null)
{
var dataContextMapper = dataContextExpression.Compile();
if (propertyExpression.Body is not MemberExpression memberExpression
|| memberExpression.Member is not PropertyInfo propertyInfo)
if (propertyExpression.Body is not MemberExpression {Member: PropertyInfo propertyInfo})
throw new AggregateException(nameof(propertyExpression) + " must be a property expression");
return new Binding<TDataContext, TResult>(view, dataContextMapper, targetView, propertyInfo);
return new Binding<TDataContext, TResult>(
dataSourceView,
dataContextExpression,
targetView,
propertyInfo,
rerenderProperties
);
}
}

View File

@@ -0,0 +1,7 @@
namespace TerminalUI;
public interface IApplicationContext
{
IEventLoop EventLoop { get; init; }
bool IsRunning { get; set; }
}

View File

@@ -0,0 +1,10 @@
using TerminalUI.Controls;
namespace TerminalUI;
public interface IEventLoop
{
void Render();
void AddViewToRender(IView view);
void Run();
}

View File

@@ -10,4 +10,11 @@
<ProjectReference Include="..\DeclarativeProperty\DeclarativeProperty.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using TerminalUI.Controls;
namespace TerminalUI.Traits;
public interface IContentRenderer
{
IView? Content { get; set; }
Action ContentRendererMethod { get; set; }
}

View File

@@ -3,4 +3,5 @@
public interface IDisposableCollection : IDisposable
{
void AddDisposable(IDisposable disposable);
void RemoveDisposable(IDisposable disposable);
}