Force rerender on visibility change
This commit is contained in:
@@ -57,7 +57,7 @@ public class App : IApplication
|
|||||||
|
|
||||||
foreach (var rootView in _mainWindow.RootViews())
|
foreach (var rootView in _mainWindow.RootViews())
|
||||||
{
|
{
|
||||||
_applicationContext.EventLoop.AddViewToRender(rootView);
|
_applicationContext.RenderEngine.AddViewToPermanentRenderGroup(rootView);
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationContext.IsRunning = true;
|
_applicationContext.IsRunning = true;
|
||||||
@@ -101,5 +101,5 @@ public class App : IApplication
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Render() => _applicationContext.EventLoop.Run();
|
private void Render() => _applicationContext.RenderEngine.Run();
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using FileTime.App.Core.Configuration;
|
using FileTime.App.Core.Configuration;
|
||||||
using FileTime.App.Core.Models;
|
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.ConsoleUI.App.Configuration;
|
using FileTime.ConsoleUI.App.Configuration;
|
||||||
@@ -10,9 +9,6 @@ using FileTime.Core.Interactions;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using TerminalUI;
|
|
||||||
using TerminalUI.ConsoleDrivers;
|
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
@@ -30,18 +26,8 @@ public static class Startup
|
|||||||
services.AddSingleton<CustomLoggerSink>();
|
services.AddSingleton<CustomLoggerSink>();
|
||||||
services.TryAddSingleton(new ApplicationConfiguration(true));
|
services.TryAddSingleton(new ApplicationConfiguration(true));
|
||||||
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
||||||
services.TryAddSingleton<IFocusManager, FocusManager>();
|
|
||||||
|
|
||||||
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
||||||
|
|
||||||
services.TryAddSingleton<IApplicationContext>(sp
|
|
||||||
=> new ApplicationContext
|
|
||||||
{
|
|
||||||
ConsoleDriver = sp.GetRequiredService<IConsoleDriver>(),
|
|
||||||
LoggerFactory = sp.GetRequiredService<ILoggerFactory>(),
|
|
||||||
FocusManager = sp.GetRequiredService<IFocusManager>(),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using FileTime.Tools.Compression;
|
|||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using TerminalUI.DependencyInjection;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI;
|
namespace FileTime.ConsoleUI;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ public static class DI
|
|||||||
.RegisterDefaultServices(configuration: configuration)
|
.RegisterDefaultServices(configuration: configuration)
|
||||||
.AddConsoleServices(configuration)
|
.AddConsoleServices(configuration)
|
||||||
.AddConsoleViews()
|
.AddConsoleViews()
|
||||||
|
.AddTerminalUi()
|
||||||
.AddLocalProviderServices()
|
.AddLocalProviderServices()
|
||||||
.AddServerCoreServices()
|
.AddServerCoreServices()
|
||||||
.AddFrequencyNavigation()
|
.AddFrequencyNavigation()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.ContainerSizeScanner\FileTime.App.ContainerSizeScanner.csproj" />
|
<ProjectReference Include="..\..\AppCommon\FileTime.App.ContainerSizeScanner\FileTime.App.ContainerSizeScanner.csproj" />
|
||||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
<ProjectReference Include="..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
||||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.FrequencyNavigation\FileTime.App.FrequencyNavigation.csproj" />
|
<ProjectReference Include="..\..\AppCommon\FileTime.App.FrequencyNavigation\FileTime.App.FrequencyNavigation.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Library\TerminalUI.DependencyInjection\TerminalUI.DependencyInjection.csproj" />
|
||||||
<ProjectReference Include="..\..\Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj" />
|
<ProjectReference Include="..\..\Tools\FileTime.Tools.Compression\FileTime.Tools.Compression.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj" />
|
<ProjectReference Include="..\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.ConsoleUI.Styles\FileTime.ConsoleUI.Styles.csproj" />
|
<ProjectReference Include="..\FileTime.ConsoleUI.Styles\FileTime.ConsoleUI.Styles.csproj" />
|
||||||
|
|||||||
@@ -127,6 +127,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservableComputations.Exte
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeneralInputKey", "Library\GeneralInputKey\GeneralInputKey.csproj", "{91AE5B64-042B-4660-A8E8-D247E6E14A1E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeneralInputKey", "Library\GeneralInputKey\GeneralInputKey.csproj", "{91AE5B64-042B-4660-A8E8-D247E6E14A1E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerminalUI.DependencyInjection", "Library\TerminalUI.DependencyInjection\TerminalUI.DependencyInjection.csproj", "{E72F6430-0E6E-4818-BD5F-114893ACB18E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -345,6 +347,10 @@ Global
|
|||||||
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{91AE5B64-042B-4660-A8E8-D247E6E14A1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E72F6430-0E6E-4818-BD5F-114893ACB18E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E72F6430-0E6E-4818-BD5F-114893ACB18E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E72F6430-0E6E-4818-BD5F-114893ACB18E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E72F6430-0E6E-4818-BD5F-114893ACB18E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -404,6 +410,7 @@ Global
|
|||||||
{CCB6F86A-7E80-448E-B543-DF9DB337C42A} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
|
{CCB6F86A-7E80-448E-B543-DF9DB337C42A} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
|
||||||
{6C3C3151-9341-4792-9B0B-A11C0658524E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
{6C3C3151-9341-4792-9B0B-A11C0658524E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
{91AE5B64-042B-4660-A8E8-D247E6E14A1E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
{91AE5B64-042B-4660-A8E8-D247E6E14A1E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
|
{E72F6430-0E6E-4818-BD5F-114893ACB18E} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TerminalUI\TerminalUI.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
namespace TerminalUI.DependencyInjection;
|
||||||
|
|
||||||
|
public static class TerminalUiServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddTerminalUi(this IServiceCollection collection)
|
||||||
|
{
|
||||||
|
collection.TryAddSingleton<IFocusManager, FocusManager>();
|
||||||
|
collection.TryAddSingleton<IRenderEngine, RenderEngine>();
|
||||||
|
collection.TryAddSingleton<IApplicationContext, ApplicationContext>();
|
||||||
|
collection.TryAddSingleton<IEventLoop, EventLoop>();
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using FileTime.App.Core.Models;
|
using FileTime.App.Core.Models;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TerminalUI.ConsoleDrivers;
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
|
||||||
@@ -6,15 +7,23 @@ namespace TerminalUI;
|
|||||||
|
|
||||||
public class ApplicationContext : IApplicationContext
|
public class ApplicationContext : IApplicationContext
|
||||||
{
|
{
|
||||||
public required IConsoleDriver ConsoleDriver { get; init; }
|
private readonly Lazy<IConsoleDriver> _consoleDriver;
|
||||||
public required IFocusManager FocusManager { get; init; }
|
private readonly Lazy<IFocusManager> _focusManager;
|
||||||
public ILoggerFactory? LoggerFactory { get; init; }
|
private readonly Lazy<ILoggerFactory?> _loggerFactory;
|
||||||
public IEventLoop EventLoop { get; init; }
|
private readonly Lazy<IRenderEngine> _renderEngine;
|
||||||
|
|
||||||
|
public IConsoleDriver ConsoleDriver => _consoleDriver.Value;
|
||||||
|
public IFocusManager FocusManager => _focusManager.Value;
|
||||||
|
public ILoggerFactory? LoggerFactory => _loggerFactory.Value;
|
||||||
|
public IRenderEngine RenderEngine => _renderEngine.Value;
|
||||||
public bool IsRunning { get; set; }
|
public bool IsRunning { get; set; }
|
||||||
public char EmptyCharacter { get; init; } = ' ';
|
public char EmptyCharacter { get; init; } = ' ';
|
||||||
|
|
||||||
public ApplicationContext()
|
public ApplicationContext(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
EventLoop = new EventLoop(this);
|
_consoleDriver = new Lazy<IConsoleDriver>(serviceProvider.GetRequiredService<IConsoleDriver>);
|
||||||
|
_focusManager = new Lazy<IFocusManager>(serviceProvider.GetRequiredService<IFocusManager>);
|
||||||
|
_loggerFactory = new Lazy<ILoggerFactory?>(serviceProvider.GetService<ILoggerFactory?>);
|
||||||
|
_renderEngine = new Lazy<IRenderEngine>(serviceProvider.GetRequiredService<IRenderEngine>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ namespace TerminalUI.Controls;
|
|||||||
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
||||||
{
|
{
|
||||||
private readonly ObservableCollection<IView> _children = new();
|
private readonly ObservableCollection<IView> _children = new();
|
||||||
private readonly Dictionary<IView, bool> _visibilities = new();
|
|
||||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||||
public ChildInitializer<T> ChildInitializer { get; }
|
public ChildInitializer<T> ChildInitializer { get; }
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationContext?.EventLoop.RequestRerender();
|
ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,18 +42,6 @@ public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SaveVisibilities()
|
|
||||||
{
|
|
||||||
_visibilities.Clear();
|
|
||||||
foreach (var child in _children)
|
|
||||||
{
|
|
||||||
_visibilities[child] = child.IsVisible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool? GetLastVisibility(IView view)
|
|
||||||
=> _visibilities.TryGetValue(view, out var visibility) ? visibility : null;
|
|
||||||
|
|
||||||
public override TChild AddChild<TChild>(TChild child)
|
public override TChild AddChild<TChild>(TChild child)
|
||||||
{
|
{
|
||||||
child = base.AddChild(child);
|
child = base.AddChild(child);
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
using TerminalUI.ViewExtensions;
|
using TerminalUI.ViewExtensions;
|
||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public class Grid<T> : ChildContainerView<T>
|
public class Grid<T> : ChildContainerView<T>, IVisibilityChangeHandler
|
||||||
{
|
{
|
||||||
private List<RowDefinition> _rowDefinitions = new() {RowDefinition.Star(1)};
|
private List<RowDefinition> _rowDefinitions = new() {RowDefinition.Star(1)};
|
||||||
private List<ColumnDefinition> _columnDefinitions = new() {ColumnDefinition.Star(1)};
|
private List<ColumnDefinition> _columnDefinitions = new() {ColumnDefinition.Star(1)};
|
||||||
|
private List<IView> _forceRerenderChildren = new();
|
||||||
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
private ILogger<Grid<T>>? Logger => ApplicationContext?.LoggerFactory?.CreateLogger<Grid<T>>();
|
private ILogger<Grid<T>>? Logger => ApplicationContext?.LoggerFactory?.CreateLogger<Grid<T>>();
|
||||||
|
|
||||||
private delegate void WithSizes(RenderContext renderContext, ReadOnlySpan<int> widths, ReadOnlySpan<int> heights);
|
private delegate void WithSizes(RenderContext renderContext, ReadOnlySpan<int> widths, ReadOnlySpan<int> heights);
|
||||||
@@ -150,6 +153,13 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
new Option<Size>(size, true),
|
new Option<Size>(size, true),
|
||||||
(context, columnWidths, rowHeights) =>
|
(context, columnWidths, rowHeights) =>
|
||||||
{
|
{
|
||||||
|
IReadOnlyList<IView> forceRerenderChildren;
|
||||||
|
lock (_forceRerenderChildrenLock)
|
||||||
|
{
|
||||||
|
forceRerenderChildren = _forceRerenderChildren;
|
||||||
|
_forceRerenderChildren.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
context = new RenderContext(
|
context = new RenderContext(
|
||||||
context.ConsoleDriver,
|
context.ConsoleDriver,
|
||||||
context.ForceRerender,
|
context.ForceRerender,
|
||||||
@@ -169,7 +179,8 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
rowHeights,
|
rowHeights,
|
||||||
viewsByPosition,
|
viewsByPosition,
|
||||||
column,
|
column,
|
||||||
row
|
row,
|
||||||
|
forceRerenderChildren
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,29 +188,17 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
private void RenderViewsByPosition(
|
private void RenderViewsByPosition(RenderContext context,
|
||||||
RenderContext context,
|
|
||||||
Position gridPosition,
|
Position gridPosition,
|
||||||
ReadOnlySpan<int> columnWidths,
|
ReadOnlySpan<int> columnWidths,
|
||||||
ReadOnlySpan<int> rowHeights,
|
ReadOnlySpan<int> rowHeights,
|
||||||
IReadOnlyDictionary<(int, int), List<IView>> viewsByPosition,
|
IReadOnlyDictionary<(int, int), List<IView>> viewsByPosition,
|
||||||
int column,
|
int column,
|
||||||
int row)
|
int row,
|
||||||
|
IReadOnlyList<IView> forceRerenderChildren)
|
||||||
{
|
{
|
||||||
if (!viewsByPosition.TryGetValue((column, row), out var children)) return;
|
if (!viewsByPosition.TryGetValue((column, row), out var children)) return;
|
||||||
|
|
||||||
var anyChangedVisibility = false;
|
|
||||||
|
|
||||||
foreach (var child in children)
|
|
||||||
{
|
|
||||||
var lastVisibility = GetLastVisibility(child);
|
|
||||||
if (lastVisibility is { } b && b != child.IsVisible)
|
|
||||||
{
|
|
||||||
anyChangedVisibility = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var width = columnWidths[column];
|
var width = columnWidths[column];
|
||||||
var height = rowHeights[row];
|
var height = rowHeights[row];
|
||||||
var renderSize = new Size(width, height);
|
var renderSize = new Size(width, height);
|
||||||
@@ -212,7 +211,7 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
row
|
row
|
||||||
);
|
);
|
||||||
|
|
||||||
var needsRerender = anyChangedVisibility;
|
var needsRerender = children.Any(forceRerenderChildren.Contains);
|
||||||
if (needsRerender)
|
if (needsRerender)
|
||||||
{
|
{
|
||||||
context = new RenderContext(
|
context = new RenderContext(
|
||||||
@@ -483,4 +482,20 @@ public class Grid<T> : ChildContainerView<T>
|
|||||||
|
|
||||||
ColumnDefinitions = columnDefinitions;
|
ColumnDefinitions = columnDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChildVisibilityChanged(IView child)
|
||||||
|
{
|
||||||
|
var viewToForceRerender = child;
|
||||||
|
while (viewToForceRerender.VisualParent != null && viewToForceRerender.VisualParent != this)
|
||||||
|
{
|
||||||
|
viewToForceRerender = viewToForceRerender.VisualParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewToForceRerender.VisualParent != this) return;
|
||||||
|
|
||||||
|
lock (_forceRerenderChildrenLock)
|
||||||
|
{
|
||||||
|
_forceRerenderChildren.Add(viewToForceRerender);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ public interface IView : INotifyPropertyChanged, IDisposableCollection
|
|||||||
IApplicationContext? ApplicationContext { get; set; }
|
IApplicationContext? ApplicationContext { get; set; }
|
||||||
List<object> Extensions { get; }
|
List<object> Extensions { get; }
|
||||||
RenderMethod RenderMethod { get; set; }
|
RenderMethod RenderMethod { get; set; }
|
||||||
|
IView? VisualParent { get; set; }
|
||||||
event Action<IView> Disposed;
|
event Action<IView> Disposed;
|
||||||
|
|
||||||
Size GetRequestedSize();
|
Size GetRequestedSize();
|
||||||
|
|||||||
@@ -102,14 +102,14 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
|
|||||||
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
|
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
|
||||||
{
|
{
|
||||||
((INotifyCollectionChanged) observableDeclarative).CollectionChanged +=
|
((INotifyCollectionChanged) observableDeclarative).CollectionChanged +=
|
||||||
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
(_, _) => ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
|
|
||||||
_getItems = () => observableDeclarative;
|
_getItems = () => observableDeclarative;
|
||||||
}
|
}
|
||||||
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
|
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
|
||||||
{
|
{
|
||||||
((INotifyCollectionChanged) readOnlyObservableDeclarative).CollectionChanged +=
|
((INotifyCollectionChanged) readOnlyObservableDeclarative).CollectionChanged +=
|
||||||
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
|
(_, _) => ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
|
|
||||||
_getItems = () => readOnlyObservableDeclarative;
|
_getItems = () => readOnlyObservableDeclarative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ public partial class TextBox<T> : View<T>, IFocusable
|
|||||||
HandleKeyInputInternal(keyEventArgs);
|
HandleKeyInputInternal(keyEventArgs);
|
||||||
if (keyEventArgs.Handled)
|
if (keyEventArgs.Handled)
|
||||||
{
|
{
|
||||||
ApplicationContext?.EventLoop.RequestRerender();
|
ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public abstract partial class View<T> : IView<T>
|
|||||||
[Notify] private string? _name;
|
[Notify] private string? _name;
|
||||||
[Notify] private IApplicationContext? _applicationContext;
|
[Notify] private IApplicationContext? _applicationContext;
|
||||||
[Notify] private bool _attached;
|
[Notify] private bool _attached;
|
||||||
|
[Notify] private IView? _visualParent;
|
||||||
|
|
||||||
protected ObservableCollection<IView> VisualChildren { get; } = new();
|
protected ObservableCollection<IView> VisualChildren { get; } = new();
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ public abstract partial class View<T> : IView<T>
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
ApplicationContext?.EventLoop.RequestRerender();
|
ApplicationContext?.RenderEngine.RequestRerender(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.PropertyName == nameof(Attached))
|
if (e.PropertyName == nameof(Attached))
|
||||||
@@ -107,6 +108,10 @@ public abstract partial class View<T> : IView<T>
|
|||||||
visualChild.ApplicationContext = ApplicationContext;
|
visualChild.ApplicationContext = ApplicationContext;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (e.PropertyName == nameof(IsVisible))
|
||||||
|
{
|
||||||
|
ApplicationContext?.RenderEngine.VisibilityChanged(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool DefaultRenderer(RenderContext renderContext, Position position, Size size);
|
protected abstract bool DefaultRenderer(RenderContext renderContext, Position position, Size size);
|
||||||
@@ -264,12 +269,8 @@ public abstract partial class View<T> : IView<T>
|
|||||||
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
|
||||||
{
|
{
|
||||||
child.DataContext = DataContext;
|
child.DataContext = DataContext;
|
||||||
CopyCommonPropertiesToNewChild(child);
|
|
||||||
VisualChildren.Add(child);
|
|
||||||
|
|
||||||
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
var mapper = new DataContextMapper<T, T>(this, child, d => d);
|
||||||
AddDisposable(mapper);
|
SetupNewChild(child, mapper);
|
||||||
child.AddDisposable(mapper);
|
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
@@ -278,20 +279,21 @@ public abstract partial class View<T> : IView<T>
|
|||||||
where TChild : IView<TDataContext>
|
where TChild : IView<TDataContext>
|
||||||
{
|
{
|
||||||
child.DataContext = dataContextMapper(DataContext);
|
child.DataContext = dataContextMapper(DataContext);
|
||||||
CopyCommonPropertiesToNewChild(child);
|
|
||||||
VisualChildren.Add(child);
|
|
||||||
|
|
||||||
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
|
||||||
AddDisposable(mapper);
|
SetupNewChild(child, mapper);
|
||||||
child.AddDisposable(mapper);
|
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CopyCommonPropertiesToNewChild(IView child)
|
private void SetupNewChild(IView child, IDisposable dataContextmapper)
|
||||||
{
|
{
|
||||||
child.ApplicationContext = ApplicationContext;
|
child.ApplicationContext = ApplicationContext;
|
||||||
child.Attached = Attached;
|
child.Attached = Attached;
|
||||||
|
child.VisualParent = this;
|
||||||
|
VisualChildren.Add(child);
|
||||||
|
|
||||||
|
AddDisposable(dataContextmapper);
|
||||||
|
child.AddDisposable(dataContextmapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void RemoveChild<TDataContext>(IView<TDataContext> child)
|
public virtual void RemoveChild<TDataContext>(IView<TDataContext> child)
|
||||||
|
|||||||
@@ -1,86 +1,32 @@
|
|||||||
using TerminalUI.Controls;
|
namespace TerminalUI;
|
||||||
using TerminalUI.Models;
|
|
||||||
|
|
||||||
namespace TerminalUI;
|
|
||||||
|
|
||||||
public class EventLoop : IEventLoop
|
public class EventLoop : IEventLoop
|
||||||
{
|
{
|
||||||
private readonly IApplicationContext _applicationContext;
|
private readonly IApplicationContext _applicationContext;
|
||||||
private readonly object _lock = new();
|
private readonly List<Action> _permanentQueue = new();
|
||||||
private readonly List<IView> _viewsToRender = new();
|
|
||||||
private bool _rerenderRequested;
|
|
||||||
private bool _lastCursorVisible;
|
|
||||||
|
|
||||||
public EventLoop(IApplicationContext applicationContext)
|
public EventLoop(IApplicationContext applicationContext)
|
||||||
{
|
{
|
||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddToPermanentQueue(Action action) => _permanentQueue.Add(action);
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
_applicationContext.IsRunning = true;
|
_applicationContext.IsRunning = true;
|
||||||
_rerenderRequested = true;
|
|
||||||
while (_applicationContext.IsRunning)
|
while (_applicationContext.IsRunning)
|
||||||
{
|
{
|
||||||
Render();
|
ProcessQueues();
|
||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestRerender()
|
private void ProcessQueues()
|
||||||
{
|
{
|
||||||
lock (_lock)
|
foreach (var action in _permanentQueue)
|
||||||
{
|
{
|
||||||
_rerenderRequested = true;
|
action();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Render()
|
|
||||||
{
|
|
||||||
List<IView> viewsToRender;
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
if (!_rerenderRequested) return;
|
|
||||||
_rerenderRequested = false;
|
|
||||||
viewsToRender = _viewsToRender.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
var driver = _applicationContext.ConsoleDriver;
|
|
||||||
var size = driver.GetWindowSize();
|
|
||||||
var renderContext = new RenderContext(
|
|
||||||
driver,
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
foreach (var view in viewsToRender)
|
|
||||||
{
|
|
||||||
view.Attached = true;
|
|
||||||
view.GetRequestedSize();
|
|
||||||
view.Render(renderContext, new Position(0, 0), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_applicationContext.FocusManager.Focused is { } focused)
|
|
||||||
{
|
|
||||||
focused.SetCursorPosition(driver);
|
|
||||||
if (!_lastCursorVisible)
|
|
||||||
{
|
|
||||||
driver.SetCursorVisible(true);
|
|
||||||
_lastCursorVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_lastCursorVisible)
|
|
||||||
{
|
|
||||||
driver.SetCursorVisible(false);
|
|
||||||
_lastCursorVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddViewToRender(IView view)
|
|
||||||
{
|
|
||||||
lock (_lock)
|
|
||||||
{
|
|
||||||
_viewsToRender.Add(view);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,10 @@ namespace TerminalUI;
|
|||||||
|
|
||||||
public interface IApplicationContext
|
public interface IApplicationContext
|
||||||
{
|
{
|
||||||
IEventLoop EventLoop { get; init; }
|
IRenderEngine RenderEngine { get; }
|
||||||
bool IsRunning { get; set; }
|
bool IsRunning { get; set; }
|
||||||
IConsoleDriver ConsoleDriver { get; init; }
|
IConsoleDriver ConsoleDriver { get; }
|
||||||
ILoggerFactory? LoggerFactory { get; init; }
|
ILoggerFactory? LoggerFactory { get; }
|
||||||
char EmptyCharacter { get; init; }
|
char EmptyCharacter { get; }
|
||||||
IFocusManager FocusManager { get; init; }
|
IFocusManager FocusManager { get; }
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
using TerminalUI.Controls;
|
namespace TerminalUI;
|
||||||
|
|
||||||
namespace TerminalUI;
|
|
||||||
|
|
||||||
public interface IEventLoop
|
public interface IEventLoop
|
||||||
{
|
{
|
||||||
void Render();
|
|
||||||
void AddViewToRender(IView view);
|
|
||||||
void Run();
|
void Run();
|
||||||
void RequestRerender();
|
void AddToPermanentQueue(Action action);
|
||||||
}
|
}
|
||||||
11
src/Library/TerminalUI/IRenderEngine.cs
Normal file
11
src/Library/TerminalUI/IRenderEngine.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using TerminalUI.Controls;
|
||||||
|
|
||||||
|
namespace TerminalUI;
|
||||||
|
|
||||||
|
public interface IRenderEngine
|
||||||
|
{
|
||||||
|
void RequestRerender(IView view);
|
||||||
|
void VisibilityChanged(IView view);
|
||||||
|
void AddViewToPermanentRenderGroup(IView view);
|
||||||
|
void Run();
|
||||||
|
}
|
||||||
141
src/Library/TerminalUI/RenderEngine.cs
Normal file
141
src/Library/TerminalUI/RenderEngine.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using TerminalUI.Controls;
|
||||||
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
|
namespace TerminalUI;
|
||||||
|
|
||||||
|
public class RenderEngine : IRenderEngine
|
||||||
|
{
|
||||||
|
private readonly IApplicationContext _applicationContext;
|
||||||
|
private readonly IEventLoop _eventLoop;
|
||||||
|
private readonly object _lock = new();
|
||||||
|
private readonly List<IView> _permanentViewsToRender = new();
|
||||||
|
private readonly List<IView> _forcedTemporaryViewsToRender = new();
|
||||||
|
private bool _rerenderRequested = true;
|
||||||
|
private bool _lastCursorVisible;
|
||||||
|
|
||||||
|
public RenderEngine(IApplicationContext applicationContext, IEventLoop eventLoop)
|
||||||
|
{
|
||||||
|
_applicationContext = applicationContext;
|
||||||
|
_eventLoop = eventLoop;
|
||||||
|
|
||||||
|
_eventLoop.AddToPermanentQueue(Render);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestRerender(IView view) => RequestRerender();
|
||||||
|
|
||||||
|
public void VisibilityChanged(IView view)
|
||||||
|
{
|
||||||
|
IVisibilityChangeHandler? visibilityChangeHandler = null;
|
||||||
|
var parent = view.VisualParent;
|
||||||
|
while (parent?.VisualParent != null)
|
||||||
|
{
|
||||||
|
if (parent is IVisibilityChangeHandler v)
|
||||||
|
{
|
||||||
|
visibilityChangeHandler = v;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.VisualParent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibilityChangeHandler is null)
|
||||||
|
{
|
||||||
|
AddViewToForcedTemporaryRenderGroup(parent ?? view);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
visibilityChangeHandler.ChildVisibilityChanged(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run() => _eventLoop.Run();
|
||||||
|
|
||||||
|
public void RequestRerender()
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_rerenderRequested = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Render()
|
||||||
|
{
|
||||||
|
List<IView> permanentViewsToRender;
|
||||||
|
List<IView> forcedTemporaryViewsToRender;
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
if (!_rerenderRequested) return;
|
||||||
|
_rerenderRequested = false;
|
||||||
|
permanentViewsToRender = _permanentViewsToRender.ToList();
|
||||||
|
forcedTemporaryViewsToRender = _forcedTemporaryViewsToRender.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var driver = _applicationContext.ConsoleDriver;
|
||||||
|
var initialPosition = new Position(0, 0);
|
||||||
|
var size = driver.GetWindowSize();
|
||||||
|
|
||||||
|
RenderViews(
|
||||||
|
forcedTemporaryViewsToRender,
|
||||||
|
new RenderContext(
|
||||||
|
driver,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
initialPosition,
|
||||||
|
size);
|
||||||
|
|
||||||
|
RenderViews(
|
||||||
|
permanentViewsToRender,
|
||||||
|
new RenderContext(
|
||||||
|
driver,
|
||||||
|
false,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
),
|
||||||
|
initialPosition,
|
||||||
|
size);
|
||||||
|
|
||||||
|
if (_applicationContext.FocusManager.Focused is { } focused)
|
||||||
|
{
|
||||||
|
focused.SetCursorPosition(driver);
|
||||||
|
if (!_lastCursorVisible)
|
||||||
|
{
|
||||||
|
driver.SetCursorVisible(true);
|
||||||
|
_lastCursorVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_lastCursorVisible)
|
||||||
|
{
|
||||||
|
driver.SetCursorVisible(false);
|
||||||
|
_lastCursorVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderViews(List<IView> views, RenderContext renderContext, Position position, Size size)
|
||||||
|
{
|
||||||
|
foreach (var view in views)
|
||||||
|
{
|
||||||
|
view.Attached = true;
|
||||||
|
view.GetRequestedSize();
|
||||||
|
view.Render(renderContext, position, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddViewToPermanentRenderGroup(IView view)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_permanentViewsToRender.Add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddViewToForcedTemporaryRenderGroup(IView view)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
_forcedTemporaryViewsToRender.Add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using TerminalUI.Controls;
|
||||||
|
|
||||||
|
namespace TerminalUI.Traits;
|
||||||
|
|
||||||
|
public interface IVisibilityChangeHandler
|
||||||
|
{
|
||||||
|
void ChildVisibilityChanged(IView child);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user