TerminalUI theming, Commands panel
This commit is contained in:
@@ -153,6 +153,8 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
||||
|
||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (size.Width == 0 || size.Height == 0) return false;
|
||||
|
||||
return WithCalculatedSize(
|
||||
renderContext,
|
||||
new Option<Size>(size, true),
|
||||
@@ -177,29 +179,34 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
||||
);
|
||||
var viewsByPosition = GroupViewsByPosition(columnWidths.Length, rowHeights.Length);
|
||||
|
||||
var anyRendered = false;
|
||||
for (var column = 0; column < columnWidths.Length; column++)
|
||||
{
|
||||
for (var row = 0; row < rowHeights.Length; row++)
|
||||
{
|
||||
RenderViewsByPosition(
|
||||
childContext,
|
||||
position,
|
||||
columnWidths,
|
||||
rowHeights,
|
||||
viewsByPosition,
|
||||
column,
|
||||
row,
|
||||
forceRerenderChildren
|
||||
);
|
||||
anyRendered =
|
||||
RenderViewsByPosition(
|
||||
childContext,
|
||||
position,
|
||||
size,
|
||||
columnWidths,
|
||||
rowHeights,
|
||||
viewsByPosition,
|
||||
column,
|
||||
row,
|
||||
forceRerenderChildren
|
||||
)
|
||||
|| anyRendered;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return anyRendered;
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderViewsByPosition(RenderContext context,
|
||||
private bool RenderViewsByPosition(RenderContext context,
|
||||
Position gridPosition,
|
||||
Size gridSize,
|
||||
ReadOnlySpan<int> columnWidths,
|
||||
ReadOnlySpan<int> rowHeights,
|
||||
IReadOnlyDictionary<(int, int), List<IView>> viewsByPosition,
|
||||
@@ -207,7 +214,7 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
||||
int row,
|
||||
IReadOnlyList<IView> forceRerenderChildren)
|
||||
{
|
||||
if (!viewsByPosition.TryGetValue((column, row), out var children)) return;
|
||||
if (!viewsByPosition.TryGetValue((column, row), out var children)) return false;
|
||||
|
||||
var width = columnWidths[column];
|
||||
var height = rowHeights[row];
|
||||
@@ -221,6 +228,18 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
||||
row
|
||||
);
|
||||
|
||||
if (renderPosition.X + width > gridPosition.X + gridSize.Width)
|
||||
{
|
||||
renderSize = renderSize with {Width = gridPosition.X + gridSize.Width - renderPosition.X};
|
||||
}
|
||||
|
||||
if (renderPosition.Y + height > gridPosition.Y + gridSize.Height)
|
||||
{
|
||||
renderSize = renderSize with {Height = gridPosition.Y + gridSize.Height - renderPosition.Y};
|
||||
}
|
||||
|
||||
if (renderSize.Width == 0 || renderSize.Height == 0) return false;
|
||||
|
||||
var needsRerender = children.Any(forceRerenderChildren.Contains);
|
||||
if (needsRerender)
|
||||
{
|
||||
@@ -251,6 +270,8 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
static Position GetRenderPosition(
|
||||
Position gridPosition,
|
||||
ReadOnlySpan<int> columnWidths,
|
||||
|
||||
168
src/Library/TerminalUI/Controls/ProgressBar.cs
Normal file
168
src/Library/TerminalUI/Controls/ProgressBar.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using PropertyChanged.SourceGenerator;
|
||||
using TerminalUI.Color;
|
||||
using TerminalUI.Models;
|
||||
using TerminalUI.Styling.Controls;
|
||||
|
||||
namespace TerminalUI.Controls;
|
||||
|
||||
public partial class ProgressBar<T> : View<ProgressBar<T>, T>
|
||||
{
|
||||
private record RenderState(
|
||||
Position Position,
|
||||
Size Size,
|
||||
int Minimum,
|
||||
int Maximum,
|
||||
int Value,
|
||||
char? LeftCap,
|
||||
char? RightCap,
|
||||
char? Fill,
|
||||
char? Unfilled,
|
||||
IColor? UnfilledForeground,
|
||||
IColor? UnfilledBackground);
|
||||
|
||||
private RenderState? _lastRenderState;
|
||||
|
||||
[Notify] private int _minimum = 0;
|
||||
[Notify] private int _maximum = 100;
|
||||
[Notify] private int _value = 0;
|
||||
[Notify] private IProgressBarTheme? _theme;
|
||||
|
||||
private IProgressBarTheme? AppTheme => ApplicationContext?.Theme?.ControlThemes.ProgressBar;
|
||||
|
||||
public ProgressBar()
|
||||
{
|
||||
RerenderProperties.Add(nameof(Minimum));
|
||||
RerenderProperties.Add(nameof(Maximum));
|
||||
RerenderProperties.Add(nameof(Value));
|
||||
RerenderProperties.Add(nameof(Theme));
|
||||
}
|
||||
|
||||
|
||||
protected override Size CalculateSize() => new(5, 1);
|
||||
|
||||
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||
{
|
||||
if (size.Width == 0 || size.Height == 0) return false;
|
||||
var theme = AppTheme;
|
||||
|
||||
var foreground = Foreground ?? (_theme ?? theme)?.ForegroundColor ?? renderContext.Foreground;
|
||||
var background = Background ?? (_theme ?? theme)?.BackgroundColor ?? renderContext.Background;
|
||||
var unfilledForeground = (_theme ?? theme)?.UnfilledForeground ?? renderContext.Foreground;
|
||||
var unfilledBackground = (_theme ?? theme)?.UnfilledBackground ?? renderContext.Background;
|
||||
var unfilledCharacter = (_theme ?? theme)?.UnfilledCharacter ?? ApplicationContext?.EmptyCharacter ?? ' ';
|
||||
var fillCharacter = (_theme ?? theme)?.FilledCharacter ?? '█';
|
||||
var leftCap = (_theme ?? theme)?.LeftCap;
|
||||
var rightCap = (_theme ?? theme)?.RightCap;
|
||||
|
||||
var renderState = new RenderState(
|
||||
position,
|
||||
size,
|
||||
Minimum,
|
||||
Maximum,
|
||||
Value,
|
||||
leftCap,
|
||||
rightCap,
|
||||
fillCharacter,
|
||||
unfilledCharacter,
|
||||
unfilledForeground,
|
||||
unfilledBackground);
|
||||
|
||||
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
|
||||
|
||||
_lastRenderState = renderState;
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
|
||||
var borderWidth =
|
||||
(leftCap.HasValue ? 1 : 0)
|
||||
+ (rightCap.HasValue ? 1 : 0);
|
||||
|
||||
var progress = (double) (Value - Minimum) / (Maximum - Minimum);
|
||||
var progressAvailableSpace = size.Width - borderWidth;
|
||||
var progressWidth = progress * progressAvailableSpace;
|
||||
var progressQuotientWidth = (int) Math.Floor(progressWidth);
|
||||
var progressRemainderWidth = progressAvailableSpace - progressQuotientWidth - 1;
|
||||
if (progressRemainderWidth < 0) progressRemainderWidth = 0;
|
||||
|
||||
Span<char> filledText = stackalloc char[progressQuotientWidth];
|
||||
var transientChar = unfilledCharacter;
|
||||
|
||||
filledText.Fill(fillCharacter);
|
||||
if (ApplicationContext!.SupportUtf8Output)
|
||||
{
|
||||
var remained = progressWidth - progressQuotientWidth;
|
||||
transientChar = remained switch
|
||||
{
|
||||
< 0.125 => unfilledCharacter,
|
||||
< 0.250 => (_theme ?? theme)?.Fraction1Per8Character ?? '\u258F',
|
||||
< 0.375 => (_theme ?? theme)?.Fraction2Per8Character ?? '\u258E',
|
||||
< 0.500 => (_theme ?? theme)?.Fraction3Per8Character ?? '\u258D',
|
||||
< 0.675 => (_theme ?? theme)?.Fraction4Per8Character ?? '\u258C',
|
||||
< 0.750 => (_theme ?? theme)?.Fraction5Per8Character ?? '\u258B',
|
||||
< 0.875 => (_theme ?? theme)?.Fraction6Per8Character ?? '\u258A',
|
||||
< 0_001 => (_theme ?? theme)?.Fraction7Per8Character ?? '\u2589',
|
||||
_ => (_theme ?? theme)?.FractionFull ?? '\u2588',
|
||||
};
|
||||
}
|
||||
|
||||
SetColor(driver, foreground, background);
|
||||
|
||||
// Left border
|
||||
var textStartPosition = position;
|
||||
if (leftCap.HasValue)
|
||||
{
|
||||
RenderText(leftCap.Value, driver, position, size with {Width = 1});
|
||||
textStartPosition = textStartPosition with {X = textStartPosition.X + 1};
|
||||
}
|
||||
|
||||
// Filled
|
||||
RenderText(
|
||||
filledText,
|
||||
driver,
|
||||
textStartPosition,
|
||||
size with {Width = progressQuotientWidth}
|
||||
);
|
||||
|
||||
// Transient character
|
||||
if (progressQuotientWidth < progressAvailableSpace)
|
||||
{
|
||||
SetColor(driver, foreground, unfilledBackground);
|
||||
RenderText(
|
||||
transientChar,
|
||||
driver,
|
||||
textStartPosition with {X = textStartPosition.X + progressQuotientWidth},
|
||||
size with {Width = 1}
|
||||
);
|
||||
}
|
||||
|
||||
// Unfilled
|
||||
if (progressRemainderWidth != 0)
|
||||
{
|
||||
Span<char> unfilledText = stackalloc char[progressRemainderWidth];
|
||||
unfilledText.Fill(unfilledCharacter);
|
||||
|
||||
SetColor(driver, unfilledForeground, unfilledBackground);
|
||||
RenderText(
|
||||
unfilledText,
|
||||
driver,
|
||||
textStartPosition with {X = textStartPosition.X + progressQuotientWidth + 1},
|
||||
size with {Width = progressRemainderWidth}
|
||||
);
|
||||
}
|
||||
|
||||
// Right border
|
||||
if (rightCap.HasValue)
|
||||
{
|
||||
SetColor(driver, foreground, background);
|
||||
RenderText(
|
||||
rightCap.Value,
|
||||
driver,
|
||||
position with {X = position.X + size.Width - 1},
|
||||
size with {Width = 1});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool NeedsRerender(RenderState renderState)
|
||||
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||
}
|
||||
@@ -78,16 +78,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
||||
_placeholderRenderDone = false;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
SetColor(driver, foreground, background);
|
||||
|
||||
RenderText(_textLines, driver, position, size, TransformText);
|
||||
|
||||
|
||||
@@ -104,16 +104,7 @@ public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDispl
|
||||
_lastRenderState = renderStatus;
|
||||
|
||||
var driver = renderContext.ConsoleDriver;
|
||||
driver.ResetColor();
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
SetColor(driver, foreground, background);
|
||||
|
||||
RenderEmpty(renderContext, position, size);
|
||||
|
||||
|
||||
@@ -67,7 +67,21 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
||||
|
||||
public virtual Size GetRequestedSize()
|
||||
{
|
||||
var size = CalculateSize();
|
||||
Size size;
|
||||
if (Width.HasValue && Height.HasValue)
|
||||
{
|
||||
size = new Size(Width.Value, Height.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
size = CalculateSize();
|
||||
|
||||
if (Width.HasValue)
|
||||
size = size with {Width = Width.Value};
|
||||
|
||||
if (Height.HasValue)
|
||||
size = size with {Height = Height.Value};
|
||||
}
|
||||
|
||||
if (MinWidth.HasValue && size.Width < MinWidth.Value)
|
||||
size = size with {Width = MinWidth.Value};
|
||||
@@ -220,8 +234,14 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
||||
text = text[..size.Width];
|
||||
}
|
||||
|
||||
driver.SetCursorPosition(currentPosition);
|
||||
driver.Write(text);
|
||||
try
|
||||
{
|
||||
driver.SetCursorPosition(currentPosition);
|
||||
driver.Write(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +309,18 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
||||
driver.Write(contentString);
|
||||
}
|
||||
}
|
||||
protected void SetColor(IConsoleDriver driver, IColor? foreground, IColor? background)
|
||||
{
|
||||
driver.ResetColor();
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
if(background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetColorsForDriver(in RenderContext renderContext)
|
||||
{
|
||||
@@ -296,15 +328,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
||||
|
||||
var foreground = Foreground ?? renderContext.Foreground;
|
||||
var background = Background ?? renderContext.Background;
|
||||
if (foreground is not null)
|
||||
{
|
||||
driver.SetForegroundColor(foreground);
|
||||
}
|
||||
|
||||
if (background is not null)
|
||||
{
|
||||
driver.SetBackgroundColor(background);
|
||||
}
|
||||
SetColor(driver, foreground, background);
|
||||
}
|
||||
|
||||
public TChild CreateChild<TChild>() where TChild : IView<T>, new()
|
||||
|
||||
Reference in New Issue
Block a user