Force rerender on visibility change

This commit is contained in:
2023-08-12 09:09:17 +02:00
parent 1fde0df2d6
commit 16bdc1ed40
20 changed files with 292 additions and 147 deletions

View File

@@ -7,7 +7,6 @@ namespace TerminalUI.Controls;
public abstract class ChildContainerView<T> : View<T>, IChildContainer<T>
{
private readonly ObservableCollection<IView> _children = new();
private readonly Dictionary<IView, bool> _visibilities = new();
public ReadOnlyObservableCollection<IView> Children { 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)
{
child = base.AddChild(child);

View File

@@ -2,14 +2,17 @@
using Microsoft.Extensions.Logging;
using TerminalUI.Extensions;
using TerminalUI.Models;
using TerminalUI.Traits;
using TerminalUI.ViewExtensions;
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<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 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),
(context, columnWidths, rowHeights) =>
{
IReadOnlyList<IView> forceRerenderChildren;
lock (_forceRerenderChildrenLock)
{
forceRerenderChildren = _forceRerenderChildren;
_forceRerenderChildren.Clear();
}
context = new RenderContext(
context.ConsoleDriver,
context.ForceRerender,
@@ -169,7 +179,8 @@ public class Grid<T> : ChildContainerView<T>
rowHeights,
viewsByPosition,
column,
row
row,
forceRerenderChildren
);
}
}
@@ -177,29 +188,17 @@ public class Grid<T> : ChildContainerView<T>
return true;
});
private void RenderViewsByPosition(
RenderContext context,
private void RenderViewsByPosition(RenderContext context,
Position gridPosition,
ReadOnlySpan<int> columnWidths,
ReadOnlySpan<int> rowHeights,
IReadOnlyDictionary<(int, int), List<IView>> viewsByPosition,
int column,
int row)
int row,
IReadOnlyList<IView> forceRerenderChildren)
{
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 height = rowHeights[row];
var renderSize = new Size(width, height);
@@ -212,7 +211,7 @@ public class Grid<T> : ChildContainerView<T>
row
);
var needsRerender = anyChangedVisibility;
var needsRerender = children.Any(forceRerenderChildren.Contains);
if (needsRerender)
{
context = new RenderContext(
@@ -232,7 +231,7 @@ public class Grid<T> : ChildContainerView<T>
{
needsRerender = true;
context = new RenderContext(
context.ConsoleDriver,
context.ConsoleDriver,
true,
context.Foreground,
context.Background
@@ -483,4 +482,20 @@ public class Grid<T> : ChildContainerView<T>
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);
}
}
}

View File

@@ -27,6 +27,7 @@ public interface IView : INotifyPropertyChanged, IDisposableCollection
IApplicationContext? ApplicationContext { get; set; }
List<object> Extensions { get; }
RenderMethod RenderMethod { get; set; }
IView? VisualParent { get; set; }
event Action<IView> Disposed;
Size GetRequestedSize();

View File

@@ -102,14 +102,14 @@ public partial class ListView<TDataContext, TItem> : View<TDataContext>
if (_itemsSource is ObservableCollection<TItem> observableDeclarative)
{
((INotifyCollectionChanged) observableDeclarative).CollectionChanged +=
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
(_, _) => ApplicationContext?.RenderEngine.RequestRerender(this);
_getItems = () => observableDeclarative;
}
else if (_itemsSource is ReadOnlyObservableCollection<TItem> readOnlyObservableDeclarative)
{
((INotifyCollectionChanged) readOnlyObservableDeclarative).CollectionChanged +=
(_, _) => ApplicationContext?.EventLoop.RequestRerender();
(_, _) => ApplicationContext?.RenderEngine.RequestRerender(this);
_getItems = () => readOnlyObservableDeclarative;
}

View File

@@ -124,7 +124,7 @@ public partial class TextBox<T> : View<T>, IFocusable
HandleKeyInputInternal(keyEventArgs);
if (keyEventArgs.Handled)
{
ApplicationContext?.EventLoop.RequestRerender();
ApplicationContext?.RenderEngine.RequestRerender(this);
}
}

View File

@@ -30,7 +30,8 @@ public abstract partial class View<T> : IView<T>
[Notify] private string? _name;
[Notify] private IApplicationContext? _applicationContext;
[Notify] private bool _attached;
[Notify] private IView? _visualParent;
protected ObservableCollection<IView> VisualChildren { get; } = new();
public List<object> Extensions { 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))
@@ -100,13 +101,17 @@ public abstract partial class View<T> : IView<T>
visualChild.Attached = Attached;
}
}
else if(e.PropertyName == nameof(ApplicationContext))
else if (e.PropertyName == nameof(ApplicationContext))
{
foreach (var visualChild in VisualChildren)
{
visualChild.ApplicationContext = ApplicationContext;
}
}
else if (e.PropertyName == nameof(IsVisible))
{
ApplicationContext?.RenderEngine.VisibilityChanged(this);
}
}
protected abstract bool DefaultRenderer(RenderContext renderContext, Position position, Size size);
@@ -221,7 +226,7 @@ public abstract partial class View<T> : IView<T>
Size size)
{
var contentString = new string(content, size.Width);
for (var i = 0; i < size.Height; i++)
{
var currentPosition = position with {Y = position.Y + i};
@@ -234,7 +239,7 @@ public abstract partial class View<T> : IView<T>
protected void SetColorsForDriver(RenderContext renderContext)
{
var driver = renderContext.ConsoleDriver;
var foreground = Foreground ?? renderContext.Foreground;
var background = Background ?? renderContext.Background;
if (foreground is not null)
@@ -264,12 +269,8 @@ public abstract partial class View<T> : IView<T>
public virtual TChild AddChild<TChild>(TChild child) where TChild : IView<T>
{
child.DataContext = DataContext;
CopyCommonPropertiesToNewChild(child);
VisualChildren.Add(child);
var mapper = new DataContextMapper<T, T>(this, child, d => d);
AddDisposable(mapper);
child.AddDisposable(mapper);
SetupNewChild(child, mapper);
return child;
}
@@ -278,20 +279,21 @@ public abstract partial class View<T> : IView<T>
where TChild : IView<TDataContext>
{
child.DataContext = dataContextMapper(DataContext);
CopyCommonPropertiesToNewChild(child);
VisualChildren.Add(child);
var mapper = new DataContextMapper<T, TDataContext>(this, child, dataContextMapper);
AddDisposable(mapper);
child.AddDisposable(mapper);
SetupNewChild(child, mapper);
return child;
}
private void CopyCommonPropertiesToNewChild(IView child)
private void SetupNewChild(IView child, IDisposable dataContextmapper)
{
child.ApplicationContext = ApplicationContext;
child.Attached = Attached;
child.VisualParent = this;
VisualChildren.Add(child);
AddDisposable(dataContextmapper);
child.AddDisposable(dataContextmapper);
}
public virtual void RemoveChild<TDataContext>(IView<TDataContext> child)