Files
FileTime2/src/Library/TerminalUI/RenderEngine.cs
2023-08-21 12:27:07 +02:00

240 lines
6.7 KiB
C#

using TerminalUI.Controls;
using TerminalUI.Models;
using TerminalUI.Styling;
using TerminalUI.TextFormat;
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;
private bool _forceRerenderAll;
private bool[,]? _updatedCells;
private bool[,]? _filledCells;
private bool[,]? _lastFilledCells;
private DateTime _renderRequestDetected;
private ITheme? _lastTheme;
public RenderEngine(IApplicationContext applicationContext, IEventLoop eventLoop)
{
_applicationContext = applicationContext;
_eventLoop = eventLoop;
_eventLoop.AddToPermanentQueue(Render);
_eventLoop.AddInitializer(() =>
{
_applicationContext.ConsoleDriver.ThreadId = _eventLoop.ThreadId;
_applicationContext.ConsoleDriver.EnterRestrictedMode();
});
}
public void RequestRerender(IView view) => RequestRerender();
public void VisibilityChanged(IView view, bool newVisibility)
{
if (!newVisibility)
{
_forceRerenderAll = true;
return;
}
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);
return;
}
visibilityChangeHandler.ChildVisibilityChanged(view);
}
public void Run() => _eventLoop.Run();
public void RequestRerender()
{
lock (_lock)
{
_rerenderRequested = true;
}
}
private void Render()
{
List<IView> permanentViewsToRender;
List<IView> forcedTemporaryViewsToRender;
bool forceRerenderAll;
lock (_lock)
{
if (!_rerenderRequested) return;
_rerenderRequested = false;
permanentViewsToRender = _permanentViewsToRender.ToList();
forcedTemporaryViewsToRender = _forcedTemporaryViewsToRender.ToList();
_forcedTemporaryViewsToRender.Clear();
forceRerenderAll = _forceRerenderAll;
_forceRerenderAll = false;
}
if (_applicationContext.Theme != _lastTheme)
{
_lastTheme = _applicationContext.Theme;
forceRerenderAll = true;
}
var driver = _applicationContext.ConsoleDriver;
var initialPosition = new Position(0, 0);
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);
}
if (!forceRerenderAll)
{
RenderViews(
forcedTemporaryViewsToRender,
new RenderContext(
driver,
true,
null,
null,
new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
_updatedCells
),
initialPosition,
size);
}
RenderViews(
permanentViewsToRender,
new RenderContext(
driver,
forceRerenderAll,
null,
null,
new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
_updatedCells
),
initialPosition,
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)
{
focused.SetCursorPosition(driver);
if (!_lastCursorVisible)
{
driver.SetCursorVisible(true);
_lastCursorVisible = true;
}
}
else if (_lastCursorVisible)
{
driver.SetCursorVisible(false);
_lastCursorVisible = false;
}
}
private void RenderViews(List<IView> views, in RenderContext renderContext, Position position, Size size)
{
foreach (var view in views)
{
view.Attached = true;
view.GetRequestedSize();
view.Render(renderContext, position, size);
}
}
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)
{
lock (_lock)
{
_permanentViewsToRender.Add(view);
}
}
public void AddViewToForcedTemporaryRenderGroup(IView view)
{
lock (_lock)
{
_forcedTemporaryViewsToRender.Add(view);
}
}
}