FilePreview use ASCII only, force rerender when item disappears
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using TerminalUI.Controls;
|
||||
using TerminalUI.ExpressionTrackers;
|
||||
using TerminalUI.Traits;
|
||||
|
||||
namespace TerminalUI;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -40,7 +65,7 @@ public class DotnetDriver : IConsoleDriver
|
||||
public virtual void SetForegroundColor(IColor foreground)
|
||||
{
|
||||
if (foreground == SpecialColor.None) return;
|
||||
|
||||
|
||||
if (foreground is not ConsoleColor consoleColor) throw new NotSupportedException();
|
||||
Console.ForegroundColor = consoleColor.Color;
|
||||
}
|
||||
@@ -48,7 +73,7 @@ public class DotnetDriver : IConsoleDriver
|
||||
public virtual void SetBackgroundColor(IColor background)
|
||||
{
|
||||
if (background == SpecialColor.None) return;
|
||||
|
||||
|
||||
if (background is not ConsoleColor consoleColor) throw new NotSupportedException();
|
||||
Console.BackgroundColor = consoleColor.Color;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public sealed class XTermDriver : DotnetDriver
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
CheckThreadId = false;
|
||||
Write("\x1b[?1047l");
|
||||
SetCursorPosition(_initialCursorPosition);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -4,4 +4,6 @@ public interface IEventLoop
|
||||
{
|
||||
void Run();
|
||||
void AddToPermanentQueue(Action action);
|
||||
void AddInitializer(Action action);
|
||||
int ThreadId { get; set; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
@@ -24,8 +24,4 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="KeyHandling\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user