FilePreview use ASCII only, force rerender when item disappears

This commit is contained in:
2023-08-19 12:46:26 +02:00
parent 1b60af389b
commit c9a92f4923
15 changed files with 170 additions and 113 deletions

View File

@@ -79,9 +79,11 @@ public class ItemPreviews
.Setup(t => t.Bind(
t,
dc => dc.TextContent,
t => t.Text)),
t => t.Text,
fallbackValue: string.Empty)),
new TextBlock<IElementPreviewViewModel>
{
Margin = "0 1 0 0",
Extensions = {new GridPositionExtension(0, 1)}
}.Setup(t => t.Bind(
t,

View File

@@ -2,7 +2,6 @@
using System.Linq.Expressions;
using System.Reflection;
using TerminalUI.Controls;
using TerminalUI.ExpressionTrackers;
using TerminalUI.Traits;
namespace TerminalUI;

View File

@@ -6,7 +6,11 @@ namespace TerminalUI.ConsoleDrivers;
public class DotnetDriver : IConsoleDriver
{
protected bool CheckThreadId;
public bool SupportsAnsiEscapeSequence { get; protected set; }
public int ThreadId { get; set; }
public void EnterRestrictedMode() => CheckThreadId = true;
public virtual bool Init()
{
@@ -25,10 +29,31 @@ public class DotnetDriver : IConsoleDriver
return new(x, y);
}
public void Write(string text) => Console.Out.Write(text);
public void Write(ReadOnlySpan<char> text) => Console.Out.Write(text);
public void Write(string text)
{
CheckThread();
Console.Out.Write(text);
}
public void Write(char text) => Console.Out.Write(text);
public void Write(ReadOnlySpan<char> text)
{
CheckThread();
Console.Out.Write(text);
}
public void Write(char text)
{
CheckThread();
Console.Out.Write(text);
}
private void CheckThread()
{
if (CheckThreadId && ThreadId != Thread.CurrentThread.ManagedThreadId)
{
throw new InvalidOperationException("Cannot write to console from another thread");
}
}
public virtual void Dispose() => Console.Clear();

View File

@@ -6,6 +6,7 @@ namespace TerminalUI.ConsoleDrivers;
public interface IConsoleDriver
{
bool SupportsAnsiEscapeSequence { get; }
int ThreadId { get; set; }
bool Init();
void Dispose();
void SetCursorPosition(Position position);
@@ -22,4 +23,5 @@ public interface IConsoleDriver
void SetBackgroundColor(IColor background);
Size GetWindowSize();
void Clear();
void EnterRestrictedMode();
}

View File

@@ -28,6 +28,7 @@ public sealed class XTermDriver : DotnetDriver
public override void Dispose()
{
CheckThreadId = false;
Write("\x1b[?1047l");
SetCursorPosition(_initialCursorPosition);
}

View File

@@ -1,6 +1,5 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
namespace TerminalUI.Controls;

View File

@@ -96,37 +96,6 @@ public sealed partial class StackPanel<T> : ChildCollectionView<StackPanel<T>, T
: childSize.Width;
}
if (Orientation == Orientation.Horizontal)
{
var leftWidth = size.Width - delta;
Span<char> text = stackalloc char[leftWidth];
text.Fill(ApplicationContext!.EmptyCharacter);
SetColorsForDriver(renderContext);
RenderText(
text,
renderContext,
position with {X = position.X + delta},
size with {Width = leftWidth},
!neededRerender
);
}
else
{
var leftHeight = size.Height - delta;
Span<char> text = stackalloc char[size.Width];
text.Fill(ApplicationContext!.EmptyCharacter);
SetColorsForDriver(renderContext);
RenderText(
text,
renderContext,
position with {Y = position.Y + delta},
size with {Height = leftHeight},
!neededRerender
);
}
return neededRerender;
}

View File

@@ -21,7 +21,6 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
private RenderState? _lastRenderState;
private string[]? _textLines;
private bool _placeholderRenderDone;
[Notify] private string? _text = string.Empty;
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
@@ -64,30 +63,22 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
_lastRenderState = renderState;
if (_textLines is null)
var textLines = _textLines;
var textStartIndex = _textStartIndex;
if (textLines is null)
{
if (!_placeholderRenderDone)
{
_placeholderRenderDone = true;
RenderEmpty(renderContext, position, size, skipRender);
return true;
}
return false;
}
_placeholderRenderDone = false;
SetStyleColor(renderContext, foreground, background, _textFormat);
var textLines = _textLines;
if (_textStartIndex < _textLines.Length)
if (textStartIndex < textLines.Length)
{
textLines = _textLines[_textStartIndex..];
textLines = textLines[textStartIndex..];
}
else
{
_textStartIndex = _textLines.Length - size.Height;
_textStartIndex = textLines.Length - size.Height;
}
RenderText(textLines, renderContext, position, size, skipRender, TransformText);
@@ -99,7 +90,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
=> TextAlignment switch
{
TextAlignment.Right => string.Format($"{{0,{size.Width}}}", text),
_ => string.Format($"{{0,{-size.Width}}}", text)
_ => text
};
private bool NeedsRerender(RenderState renderState)

View File

@@ -103,11 +103,8 @@ public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDispl
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderStatus);
_lastRenderState = renderStatus;
var driver = renderContext.ConsoleDriver;
SetStyleColor(renderContext, foreground, background);
RenderEmpty(renderContext, position, size, skipRender);
if (PasswordChar is { } passwordChar && !char.IsControl(passwordChar))
{
for (var i = 0; i < _textLines.Count; i++)

View File

@@ -132,7 +132,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
}
else if (e.PropertyName == nameof(IsVisible))
{
ApplicationContext?.RenderEngine.VisibilityChanged(this);
ApplicationContext?.RenderEngine.VisibilityChanged(this, IsVisible);
}
}
@@ -218,7 +218,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
}
}
private void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
private static void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
{
for (var x = 0; x < sizeWidth; x++)
{
@@ -235,14 +235,12 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
Position position,
Size size,
bool updateCellsOnly,
TextTransformer? textTransformer = null)
TextTransformer? textTransformer = null,
bool useAsciiOnly = true)
{
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
if (updateCellsOnly) return;
var driver = renderContext.ConsoleDriver;
for (var i = 0; i < textLines.Count; i++)
var end = int.Min(textLines.Count, size.Height);
for (var i = 0; i < end; i++)
{
var currentPosition = position with {Y = position.Y + i};
var text = textLines[i];
@@ -252,22 +250,40 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
text = textTransformer(text, currentPosition, size);
}
if (text.Length > size.Width)
if (useAsciiOnly)
{
text = text[..size.Width];
RenderTextAsciiOnly(
text,
renderContext,
currentPosition,
size.Width,
updateCellsOnly);
}
else if (text.Length < size.Width)
else
{
text = text.PadRight(size.Width);
}
if (text.Length > size.Width)
{
text = text[..size.Width];
}
try
{
driver.SetCursorPosition(currentPosition);
driver.Write(text);
}
catch
{
foreach (var c in text)
{
if (char.IsControl(c))
throw new Exception("Control character");
}
if (updateCellsOnly) continue;
UpdateCells(renderContext.UpdatedCells, currentPosition, text.Length, 1);
try
{
driver.SetCursorPosition(currentPosition);
driver.Write(text);
}
catch
{
}
}
}
}
@@ -279,10 +295,6 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
Size size,
bool updateCellsOnly)
{
UpdateCells(renderContext.UpdatedCells, position, size.Width, size.Height);
if (updateCellsOnly) return;
var driver = renderContext.ConsoleDriver;
for (var i = 0; i < size.Height; i++)
{
@@ -294,6 +306,9 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
finalText = finalText[..size.Width];
}
UpdateCells(renderContext.UpdatedCells, currentPosition, finalText.Length, 1);
if (updateCellsOnly) continue;
driver.SetCursorPosition(currentPosition);
driver.Write(finalText);
}
@@ -311,7 +326,8 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
if (updateCellsOnly) return;
var driver = renderContext.ConsoleDriver;
var contentString = new string(content, size.Width);
Span<char> contentString = stackalloc char[size.Width];
contentString.Fill(content);
for (var i = 0; i < size.Height; i++)
{
@@ -322,6 +338,37 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
}
}
private void RenderTextAsciiOnly(
ReadOnlySpan<char> text,
in RenderContext renderContext,
Position position,
int width,
bool updateCellsOnly)
{
Span<char> finalText = stackalloc char[width];
var finalTextPosition = 0;
for (var i = 0; i < text.Length && finalTextPosition < width; i++)
{
var c = text[i];
if (c < 32 || c > 255) continue;
finalText[finalTextPosition] = c;
finalTextPosition++;
}
for (var i = 0; i < finalTextPosition; i++)
{
renderContext.UpdatedCells[position.X + i, position.Y] = true;
}
if (updateCellsOnly) return;
var driver = renderContext.ConsoleDriver;
driver.SetCursorPosition(position);
driver.Write(finalText[..finalTextPosition]);
}
protected void SetStyleColor(
in RenderContext renderContext,
IColor? foreground = null,

View File

@@ -1,5 +1,4 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
namespace TerminalUI;
@@ -7,8 +6,11 @@ public class EventLoop : IEventLoop
{
private readonly IApplicationContext _applicationContext;
private readonly ILogger<EventLoop> _logger;
private readonly List<Action> _initializers = new();
private readonly List<Action> _permanentQueue = new();
public int ThreadId { get; set; } = -1;
public EventLoop(
IApplicationContext applicationContext,
ILogger<EventLoop> logger)
@@ -18,15 +20,22 @@ public class EventLoop : IEventLoop
}
public void AddToPermanentQueue(Action action) => _permanentQueue.Add(action);
public void AddInitializer(Action action) => _initializers.Add(action);
public void Run()
{
_applicationContext.IsRunning = true;
ThreadId = Thread.CurrentThread.ManagedThreadId;
foreach (var initializer in _initializers)
{
initializer();
}
while (_applicationContext.IsRunning)
{
ProcessQueues();
Thread.Sleep(10);
}
ThreadId = -1;
}
private void ProcessQueues()
@@ -35,7 +44,7 @@ public class EventLoop : IEventLoop
{
/*try
{*/
action();
action();
/*}
catch (Exception e)
{

View File

@@ -4,4 +4,6 @@ public interface IEventLoop
{
void Run();
void AddToPermanentQueue(Action action);
void AddInitializer(Action action);
int ThreadId { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace TerminalUI;
public interface IRenderEngine
{
void RequestRerender(IView view);
void VisibilityChanged(IView view);
void VisibilityChanged(IView view, bool newVisibility);
void AddViewToPermanentRenderGroup(IView view);
void Run();
}

View File

@@ -1,5 +1,4 @@
using TerminalUI.ConsoleDrivers;
using TerminalUI.Controls;
using TerminalUI.Controls;
using TerminalUI.Models;
using TerminalUI.TextFormat;
using TerminalUI.Traits;
@@ -15,9 +14,11 @@ public class RenderEngine : IRenderEngine
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;
public RenderEngine(IApplicationContext applicationContext, IEventLoop eventLoop)
{
@@ -25,12 +26,23 @@ public class RenderEngine : IRenderEngine
_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)
public void VisibilityChanged(IView view, bool newVisibility)
{
if (!newVisibility)
{
_forceRerenderAll = true;
return;
}
IVisibilityChangeHandler? visibilityChangeHandler = null;
var parent = view.VisualParent;
while (parent?.VisualParent != null)
@@ -46,12 +58,11 @@ public class RenderEngine : IRenderEngine
if (visibilityChangeHandler is null)
{
AddViewToForcedTemporaryRenderGroup(parent ?? view);
}
else
{
visibilityChangeHandler.ChildVisibilityChanged(view);
//AddViewToForcedTemporaryRenderGroup(parent ?? view);
return;
}
visibilityChangeHandler.ChildVisibilityChanged(view);
}
public void Run() => _eventLoop.Run();
@@ -68,6 +79,7 @@ public class RenderEngine : IRenderEngine
{
List<IView> permanentViewsToRender;
List<IView> forcedTemporaryViewsToRender;
bool forceRerenderAll;
lock (_lock)
{
if (!_rerenderRequested) return;
@@ -75,6 +87,9 @@ public class RenderEngine : IRenderEngine
permanentViewsToRender = _permanentViewsToRender.ToList();
forcedTemporaryViewsToRender = _forcedTemporaryViewsToRender.ToList();
_forcedTemporaryViewsToRender.Clear();
forceRerenderAll = _forceRerenderAll;
_forceRerenderAll = false;
}
var driver = _applicationContext.ConsoleDriver;
@@ -93,25 +108,28 @@ public class RenderEngine : IRenderEngine
ClearArray2D(_updatedCells);
}
RenderViews(
forcedTemporaryViewsToRender,
new RenderContext(
driver,
true,
null,
null,
new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
_updatedCells
),
initialPosition,
size);
if (!forceRerenderAll)
{
RenderViews(
forcedTemporaryViewsToRender,
new RenderContext(
driver,
true,
null,
null,
new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence),
_updatedCells
),
initialPosition,
size);
}
RenderViews(
permanentViewsToRender,
new RenderContext(
driver,
false,
forceRerenderAll,
null,
null,
new RenderStatistics(),

View File

@@ -24,8 +24,4 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<Folder Include="KeyHandling\" />
</ItemGroup>
</Project>