Fill not render space (Render engine, Border)
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
using FileTime.App.CommandPalette.ViewModels;
|
using FileTime.App.CommandPalette.ViewModels;
|
||||||
using FileTime.ConsoleUI.App.Styling;
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
@@ -59,6 +60,7 @@ public class CommandPalette
|
|||||||
Margin = 5,
|
Margin = 5,
|
||||||
Padding = 1,
|
Padding = 1,
|
||||||
MaxWidth = 50,
|
MaxWidth = 50,
|
||||||
|
Fill = SpecialColor.None,
|
||||||
Content = new Grid<IRootViewModel>
|
Content = new Grid<IRootViewModel>
|
||||||
{
|
{
|
||||||
RowDefinitionsObject = "Auto *",
|
RowDefinitionsObject = "Auto *",
|
||||||
|
|||||||
@@ -52,14 +52,16 @@ public class ProgressBarExamples
|
|||||||
|
|
||||||
private void RenderProgressBar<T>(ProgressBar<T> progressBar, Position position)
|
private void RenderProgressBar<T>(ProgressBar<T> progressBar, Position position)
|
||||||
{
|
{
|
||||||
|
var s = new Size(10, 1);
|
||||||
var renderContext = new RenderContext(
|
var renderContext = new RenderContext(
|
||||||
_driver,
|
_driver,
|
||||||
true,
|
true,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
new(),
|
new(),
|
||||||
new TextFormatContext(true)
|
new TextFormatContext(true),
|
||||||
|
new bool[s.Width,s.Height]
|
||||||
);
|
);
|
||||||
progressBar.Render(renderContext, position, new Size(10, 1));
|
progressBar.Render(renderContext, position, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
96
src/Library/TerminalUI/Array2DHelper.cs
Normal file
96
src/Library/TerminalUI/Array2DHelper.cs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
using TerminalUI.Models;
|
||||||
|
|
||||||
|
namespace TerminalUI;
|
||||||
|
|
||||||
|
public static class Array2DHelper
|
||||||
|
{
|
||||||
|
public static void CombineArray2Ds<T, TResult>(T[,] array1, T[,] array2, Position array2Delta, TResult[,] array3, Func<T?, T?, TResult> func)
|
||||||
|
where T : struct
|
||||||
|
where TResult : struct
|
||||||
|
{
|
||||||
|
var array1Size = new Size(array1.GetLength(0), array1.GetLength(1));
|
||||||
|
var array2Size = new Size(array2.GetLength(0), array2.GetLength(1));
|
||||||
|
var array3Size = new Size(array3.GetLength(0), array3.GetLength(1));
|
||||||
|
|
||||||
|
var maxX = int.Max(array1Size.Width, array2Size.Width + array2Delta.X);
|
||||||
|
maxX = int.Max(maxX, array3Size.Width);
|
||||||
|
|
||||||
|
var maxY = int.Max(array1Size.Height, array2Size.Height + array2Delta.Y);
|
||||||
|
maxY = int.Max(maxY, array3Size.Height);
|
||||||
|
|
||||||
|
|
||||||
|
for (var x = 0; x < maxX; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < maxY; y++)
|
||||||
|
{
|
||||||
|
if (x >= array3Size.Width
|
||||||
|
|| y >= array3Size.Height) continue;
|
||||||
|
|
||||||
|
T? v1 = x < array1Size.Width && y < array1Size.Height ? array1[x, y] : null;
|
||||||
|
|
||||||
|
|
||||||
|
var array2X = x - array2Delta.X;
|
||||||
|
var array2Y = y - array2Delta.Y;
|
||||||
|
T? v2 = array2X >= 0
|
||||||
|
&& array2X < array2Size.Width
|
||||||
|
&& array2Y >= 0
|
||||||
|
&& array2Y < array2Size.Height
|
||||||
|
? array2[array2X, array2Y]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
array3[x, y] = func(v1, v2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RenderEmpty(
|
||||||
|
IConsoleDriver driver,
|
||||||
|
bool[,] updatedCells,
|
||||||
|
bool[,] resultCells,
|
||||||
|
char fillChar,
|
||||||
|
Position position,
|
||||||
|
Size size
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var endX = position.X + size.Width;
|
||||||
|
var endY = position.Y + size.Height;
|
||||||
|
for (var y = position.Y; y < endY; y++)
|
||||||
|
{
|
||||||
|
for (var x = position.X; x < endX; x++)
|
||||||
|
{
|
||||||
|
if (updatedCells[x, y]) continue;
|
||||||
|
|
||||||
|
var startIndex = x;
|
||||||
|
while (x < endX && !updatedCells[x, y])
|
||||||
|
{
|
||||||
|
x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderEmpty(driver, resultCells, fillChar, startIndex, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RenderEmpty(
|
||||||
|
IConsoleDriver driver,
|
||||||
|
bool[,] resultCells,
|
||||||
|
char fillChar,
|
||||||
|
int startX,
|
||||||
|
int endX,
|
||||||
|
int y
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var length = endX - startX;
|
||||||
|
Span<char> text = stackalloc char[length];
|
||||||
|
text.Fill(fillChar);
|
||||||
|
|
||||||
|
driver.SetCursorPosition(new Position(startX, y));
|
||||||
|
driver.Write(text);
|
||||||
|
|
||||||
|
for (var x = startX; x < endX; x++)
|
||||||
|
{
|
||||||
|
resultCells[x, y] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
|||||||
[Notify] private char _topRightChar = '┐';
|
[Notify] private char _topRightChar = '┐';
|
||||||
[Notify] private char _bottomLeftChar = '└';
|
[Notify] private char _bottomLeftChar = '└';
|
||||||
[Notify] private char _bottomRightChar = '┘';
|
[Notify] private char _bottomRightChar = '┘';
|
||||||
|
[Notify] private IColor? _fill;
|
||||||
|
|
||||||
public Border()
|
public Border()
|
||||||
{
|
{
|
||||||
@@ -25,6 +27,9 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
|||||||
RerenderProperties.Add(nameof(LeftChar));
|
RerenderProperties.Add(nameof(LeftChar));
|
||||||
RerenderProperties.Add(nameof(RightChar));
|
RerenderProperties.Add(nameof(RightChar));
|
||||||
RerenderProperties.Add(nameof(BottomChar));
|
RerenderProperties.Add(nameof(BottomChar));
|
||||||
|
RerenderProperties.Add(nameof(TopLeftChar));
|
||||||
|
RerenderProperties.Add(nameof(TopRightChar));
|
||||||
|
RerenderProperties.Add(nameof(Fill));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Size CalculateSize()
|
protected override Size CalculateSize()
|
||||||
@@ -51,12 +56,19 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
|||||||
+ DataContext?.GetType().Name);
|
+ DataContext?.GetType().Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var backgroundColor = Background ?? renderContext.Background;
|
||||||
|
var foregroundColor = Foreground ?? renderContext.Foreground;
|
||||||
|
var fillColor = Fill ?? Background ?? renderContext.Background;
|
||||||
|
|
||||||
var childPosition = new Position(X: position.X + _borderThickness.Left, Y: position.Y + _borderThickness.Top);
|
var childPosition = new Position(X: position.X + _borderThickness.Left, Y: position.Y + _borderThickness.Top);
|
||||||
var childSize = new Size(
|
var childSize = new Size(
|
||||||
Width: size.Width - _borderThickness.Left - _borderThickness.Right,
|
Width: size.Width - _borderThickness.Left - _borderThickness.Right,
|
||||||
Height: size.Height - _borderThickness.Top - _borderThickness.Bottom
|
Height: size.Height - _borderThickness.Top - _borderThickness.Bottom
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var childPositionWithoutPadding = childPosition;
|
||||||
|
var childSizeWithoutPadding = childSize;
|
||||||
|
|
||||||
if (_padding.Left > 0 || _padding.Top > 0 || _padding.Right > 0 || _padding.Bottom > 0)
|
if (_padding.Left > 0 || _padding.Top > 0 || _padding.Right > 0 || _padding.Bottom > 0)
|
||||||
{
|
{
|
||||||
childPosition = new Position(X: childPosition.X + _padding.Left, Y: childPosition.Y + _padding.Top);
|
childPosition = new Position(X: childPosition.X + _padding.Left, Y: childPosition.Y + _padding.Top);
|
||||||
@@ -66,85 +78,117 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentRendered = ContentRendererMethod(renderContext, childPosition, childSize);
|
// Same size as the original.
|
||||||
|
// Although wasting memory, but we would have to delta the position when setting "updatedcells"
|
||||||
|
// It is easier and also covers the fact the children use a different array
|
||||||
|
var borderChildUpdatedCells = new bool[
|
||||||
|
renderContext.UpdatedCells.GetLength(0),
|
||||||
|
renderContext.UpdatedCells.GetLength(1)
|
||||||
|
];
|
||||||
|
var childRenderContext = renderContext with {UpdatedCells = borderChildUpdatedCells};
|
||||||
|
|
||||||
|
var contentRendered = ContentRendererMethod(childRenderContext, childPosition, childSize);
|
||||||
|
|
||||||
if (contentRendered)
|
if (contentRendered)
|
||||||
{
|
{
|
||||||
var driver = renderContext.ConsoleDriver;
|
var driver = renderContext.ConsoleDriver;
|
||||||
driver.ResetStyle();
|
driver.ResetStyle();
|
||||||
SetColorsForDriver(renderContext);
|
SetStyleColor(renderContext, foregroundColor, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
RenderTopBorder(renderContext, position, size);
|
var updateCellsOnly = !contentRendered;
|
||||||
RenderBottomBorder(renderContext, position, size);
|
RenderTopBorder(renderContext, position, size, updateCellsOnly);
|
||||||
RenderLeftBorder(renderContext, position, size);
|
RenderBottomBorder(renderContext, position, size, updateCellsOnly);
|
||||||
RenderRightBorder(renderContext, position, size);
|
RenderLeftBorder(renderContext, position, size, updateCellsOnly);
|
||||||
|
RenderRightBorder(renderContext, position, size, updateCellsOnly);
|
||||||
|
|
||||||
RenderTopLeftCorner(renderContext, position);
|
RenderTopLeftCorner(renderContext, position, updateCellsOnly);
|
||||||
RenderTopRightCorner(renderContext, position, size);
|
RenderTopRightCorner(renderContext, position, size, updateCellsOnly);
|
||||||
RenderBottomLeftCorner(renderContext, position, size);
|
RenderBottomLeftCorner(renderContext, position, size, updateCellsOnly);
|
||||||
RenderBottomRightCorner(renderContext, position, size);
|
RenderBottomRightCorner(renderContext, position, size, updateCellsOnly);
|
||||||
|
|
||||||
//TODO render padding
|
if (fillColor != null)
|
||||||
|
{
|
||||||
|
SetStyleColor(renderContext, foregroundColor, fillColor);
|
||||||
|
|
||||||
|
// Use the same array that children use. Also use that area, so we working only inside the border
|
||||||
|
Array2DHelper.RenderEmpty(
|
||||||
|
renderContext.ConsoleDriver,
|
||||||
|
borderChildUpdatedCells,
|
||||||
|
borderChildUpdatedCells,
|
||||||
|
ApplicationContext!.EmptyCharacter,
|
||||||
|
childPositionWithoutPadding,
|
||||||
|
childSizeWithoutPadding
|
||||||
|
);
|
||||||
|
|
||||||
|
//Write back the changes to the original array
|
||||||
|
Array2DHelper.CombineArray2Ds(
|
||||||
|
renderContext.UpdatedCells,
|
||||||
|
borderChildUpdatedCells,
|
||||||
|
new Position(0, 0),
|
||||||
|
renderContext.UpdatedCells,
|
||||||
|
(a, b) => (a ?? false) || (b ?? false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return contentRendered;
|
return contentRendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderTopBorder(in RenderContext renderContext, Position position, Size size)
|
private void RenderTopBorder(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
position = position with {X = position.X + _borderThickness.Left};
|
position = position with {X = position.X + _borderThickness.Left};
|
||||||
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Top);
|
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Top);
|
||||||
RenderText(_topChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_topChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderBottomBorder(in RenderContext renderContext, Position position, Size size)
|
private void RenderBottomBorder(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
position = new Position(X: position.X + _borderThickness.Left, Y: position.Y + size.Height - _borderThickness.Bottom);
|
position = new Position(X: position.X + _borderThickness.Left, Y: position.Y + size.Height - _borderThickness.Bottom);
|
||||||
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Bottom);
|
size = new Size(Width: size.Width - _borderThickness.Left - _borderThickness.Right, Height: _borderThickness.Bottom);
|
||||||
RenderText(_bottomChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_bottomChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderLeftBorder(in RenderContext renderContext, Position position, Size size)
|
private void RenderLeftBorder(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
position = position with {Y = position.Y + _borderThickness.Top};
|
position = position with {Y = position.Y + _borderThickness.Top};
|
||||||
size = new Size(Width: _borderThickness.Left, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
size = new Size(Width: _borderThickness.Left, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
||||||
RenderText(_leftChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_leftChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderRightBorder(in RenderContext renderContext, Position position, Size size)
|
private void RenderRightBorder(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
position = new Position(X: position.X + size.Width - _borderThickness.Right, Y: position.Y + _borderThickness.Top);
|
position = new Position(X: position.X + size.Width - _borderThickness.Right, Y: position.Y + _borderThickness.Top);
|
||||||
size = new Size(Width: _borderThickness.Right, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
size = new Size(Width: _borderThickness.Right, Height: size.Height - _borderThickness.Top - _borderThickness.Bottom);
|
||||||
RenderText(_rightChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_rightChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderTopLeftCorner(in RenderContext renderContext, Position position)
|
private void RenderTopLeftCorner(in RenderContext renderContext, Position position, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
if (_borderThickness.Left == 0 || _borderThickness.Top == 0) return;
|
if (_borderThickness.Left == 0 || _borderThickness.Top == 0) return;
|
||||||
|
|
||||||
var size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Top);
|
var size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Top);
|
||||||
RenderText(_topLeftChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_topLeftChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderTopRightCorner(in RenderContext renderContext, Position position, Size size)
|
private void RenderTopRightCorner(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
if (_borderThickness.Right == 0 || _borderThickness.Top == 0) return;
|
if (_borderThickness.Right == 0 || _borderThickness.Top == 0) return;
|
||||||
|
|
||||||
position = position with {X = position.X + size.Width - _borderThickness.Right};
|
position = position with {X = position.X + size.Width - _borderThickness.Right};
|
||||||
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Top);
|
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Top);
|
||||||
RenderText(_topRightChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_topRightChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderBottomLeftCorner(in RenderContext renderContext, Position position, Size size)
|
private void RenderBottomLeftCorner(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
if (_borderThickness.Left == 0 || _borderThickness.Bottom == 0) return;
|
if (_borderThickness.Left == 0 || _borderThickness.Bottom == 0) return;
|
||||||
|
|
||||||
position = position with {Y = position.Y + size.Height - _borderThickness.Bottom};
|
position = position with {Y = position.Y + size.Height - _borderThickness.Bottom};
|
||||||
size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Bottom);
|
size = new Size(Width: _borderThickness.Left, Height: _borderThickness.Bottom);
|
||||||
RenderText(_bottomLeftChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_bottomLeftChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderBottomRightCorner(in RenderContext renderContext, Position position, Size size)
|
private void RenderBottomRightCorner(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
if (_borderThickness.Right == 0 || _borderThickness.Bottom == 0) return;
|
if (_borderThickness.Right == 0 || _borderThickness.Bottom == 0) return;
|
||||||
|
|
||||||
@@ -153,6 +197,6 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
|
|||||||
Y: position.Y + size.Height - _borderThickness.Bottom
|
Y: position.Y + size.Height - _borderThickness.Bottom
|
||||||
);
|
);
|
||||||
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Bottom);
|
size = new Size(Width: _borderThickness.Right, Height: _borderThickness.Bottom);
|
||||||
RenderText(_bottomRightChar, renderContext.ConsoleDriver, position, size);
|
RenderText(_bottomRightChar, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public abstract class ChildContainerView<TConcrete, T>
|
public abstract class ChildCollectionView<TConcrete, T>
|
||||||
: View<TConcrete, T>, IChildContainer<T>
|
: View<TConcrete, T>, IChildContainer<T>
|
||||||
where TConcrete : View<TConcrete, T>
|
where TConcrete : View<TConcrete, T>
|
||||||
{
|
{
|
||||||
@@ -12,7 +12,7 @@ public abstract class ChildContainerView<TConcrete, T>
|
|||||||
public ReadOnlyObservableCollection<IView> Children { get; }
|
public ReadOnlyObservableCollection<IView> Children { get; }
|
||||||
public ChildInitializer<T> ChildInitializer { get; }
|
public ChildInitializer<T> ChildInitializer { get; }
|
||||||
|
|
||||||
protected ChildContainerView()
|
protected ChildCollectionView()
|
||||||
{
|
{
|
||||||
ChildInitializer = new ChildInitializer<T>(this);
|
ChildInitializer = new ChildInitializer<T>(this);
|
||||||
Children = new ReadOnlyObservableCollection<IView>(_children);
|
Children = new ReadOnlyObservableCollection<IView>(_children);
|
||||||
@@ -48,7 +48,7 @@ public abstract partial class ContentView<TConcrete, T>
|
|||||||
{
|
{
|
||||||
if (_placeholderRenderDone) return false;
|
if (_placeholderRenderDone) return false;
|
||||||
_placeholderRenderDone = true;
|
_placeholderRenderDone = true;
|
||||||
RenderEmpty(renderContext, position, size);
|
RenderEmpty(renderContext, position, size, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using TerminalUI.ViewExtensions;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeHandler
|
public sealed class Grid<T> : ChildCollectionView<Grid<T>, T>, IVisibilityChangeHandler
|
||||||
{
|
{
|
||||||
private readonly List<IView> _forceRerenderChildren = new();
|
private readonly List<IView> _forceRerenderChildren = new();
|
||||||
private readonly object _forceRerenderChildrenLock = new();
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
@@ -202,7 +202,8 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool RenderViewsByPosition(RenderContext context,
|
private bool RenderViewsByPosition(
|
||||||
|
in RenderContext context,
|
||||||
Position gridPosition,
|
Position gridPosition,
|
||||||
Size gridSize,
|
Size gridSize,
|
||||||
ReadOnlySpan<int> columnWidths,
|
ReadOnlySpan<int> columnWidths,
|
||||||
@@ -238,39 +239,26 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
|||||||
|
|
||||||
if (!viewsByPosition.TryGetValue((column, row), out var children))
|
if (!viewsByPosition.TryGetValue((column, row), out var children))
|
||||||
{
|
{
|
||||||
RenderEmpty(context, renderPosition, renderSize);
|
RenderEmpty(context, renderPosition, renderSize, false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var needsRerender = children.Any(forceRerenderChildren.Contains);
|
var needsRerender = children.Any(forceRerenderChildren.Contains);
|
||||||
|
var updatedContext = context;
|
||||||
if (needsRerender)
|
if (needsRerender)
|
||||||
{
|
{
|
||||||
context = new RenderContext(
|
updatedContext = context with {ForceRerender = true};
|
||||||
context.ConsoleDriver,
|
RenderEmpty(updatedContext, renderPosition, renderSize, false);
|
||||||
true,
|
|
||||||
context.Foreground,
|
|
||||||
context.Background,
|
|
||||||
context.Statistics,
|
|
||||||
context.TextFormat
|
|
||||||
);
|
|
||||||
RenderEmpty(context, renderPosition, renderSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//This implies that children further back in the list will be rendered on top of children placed before in the list.
|
//This implies that children further back in the list will be rendered on top of children placed before in the list.
|
||||||
foreach (var child in children.Where(child => child.IsVisible))
|
foreach (var child in children)
|
||||||
{
|
{
|
||||||
var rendered = child.Render(context, renderPosition, renderSize);
|
var rendered = child.Render(updatedContext, renderPosition, renderSize);
|
||||||
if (rendered && !needsRerender)
|
if (rendered && !needsRerender)
|
||||||
{
|
{
|
||||||
needsRerender = true;
|
needsRerender = true;
|
||||||
context = new RenderContext(
|
updatedContext = context with {ForceRerender = true};
|
||||||
context.ConsoleDriver,
|
|
||||||
true,
|
|
||||||
context.Foreground,
|
|
||||||
context.Background,
|
|
||||||
context.Statistics,
|
|
||||||
context.TextFormat
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ public sealed partial class ItemsControl<TDataContext, TItem>
|
|||||||
? childSize.Height
|
? childSize.Height
|
||||||
: childSize.Width;
|
: childSize.Width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: clean non used space
|
||||||
|
|
||||||
return neededRerender;
|
return neededRerender;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -232,6 +232,8 @@ public sealed partial class ListView<TDataContext, TItem> : View<ListView<TDataC
|
|||||||
item.Render(renderContext, position with {X = position.X + deltaX}, size with {Width = width});
|
item.Render(renderContext, position with {X = position.X + deltaX}, size with {Width = width});
|
||||||
deltaX = nextDeltaX;
|
deltaX = nextDeltaX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: render empty to remaining space
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -277,23 +279,20 @@ public sealed partial class ListView<TDataContext, TItem> : View<ListView<TDataC
|
|||||||
if (lastItemIndex > listViewItems.Length)
|
if (lastItemIndex > listViewItems.Length)
|
||||||
lastItemIndex = listViewItems.Length;
|
lastItemIndex = listViewItems.Length;
|
||||||
|
|
||||||
|
var anyRendered = false;
|
||||||
for (var i = renderStartIndex; i < lastItemIndex; i++)
|
for (var i = renderStartIndex; i < lastItemIndex; i++)
|
||||||
{
|
{
|
||||||
var item = listViewItems[i];
|
var item = listViewItems[i];
|
||||||
item.Render(renderContext, position with {Y = position.Y + deltaY}, requestedItemSize with {Width = size.Width});
|
anyRendered =
|
||||||
|
item.Render(renderContext, position with {Y = position.Y + deltaY}, requestedItemSize with {Width = size.Width})
|
||||||
|
|| anyRendered;
|
||||||
deltaY += requestedItemSize.Height;
|
deltaY += requestedItemSize.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
var driver = ApplicationContext!.ConsoleDriver;
|
// TODO: this should only render if deltaY is changed compared to last render or if last render was a horizontal
|
||||||
var placeholder = new string(' ', size.Width);
|
RenderEmpty(renderContext, position with {Y = position.Y + deltaY}, size with {Height = size.Height - deltaY}, false);
|
||||||
driver.ResetStyle();
|
|
||||||
for (var i = deltaY; i < size.Height; i++)
|
|
||||||
{
|
|
||||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
|
||||||
driver.Write(placeholder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return anyRendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadOnlySpan<ListViewItem<TItem, TDataContext>> InstantiateItemViews()
|
private ReadOnlySpan<ListViewItem<TItem, TDataContext>> InstantiateItemViews()
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
|||||||
unfilledForeground,
|
unfilledForeground,
|
||||||
unfilledBackground);
|
unfilledBackground);
|
||||||
|
|
||||||
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
||||||
|
|
||||||
var utf8Support = ApplicationContext!.SupportUtf8Output;
|
var utf8Support = ApplicationContext!.SupportUtf8Output;
|
||||||
var unfilledCharacter = unfilledCharacterS.GetChar(utf8Support);
|
var unfilledCharacter = unfilledCharacterS.GetChar(utf8Support);
|
||||||
@@ -117,16 +117,17 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
|||||||
var textStartPosition = position;
|
var textStartPosition = position;
|
||||||
if (leftCap.HasValue)
|
if (leftCap.HasValue)
|
||||||
{
|
{
|
||||||
RenderText(leftCap.Value, driver, position, size with {Width = 1});
|
RenderText(leftCap.Value, renderContext, position, size with {Width = 1}, skipRender);
|
||||||
textStartPosition = textStartPosition with {X = textStartPosition.X + 1};
|
textStartPosition = textStartPosition with {X = textStartPosition.X + 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filled
|
// Filled
|
||||||
RenderText(
|
RenderText(
|
||||||
filledText,
|
filledText,
|
||||||
driver,
|
renderContext,
|
||||||
textStartPosition,
|
textStartPosition,
|
||||||
size with {Width = progressQuotientWidth}
|
size with {Width = progressQuotientWidth},
|
||||||
|
skipRender
|
||||||
);
|
);
|
||||||
|
|
||||||
// Transient character
|
// Transient character
|
||||||
@@ -135,9 +136,10 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
|||||||
SetStyleColor(renderContext, foreground, unfilledBackground);
|
SetStyleColor(renderContext, foreground, unfilledBackground);
|
||||||
RenderText(
|
RenderText(
|
||||||
transientChar,
|
transientChar,
|
||||||
driver,
|
renderContext,
|
||||||
textStartPosition with {X = textStartPosition.X + progressQuotientWidth},
|
textStartPosition with {X = textStartPosition.X + progressQuotientWidth},
|
||||||
size with {Width = 1}
|
size with {Width = 1},
|
||||||
|
skipRender
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,9 +152,10 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
|||||||
SetStyleColor(renderContext, unfilledForeground, unfilledBackground);
|
SetStyleColor(renderContext, unfilledForeground, unfilledBackground);
|
||||||
RenderText(
|
RenderText(
|
||||||
unfilledText,
|
unfilledText,
|
||||||
driver,
|
renderContext,
|
||||||
textStartPosition with {X = textStartPosition.X + progressQuotientWidth + 1},
|
textStartPosition with {X = textStartPosition.X + progressQuotientWidth + 1},
|
||||||
size with {Width = progressRemainderWidth}
|
size with {Width = progressRemainderWidth},
|
||||||
|
skipRender
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +165,13 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
|||||||
SetStyleColor(renderContext, foreground, background);
|
SetStyleColor(renderContext, foreground, background);
|
||||||
RenderText(
|
RenderText(
|
||||||
rightCap.Value,
|
rightCap.Value,
|
||||||
driver,
|
renderContext,
|
||||||
position with {X = position.X + size.Width - 1},
|
position with {X = position.X + size.Width - 1},
|
||||||
size with {Width = 1});
|
size with {Width = 1},
|
||||||
|
skipRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return !skipRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NeedsRerender(RenderState renderState)
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
|||||||
@@ -13,32 +13,40 @@ public sealed partial class Rectangle<T> : View<Rectangle<T>, T>, IDisplayView
|
|||||||
IColor? Color);
|
IColor? Color);
|
||||||
|
|
||||||
private RenderState? _lastRenderState;
|
private RenderState? _lastRenderState;
|
||||||
|
|
||||||
|
[Notify] private IColor? _fill;
|
||||||
|
|
||||||
|
public Rectangle()
|
||||||
|
{
|
||||||
|
RerenderProperties.Add(nameof(Fill));
|
||||||
|
}
|
||||||
|
|
||||||
protected override Size CalculateSize() => new(Width ?? 0, Height ?? 0);
|
protected override Size CalculateSize() => new(Width ?? 0, Height ?? 0);
|
||||||
|
|
||||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||||
{
|
{
|
||||||
var color = Background ?? renderContext.Background;
|
var fillColor = Fill ?? Background ?? renderContext.Background;
|
||||||
var renderState = new RenderState(position, size, color);
|
var renderState = new RenderState(position, size, fillColor);
|
||||||
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
||||||
_lastRenderState = renderState;
|
_lastRenderState = renderState;
|
||||||
|
|
||||||
var driver = renderContext.ConsoleDriver;
|
var driver = renderContext.ConsoleDriver;
|
||||||
|
|
||||||
var s = new string(' ', size.Width);
|
|
||||||
driver.ResetStyle();
|
driver.ResetStyle();
|
||||||
if (color is not null)
|
if (fillColor is not null)
|
||||||
{
|
{
|
||||||
driver.SetBackgroundColor(color);
|
driver.SetBackgroundColor(fillColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
var height = size.Height;
|
RenderEmpty(
|
||||||
for (var i = 0; i < height; i++)
|
renderContext,
|
||||||
{
|
position,
|
||||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
size,
|
||||||
driver.Write(s);
|
skipRender,
|
||||||
}
|
false
|
||||||
|
);
|
||||||
|
|
||||||
return true;
|
return !skipRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NeedsRerender(RenderState renderState)
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using TerminalUI.Traits;
|
|||||||
|
|
||||||
namespace TerminalUI.Controls;
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>, IVisibilityChangeHandler
|
public sealed partial class StackPanel<T> : ChildCollectionView<StackPanel<T>, T>, IVisibilityChangeHandler
|
||||||
{
|
{
|
||||||
private readonly List<IView> _forceRerenderChildren = new();
|
private readonly List<IView> _forceRerenderChildren = new();
|
||||||
private readonly object _forceRerenderChildrenLock = new();
|
private readonly object _forceRerenderChildrenLock = new();
|
||||||
@@ -39,7 +39,10 @@ public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>
|
|||||||
return new Size(width, height);
|
return new Size(width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
protected override bool DefaultRenderer(
|
||||||
|
in RenderContext renderContext,
|
||||||
|
Position position,
|
||||||
|
Size size)
|
||||||
{
|
{
|
||||||
var neededRerender = false;
|
var neededRerender = false;
|
||||||
IReadOnlyList<IView> forceRerenderChildren;
|
IReadOnlyList<IView> forceRerenderChildren;
|
||||||
@@ -102,9 +105,10 @@ public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>
|
|||||||
SetColorsForDriver(renderContext);
|
SetColorsForDriver(renderContext);
|
||||||
RenderText(
|
RenderText(
|
||||||
text,
|
text,
|
||||||
renderContext.ConsoleDriver,
|
renderContext,
|
||||||
position with {X = position.X + delta},
|
position with {X = position.X + delta},
|
||||||
size with {Width = leftWidth}
|
size with {Width = leftWidth},
|
||||||
|
!neededRerender
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -116,9 +120,10 @@ public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>
|
|||||||
SetColorsForDriver(renderContext);
|
SetColorsForDriver(renderContext);
|
||||||
RenderText(
|
RenderText(
|
||||||
text,
|
text,
|
||||||
renderContext.ConsoleDriver,
|
renderContext,
|
||||||
position with {Y = position.Y + delta},
|
position with {Y = position.Y + delta},
|
||||||
size with {Height = leftHeight}
|
size with {Height = leftHeight},
|
||||||
|
!neededRerender
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
background,
|
background,
|
||||||
_textFormat);
|
_textFormat);
|
||||||
|
|
||||||
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
||||||
|
|
||||||
_lastRenderState = renderState;
|
_lastRenderState = renderState;
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
if (_placeholderRenderDone)
|
if (_placeholderRenderDone)
|
||||||
{
|
{
|
||||||
_placeholderRenderDone = true;
|
_placeholderRenderDone = true;
|
||||||
RenderEmpty(renderContext, position, size);
|
RenderEmpty(renderContext, position, size, skipRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -75,12 +75,11 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
|
|
||||||
_placeholderRenderDone = false;
|
_placeholderRenderDone = false;
|
||||||
|
|
||||||
var driver = renderContext.ConsoleDriver;
|
|
||||||
SetStyleColor(renderContext, foreground, background, _textFormat);
|
SetStyleColor(renderContext, foreground, background, _textFormat);
|
||||||
|
|
||||||
RenderText(_textLines, driver, position, size, TransformText);
|
RenderText(_textLines, renderContext, position, size, skipRender, TransformText);
|
||||||
|
|
||||||
return true;
|
return !skipRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string TransformText(string text, Position position, Size size)
|
private string TransformText(string text, Position position, Size size)
|
||||||
|
|||||||
@@ -100,38 +100,39 @@ public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDispl
|
|||||||
foreground,
|
foreground,
|
||||||
background);
|
background);
|
||||||
|
|
||||||
if (!renderContext.ForceRerender && !NeedsRerender(renderStatus)) return false;
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderStatus);
|
||||||
_lastRenderState = renderStatus;
|
_lastRenderState = renderStatus;
|
||||||
|
|
||||||
var driver = renderContext.ConsoleDriver;
|
var driver = renderContext.ConsoleDriver;
|
||||||
SetStyleColor(renderContext, foreground, background);
|
SetStyleColor(renderContext, foreground, background);
|
||||||
|
|
||||||
RenderEmpty(renderContext, position, size);
|
RenderEmpty(renderContext, position, size, skipRender);
|
||||||
|
|
||||||
if (PasswordChar is { } passwordChar && !char.IsControl(passwordChar))
|
if (PasswordChar is { } passwordChar && !char.IsControl(passwordChar))
|
||||||
{
|
{
|
||||||
for (var i = 0; i < _textLines.Count; i++)
|
for (var i = 0; i < _textLines.Count; i++)
|
||||||
{
|
{
|
||||||
var pos = position with {Y = position.Y + i};
|
var pos = position with {Y = position.Y + i};
|
||||||
RenderPasswordTextLine(_textLines[i], passwordChar, driver, pos, size);
|
RenderPasswordTextLine(_textLines[i], passwordChar, renderContext, pos, size, skipRender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RenderText(_textLines, driver, position, size);
|
RenderText(_textLines, renderContext, position, size, skipRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cursorPosition = position + _relativeCursorPosition;
|
_cursorPosition = position + _relativeCursorPosition;
|
||||||
|
|
||||||
return true;
|
return !skipRender;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenderPasswordTextLine(
|
private void RenderPasswordTextLine(
|
||||||
string sourceText,
|
string sourceText,
|
||||||
char passwordChar,
|
char passwordChar,
|
||||||
IConsoleDriver driver,
|
in RenderContext renderContext,
|
||||||
Position position,
|
Position position,
|
||||||
Size size)
|
Size size,
|
||||||
|
bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
Span<char> text = stackalloc char[sourceText.Length];
|
Span<char> text = stackalloc char[sourceText.Length];
|
||||||
for (var j = 0; j < text.Length; j++)
|
for (var j = 0; j < text.Length; j++)
|
||||||
@@ -139,7 +140,7 @@ public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDispl
|
|||||||
text[j] = passwordChar;
|
text[j] = passwordChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderText(text, driver, position, size);
|
RenderText(text, renderContext, position, size, updateCellsOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool NeedsRerender(RenderState renderState)
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.Runtime.CompilerServices;
|
|||||||
using GeneralInputKey;
|
using GeneralInputKey;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.ConsoleDrivers;
|
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
using TerminalUI.TextFormat;
|
using TerminalUI.TextFormat;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
@@ -200,12 +199,18 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
return renderResult;
|
return renderResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void RenderEmpty(in RenderContext renderContext, Position position, Size size)
|
protected void RenderEmpty(in RenderContext renderContext, Position position, Size size, bool updateCellsOnly, bool resetStyle = true)
|
||||||
{
|
{
|
||||||
|
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
|
||||||
|
if (updateCellsOnly) return;
|
||||||
var driver = renderContext.ConsoleDriver;
|
var driver = renderContext.ConsoleDriver;
|
||||||
driver.ResetStyle();
|
if (resetStyle)
|
||||||
|
{
|
||||||
|
driver.ResetStyle();
|
||||||
|
}
|
||||||
|
|
||||||
var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width);
|
Span<char> placeHolder = stackalloc char[size.Width];
|
||||||
|
placeHolder.Fill(ApplicationContext!.EmptyCharacter);
|
||||||
for (var i = 0; i < size.Height; i++)
|
for (var i = 0; i < size.Height; i++)
|
||||||
{
|
{
|
||||||
driver.SetCursorPosition(position with {Y = position.Y + i});
|
driver.SetCursorPosition(position with {Y = position.Y + i});
|
||||||
@@ -213,13 +218,30 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < sizeWidth; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < sizeHeight; y++)
|
||||||
|
{
|
||||||
|
renderContextUpdatedCells[position.X + x, position.Y + y] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void RenderText(
|
protected void RenderText(
|
||||||
IList<string> textLines,
|
IList<string> textLines,
|
||||||
IConsoleDriver driver,
|
in RenderContext renderContext,
|
||||||
Position position,
|
Position position,
|
||||||
Size size,
|
Size size,
|
||||||
|
bool updateCellsOnly,
|
||||||
TextTransformer? textTransformer = null)
|
TextTransformer? textTransformer = null)
|
||||||
{
|
{
|
||||||
|
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
|
||||||
|
|
||||||
|
if (updateCellsOnly) return;
|
||||||
|
|
||||||
|
var driver = renderContext.ConsoleDriver;
|
||||||
for (var i = 0; i < textLines.Count; i++)
|
for (var i = 0; i < textLines.Count; i++)
|
||||||
{
|
{
|
||||||
var currentPosition = position with {Y = position.Y + i};
|
var currentPosition = position with {Y = position.Y + i};
|
||||||
@@ -234,6 +256,10 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
{
|
{
|
||||||
text = text[..size.Width];
|
text = text[..size.Width];
|
||||||
}
|
}
|
||||||
|
else if (text.Length < size.Width)
|
||||||
|
{
|
||||||
|
text = text.PadRight(size.Width);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -247,38 +273,17 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void RenderText(
|
protected void RenderText(
|
||||||
string text,
|
in ReadOnlySpan<char> text,
|
||||||
IConsoleDriver driver,
|
in RenderContext renderContext,
|
||||||
Position position,
|
Position position,
|
||||||
Size size,
|
Size size,
|
||||||
TextTransformer? textTransformer = null)
|
bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < size.Height; i++)
|
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
|
||||||
{
|
|
||||||
var currentPosition = position with {Y = position.Y + i};
|
|
||||||
var finalText = text;
|
|
||||||
|
|
||||||
if (textTransformer is not null)
|
if (updateCellsOnly) return;
|
||||||
{
|
|
||||||
finalText = textTransformer(finalText, currentPosition, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalText.Length > size.Width)
|
var driver = renderContext.ConsoleDriver;
|
||||||
{
|
|
||||||
finalText = finalText[..size.Width];
|
|
||||||
}
|
|
||||||
|
|
||||||
driver.SetCursorPosition(currentPosition);
|
|
||||||
driver.Write(finalText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void RenderText(
|
|
||||||
in ReadOnlySpan<char> text,
|
|
||||||
IConsoleDriver driver,
|
|
||||||
Position position,
|
|
||||||
Size size)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < size.Height; i++)
|
for (var i = 0; i < size.Height; i++)
|
||||||
{
|
{
|
||||||
var currentPosition = position with {Y = position.Y + i};
|
var currentPosition = position with {Y = position.Y + i};
|
||||||
@@ -296,10 +301,16 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
|
|
||||||
protected void RenderText(
|
protected void RenderText(
|
||||||
char content,
|
char content,
|
||||||
IConsoleDriver driver,
|
in RenderContext renderContext,
|
||||||
Position position,
|
Position position,
|
||||||
Size size)
|
Size size,
|
||||||
|
bool updateCellsOnly)
|
||||||
{
|
{
|
||||||
|
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
|
||||||
|
|
||||||
|
if (updateCellsOnly) return;
|
||||||
|
|
||||||
|
var driver = renderContext.ConsoleDriver;
|
||||||
var contentString = new string(content, size.Width);
|
var contentString = new string(content, size.Width);
|
||||||
|
|
||||||
for (var i = 0; i < size.Height; i++)
|
for (var i = 0; i < size.Height; i++)
|
||||||
@@ -323,6 +334,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
{
|
{
|
||||||
t.ApplyFormat(driver, renderContext.TextFormat);
|
t.ApplyFormat(driver, renderContext.TextFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foreground is not null)
|
if (foreground is not null)
|
||||||
{
|
{
|
||||||
driver.SetForegroundColor(foreground);
|
driver.SetForegroundColor(foreground);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public readonly ref struct RenderContext
|
|||||||
public IColor? Background { get; init; }
|
public IColor? Background { get; init; }
|
||||||
public RenderStatistics Statistics { get; init; }
|
public RenderStatistics Statistics { get; init; }
|
||||||
public TextFormatContext TextFormat { get; init; }
|
public TextFormatContext TextFormat { get; init; }
|
||||||
|
public bool[,] UpdatedCells { get; init; }
|
||||||
|
|
||||||
public RenderContext(
|
public RenderContext(
|
||||||
IConsoleDriver consoleDriver,
|
IConsoleDriver consoleDriver,
|
||||||
@@ -23,7 +24,8 @@ public readonly ref struct RenderContext
|
|||||||
IColor? foreground,
|
IColor? foreground,
|
||||||
IColor? background,
|
IColor? background,
|
||||||
RenderStatistics statistics,
|
RenderStatistics statistics,
|
||||||
TextFormatContext textFormat)
|
TextFormatContext textFormat,
|
||||||
|
bool[,] updatedCells)
|
||||||
{
|
{
|
||||||
RenderId = _renderId++;
|
RenderId = _renderId++;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ public readonly ref struct RenderContext
|
|||||||
Background = background;
|
Background = background;
|
||||||
Statistics = statistics;
|
Statistics = statistics;
|
||||||
TextFormat = textFormat;
|
TextFormat = textFormat;
|
||||||
|
UpdatedCells = updatedCells;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RenderContext Empty =>
|
public static RenderContext Empty =>
|
||||||
@@ -42,6 +45,7 @@ public readonly ref struct RenderContext
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
new RenderStatistics(),
|
new RenderStatistics(),
|
||||||
new TextFormatContext(false)
|
new TextFormatContext(false),
|
||||||
|
new bool[0, 0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using TerminalUI.Controls;
|
using TerminalUI.ConsoleDrivers;
|
||||||
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Models;
|
using TerminalUI.Models;
|
||||||
using TerminalUI.TextFormat;
|
using TerminalUI.TextFormat;
|
||||||
using TerminalUI.Traits;
|
using TerminalUI.Traits;
|
||||||
@@ -14,6 +15,9 @@ public class RenderEngine : IRenderEngine
|
|||||||
private readonly List<IView> _forcedTemporaryViewsToRender = new();
|
private readonly List<IView> _forcedTemporaryViewsToRender = new();
|
||||||
private bool _rerenderRequested = true;
|
private bool _rerenderRequested = true;
|
||||||
private bool _lastCursorVisible;
|
private bool _lastCursorVisible;
|
||||||
|
private bool[,]? _updatedCells;
|
||||||
|
private bool[,]? _filledCells;
|
||||||
|
private bool[,]? _lastFilledCells;
|
||||||
|
|
||||||
public RenderEngine(IApplicationContext applicationContext, IEventLoop eventLoop)
|
public RenderEngine(IApplicationContext applicationContext, IEventLoop eventLoop)
|
||||||
{
|
{
|
||||||
@@ -77,6 +81,18 @@ public class RenderEngine : IRenderEngine
|
|||||||
var initialPosition = new Position(0, 0);
|
var initialPosition = new Position(0, 0);
|
||||||
var size = driver.GetWindowSize();
|
var size = driver.GetWindowSize();
|
||||||
|
|
||||||
|
//TODO: this could be stack allocated when sizes are small
|
||||||
|
if (_updatedCells is null
|
||||||
|
|| _updatedCells.GetLength(0) != size.Width
|
||||||
|
|| _updatedCells.GetLength(1) != size.Height)
|
||||||
|
{
|
||||||
|
_updatedCells = new bool[size.Width, size.Height];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClearArray2D(_updatedCells);
|
||||||
|
}
|
||||||
|
|
||||||
RenderViews(
|
RenderViews(
|
||||||
forcedTemporaryViewsToRender,
|
forcedTemporaryViewsToRender,
|
||||||
new RenderContext(
|
new RenderContext(
|
||||||
@@ -85,7 +101,8 @@ public class RenderEngine : IRenderEngine
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
new RenderStatistics(),
|
new RenderStatistics(),
|
||||||
new TextFormatContext(driver.SupportsAnsiEscapeSequence)
|
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
|
||||||
|
_updatedCells
|
||||||
),
|
),
|
||||||
initialPosition,
|
initialPosition,
|
||||||
size);
|
size);
|
||||||
@@ -98,11 +115,41 @@ public class RenderEngine : IRenderEngine
|
|||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
new RenderStatistics(),
|
new RenderStatistics(),
|
||||||
new TextFormatContext(driver.SupportsAnsiEscapeSequence)
|
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
|
||||||
|
_updatedCells
|
||||||
),
|
),
|
||||||
initialPosition,
|
initialPosition,
|
||||||
size);
|
size);
|
||||||
|
|
||||||
|
if (_lastFilledCells is not null
|
||||||
|
&& _lastFilledCells.GetLength(0) == size.Width
|
||||||
|
&& _lastFilledCells.GetLength(1) == size.Height)
|
||||||
|
{
|
||||||
|
Array2DHelper.CombineArray2Ds(
|
||||||
|
_updatedCells,
|
||||||
|
_lastFilledCells,
|
||||||
|
new Position(0, 0),
|
||||||
|
_updatedCells,
|
||||||
|
(a, b) => (a ?? false) || (b ?? false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_filledCells is null
|
||||||
|
|| _filledCells.GetLength(0) != size.Width
|
||||||
|
|| _filledCells.GetLength(1) != size.Height)
|
||||||
|
{
|
||||||
|
_filledCells = new bool[size.Width, size.Height];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClearArray2D(_filledCells);
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.ResetStyle();
|
||||||
|
Array2DHelper.RenderEmpty(driver, _updatedCells, _filledCells, _applicationContext.EmptyCharacter, initialPosition, size);
|
||||||
|
|
||||||
|
(_lastFilledCells, _filledCells) = (_filledCells, _lastFilledCells);
|
||||||
|
|
||||||
if (_applicationContext.FocusManager.Focused is { } focused)
|
if (_applicationContext.FocusManager.Focused is { } focused)
|
||||||
{
|
{
|
||||||
focused.SetCursorPosition(driver);
|
focused.SetCursorPosition(driver);
|
||||||
@@ -129,6 +176,20 @@ public class RenderEngine : IRenderEngine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ClearArray2D<T>(T[,] array, T defaultValue = default!)
|
||||||
|
{
|
||||||
|
var maxX = array.GetLength(0);
|
||||||
|
var maxY = array.GetLength(1);
|
||||||
|
|
||||||
|
for (var x = 0; x < maxX; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < maxY; y++)
|
||||||
|
{
|
||||||
|
array[x, y] = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void AddViewToPermanentRenderGroup(IView view)
|
public void AddViewToPermanentRenderGroup(IView view)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
|||||||
Reference in New Issue
Block a user