TextFormat

This commit is contained in:
2023-08-16 19:21:50 +02:00
parent cbbf7b3704
commit 3c996f0c20
31 changed files with 304 additions and 94 deletions

View File

@@ -8,6 +8,7 @@ using GeneralInputKey;
using TerminalUI.Controls; using TerminalUI.Controls;
using TerminalUI.Extensions; using TerminalUI.Extensions;
using TerminalUI.Models; using TerminalUI.Models;
using TerminalUI.TextFormat;
using TerminalUI.Traits; using TerminalUI.Traits;
using TerminalUI.ViewExtensions; using TerminalUI.ViewExtensions;
@@ -22,11 +23,26 @@ public class Dialogs
private Action? _readInputChildHandlerUnSubscriber; private Action? _readInputChildHandlerUnSubscriber;
private readonly ITextFormat _specialItemNamePartFormat;
public Dialogs(IRootViewModel rootViewModel, ITheme theme) public Dialogs(IRootViewModel rootViewModel, ITheme theme)
{ {
_rootViewModel = rootViewModel; _rootViewModel = rootViewModel;
_theme = theme; _theme = theme;
_specialItemNamePartFormat = new OrFormat
{
Format1 = new AnsiFormat
{
IsUnderline = true
},
Format2 = new SimpleFormat
{
Foreground = _theme.DefaultForegroundAccentColor
}
};
rootViewModel.FocusReadInputElement += element => rootViewModel.FocusReadInputElement += element =>
{ {
_inputElementToFocus = element; _inputElementToFocus = element;
@@ -185,16 +201,15 @@ public class Dialogs
ColumnDefinitionsObject = "* *", ColumnDefinitionsObject = "* *",
ChildInitializer = ChildInitializer =
{ {
new ItemsControl<IPreviewElement, ItemNamePart>() new ItemsControl<IPreviewElement, ItemNamePart>
{ {
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
ItemTemplate = ItemNamePartItemTemplate ItemTemplate = ItemNamePartItemTemplate
}.Setup(i => i.Bind( }.Setup(i => i.Bind(
i, i,
dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts1, dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts1,
c => c.ItemsSource, c => c.ItemsSource)),
v => v)), new ItemsControl<IPreviewElement, ItemNamePart>
new ItemsControl<IPreviewElement, ItemNamePart>()
{ {
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
Extensions = Extensions =
@@ -226,8 +241,8 @@ public class Dialogs
); );
textBlock.Bind( textBlock.Bind(
textBlock, textBlock,
dc => dc.IsSpecial ? _theme.DefaultForegroundAccentColor : null, dc => dc.IsSpecial ? _specialItemNamePartFormat : null,
tb => tb.Foreground tb => tb.TextFormat
); );
return textBlock; return textBlock;
@@ -327,8 +342,7 @@ public class Dialogs
.Setup(t => t.Bind( .Setup(t => t.Bind(
t, t,
d => d.DialogService.ReadInput.Value.Inputs, d => d.DialogService.ReadInput.Value.Inputs,
c => c.ItemsSource, c => c.ItemsSource
v => v
)); ));
readInputs.WithKeyHandler((_, e) => readInputs.WithKeyHandler((_, e) =>

View File

@@ -91,8 +91,7 @@ public class Timeline
.Setup(i => i.Bind( .Setup(i => i.Bind(
i, i,
dc => dc.TimelineViewModel.ParallelCommandsGroups[0].Commands, dc => dc.TimelineViewModel.ParallelCommandsGroups[0].Commands,
i => i.ItemsSource, i => i.ItemsSource))
v => v))
} }
}; };

View File

@@ -185,6 +185,30 @@ public class MainWindow
}, },
ChildInitializer = ChildInitializer =
{ {
new StackPanel<IRootViewModel>
{
Orientation = Orientation.Horizontal,
ChildInitializer =
{
new TextBlock<IRootViewModel>
{
Margin = "0 0 1 0",
}
.Setup(t => t.Bind(
t,
dc => dc.AppState.SelectedTab.Value.CurrentSelectedItem.Value.Attributes,
tb => tb.Text)),
new TextBlock<IRootViewModel>
{
Margin = "0 0 1 0",
}
.Setup(t => t.Bind(
t,
dc => dc.AppState.SelectedTab.Value.CurrentSelectedItem.Value.ModifiedAt,
tb => tb.Text,
v => v.ToString()))
}
},
new TextBlock<IRootViewModel> new TextBlock<IRootViewModel>
{ {
Extensions = {new GridPositionExtension(1, 0)} Extensions = {new GridPositionExtension(1, 0)}

View File

@@ -13,7 +13,7 @@ public static class ColorSchema
public static void PrintColorSchema(ITheme theme, IConsoleDriver consoleDriver) public static void PrintColorSchema(ITheme theme, IConsoleDriver consoleDriver)
{ {
consoleDriver.Dispose(); consoleDriver.Dispose();
consoleDriver.ResetColor(); consoleDriver.ResetStyle();
PrintThemeColors(theme, consoleDriver); PrintThemeColors(theme, consoleDriver);
if (theme is IColorSampleProvider colorSampleProvider) if (theme is IColorSampleProvider colorSampleProvider)
@@ -46,7 +46,7 @@ public static class ColorSchema
PrintColor(consoleDriver, colorProperty.Name, color, colorTextStartX); PrintColor(consoleDriver, colorProperty.Name, color, colorTextStartX);
} }
consoleDriver.ResetColor(); consoleDriver.ResetStyle();
consoleDriver.Write(Environment.NewLine); consoleDriver.Write(Environment.NewLine);
} }
@@ -95,13 +95,13 @@ public static class ColorSchema
PrintColor(consoleDriver, key, value, colorTextStartX); PrintColor(consoleDriver, key, value, colorTextStartX);
} }
consoleDriver.ResetColor(); consoleDriver.ResetStyle();
consoleDriver.Write(Environment.NewLine); consoleDriver.Write(Environment.NewLine);
} }
private static void PrintColor(IConsoleDriver consoleDriver, string name, IColor? color, int colorTextStartX) private static void PrintColor(IConsoleDriver consoleDriver, string name, IColor? color, int colorTextStartX)
{ {
consoleDriver.ResetColor(); consoleDriver.ResetStyle();
consoleDriver.Write(name + ":"); consoleDriver.Write(name + ":");
var y = consoleDriver.GetCursorPosition().Y; var y = consoleDriver.GetCursorPosition().Y;
consoleDriver.SetCursorPosition(new Position(colorTextStartX, y)); consoleDriver.SetCursorPosition(new Position(colorTextStartX, y));
@@ -124,7 +124,7 @@ public static class ColorSchema
consoleDriver.Write("Sample text"); consoleDriver.Write("Sample text");
} }
consoleDriver.ResetColor(); consoleDriver.ResetStyle();
consoleDriver.Write(Environment.NewLine); consoleDriver.Write(Environment.NewLine);
} }
} }

View File

@@ -2,6 +2,7 @@
using TerminalUI.ConsoleDrivers; using TerminalUI.ConsoleDrivers;
using TerminalUI.Controls; using TerminalUI.Controls;
using TerminalUI.Models; using TerminalUI.Models;
using TerminalUI.TextFormat;
namespace TerminalUI.Examples.Controls; namespace TerminalUI.Examples.Controls;
@@ -56,7 +57,8 @@ public class ProgressBarExamples
true, true,
null, null,
null, null,
new() new(),
new TextFormatContext(true)
); );
progressBar.Render(renderContext, position, new Size(10, 1)); progressBar.Render(renderContext, position, new Size(10, 1));
} }

View File

@@ -6,6 +6,8 @@ namespace TerminalUI.ConsoleDrivers;
public class DotnetDriver : IConsoleDriver public class DotnetDriver : IConsoleDriver
{ {
public bool SupportsAnsiEscapeSequence { get; protected set; }
public virtual bool Init() public virtual bool Init()
{ {
Console.Clear(); Console.Clear();
@@ -14,7 +16,8 @@ public class DotnetDriver : IConsoleDriver
public void SetCursorPosition(Position position) => Console.SetCursorPosition(position.X, position.Y); public void SetCursorPosition(Position position) => Console.SetCursorPosition(position.X, position.Y);
public void ResetColor() => Console.ResetColor(); public virtual void ResetColor() => Console.ResetColor();
public virtual void ResetStyle() => Console.ResetColor();
public Position GetCursorPosition() public Position GetCursorPosition()
{ {

View File

@@ -5,10 +5,12 @@ namespace TerminalUI.ConsoleDrivers;
public interface IConsoleDriver public interface IConsoleDriver
{ {
bool SupportsAnsiEscapeSequence { get; }
bool Init(); bool Init();
void Dispose(); void Dispose();
void SetCursorPosition(Position position); void SetCursorPosition(Position position);
void ResetColor(); void ResetColor();
void ResetStyle();
Position GetCursorPosition(); Position GetCursorPosition();
void Write(string text); void Write(string text);
void Write(ReadOnlySpan<char> text); void Write(ReadOnlySpan<char> text);

View File

@@ -8,6 +8,11 @@ public sealed class XTermDriver : DotnetDriver
{ {
private Position _initialCursorPosition; private Position _initialCursorPosition;
public XTermDriver()
{
SupportsAnsiEscapeSequence = true;
}
public override bool Init() public override bool Init()
{ {
_initialCursorPosition = GetCursorPosition(); _initialCursorPosition = GetCursorPosition();
@@ -27,6 +32,11 @@ public sealed class XTermDriver : DotnetDriver
SetCursorPosition(_initialCursorPosition); SetCursorPosition(_initialCursorPosition);
} }
public override void ResetStyle()
{
Write("\x1b[0m");
}
public override void SetBackgroundColor(IColor background) public override void SetBackgroundColor(IColor background)
{ {
if (background == SpecialColor.None) return; if (background == SpecialColor.None) return;

View File

@@ -71,7 +71,7 @@ public sealed partial class Border<T> : ContentView<Border<T>, T>, IDisplayView
if (contentRendered) if (contentRendered)
{ {
var driver = renderContext.ConsoleDriver; var driver = renderContext.ConsoleDriver;
driver.ResetColor(); driver.ResetStyle();
SetColorsForDriver(renderContext); SetColorsForDriver(renderContext);
RenderTopBorder(renderContext, position, size); RenderTopBorder(renderContext, position, size);

View File

@@ -170,13 +170,11 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
_forceRerenderChildren.Clear(); _forceRerenderChildren.Clear();
} }
var childContext = new RenderContext( var childContext = context with
context.ConsoleDriver, {
context.ForceRerender, Foreground = Foreground ?? context.Foreground,
Foreground ?? context.Foreground, Background = Background ?? context.Background,
Background ?? context.Background, };
context.Statistics
);
var viewsByPosition = GroupViewsByPosition(columnWidths.Length, rowHeights.Length); var viewsByPosition = GroupViewsByPosition(columnWidths.Length, rowHeights.Length);
var anyRendered = false; var anyRendered = false;
@@ -214,7 +212,6 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
int row, int row,
IReadOnlyList<IView> forceRerenderChildren) IReadOnlyList<IView> forceRerenderChildren)
{ {
var width = columnWidths[column]; var width = columnWidths[column];
var height = rowHeights[row]; var height = rowHeights[row];
var renderSize = new Size(width, height); var renderSize = new Size(width, height);
@@ -253,7 +250,8 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
true, true,
context.Foreground, context.Foreground,
context.Background, context.Background,
context.Statistics context.Statistics,
context.TextFormat
); );
RenderEmpty(context, renderPosition, renderSize); RenderEmpty(context, renderPosition, renderSize);
} }
@@ -270,7 +268,8 @@ public sealed class Grid<T> : ChildContainerView<Grid<T>, T>, IVisibilityChangeH
true, true,
context.Foreground, context.Foreground,
context.Background, context.Background,
context.Statistics context.Statistics,
context.TextFormat
); );
} }
} }

View File

@@ -85,7 +85,8 @@ public sealed partial class ItemsControl<TDataContext, TItem>
double width = 0; double width = 0;
double height = 0; double height = 0;
foreach (var child in _children) var children = _children.ToList();
foreach (var child in children)
{ {
if (!child.IsVisible) continue; if (!child.IsVisible) continue;
@@ -149,13 +150,7 @@ public sealed partial class ItemsControl<TDataContext, TItem>
if (forceRerenderChildren.Contains(child)) if (forceRerenderChildren.Contains(child))
{ {
var rerenderContext = new RenderContext( var rerenderContext = renderContext with {ForceRerender = true};
renderContext.ConsoleDriver,
true,
renderContext.Foreground,
renderContext.Background,
renderContext.Statistics
);
neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender; neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender;
} }
else else

View File

@@ -286,7 +286,7 @@ public sealed partial class ListView<TDataContext, TItem> : View<ListView<TDataC
var driver = ApplicationContext!.ConsoleDriver; var driver = ApplicationContext!.ConsoleDriver;
var placeholder = new string(' ', size.Width); var placeholder = new string(' ', size.Width);
driver.ResetColor(); driver.ResetStyle();
for (var i = deltaY; i < size.Height; i++) for (var i = deltaY; i < size.Height; i++)
{ {
driver.SetCursorPosition(position with {Y = position.Y + i}); driver.SetCursorPosition(position with {Y = position.Y + i});

View File

@@ -111,7 +111,7 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
}; };
} }
SetColor(driver, foreground, background); SetStyleColor(renderContext, foreground, background);
// Left border // Left border
var textStartPosition = position; var textStartPosition = position;
@@ -132,7 +132,7 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
// Transient character // Transient character
if (progressQuotientWidth < progressAvailableSpace) if (progressQuotientWidth < progressAvailableSpace)
{ {
SetColor(driver, foreground, unfilledBackground); SetStyleColor(renderContext, foreground, unfilledBackground);
RenderText( RenderText(
transientChar, transientChar,
driver, driver,
@@ -147,7 +147,7 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
Span<char> unfilledText = stackalloc char[progressRemainderWidth]; Span<char> unfilledText = stackalloc char[progressRemainderWidth];
unfilledText.Fill(unfilledCharacter); unfilledText.Fill(unfilledCharacter);
SetColor(driver, unfilledForeground, unfilledBackground); SetStyleColor(renderContext, unfilledForeground, unfilledBackground);
RenderText( RenderText(
unfilledText, unfilledText,
driver, driver,
@@ -159,7 +159,7 @@ public partial class ProgressBar<T> : View<ProgressBar<T>, T>
// Right border // Right border
if (rightCap.HasValue) if (rightCap.HasValue)
{ {
SetColor(driver, foreground, background); SetStyleColor(renderContext, foreground, background);
RenderText( RenderText(
rightCap.Value, rightCap.Value,
driver, driver,

View File

@@ -25,7 +25,7 @@ public sealed partial class Rectangle<T> : View<Rectangle<T>, T>, IDisplayView
var driver = renderContext.ConsoleDriver; var driver = renderContext.ConsoleDriver;
var s = new string(' ', size.Width); var s = new string(' ', size.Width);
driver.ResetColor(); driver.ResetStyle();
if (color is not null) if (color is not null)
{ {
driver.SetBackgroundColor(color); driver.SetBackgroundColor(color);

View File

@@ -80,13 +80,7 @@ public sealed partial class StackPanel<T> : ChildContainerView<StackPanel<T>, T>
if (forceRerenderChildren.Contains(child)) if (forceRerenderChildren.Contains(child))
{ {
var rerenderContext = new RenderContext( var rerenderContext = renderContext with {ForceRerender = true};
renderContext.ConsoleDriver,
true,
renderContext.Foreground,
renderContext.Background,
renderContext.Statistics
);
neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender; neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender;
} }
else else

View File

@@ -2,8 +2,8 @@
using System.Diagnostics; using System.Diagnostics;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
using TerminalUI.Color; using TerminalUI.Color;
using TerminalUI.Extensions;
using TerminalUI.Models; using TerminalUI.Models;
using TerminalUI.TextFormat;
using TerminalUI.Traits; using TerminalUI.Traits;
namespace TerminalUI.Controls; namespace TerminalUI.Controls;
@@ -16,7 +16,8 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
Size Size, Size Size,
string? Text, string? Text,
IColor? Foreground, IColor? Foreground,
IColor? Background); IColor? Background,
ITextFormat? TextFormat);
private RenderState? _lastRenderState; private RenderState? _lastRenderState;
private string[]? _textLines; private string[]? _textLines;
@@ -24,17 +25,13 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
[Notify] private string? _text = string.Empty; [Notify] private string? _text = string.Empty;
[Notify] private TextAlignment _textAlignment = TextAlignment.Left; [Notify] private TextAlignment _textAlignment = TextAlignment.Left;
[Notify] private ITextFormat? _textFormat;
public TextBlock() public TextBlock()
{ {
/*this.Bind(
this,
dc => dc == null ? string.Empty : dc.ToString(),
tb => tb.Text
);*/
RerenderProperties.Add(nameof(Text)); RerenderProperties.Add(nameof(Text));
RerenderProperties.Add(nameof(TextAlignment)); RerenderProperties.Add(nameof(TextAlignment));
RerenderProperties.Add(nameof(TextFormat));
((INotifyPropertyChanged) this).PropertyChanged += (o, e) => ((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
{ {
@@ -58,7 +55,8 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
size, size,
Text, Text,
foreground, foreground,
background); background,
_textFormat);
if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false; if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false;
@@ -78,7 +76,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
_placeholderRenderDone = false; _placeholderRenderDone = false;
var driver = renderContext.ConsoleDriver; var driver = renderContext.ConsoleDriver;
SetColor(driver, foreground, background); SetStyleColor(renderContext, foreground, background, _textFormat);
RenderText(_textLines, driver, position, size, TransformText); RenderText(_textLines, driver, position, size, TransformText);

View File

@@ -104,7 +104,7 @@ public sealed partial class TextBox<T> : View<TextBox<T>, T>, IFocusable, IDispl
_lastRenderState = renderStatus; _lastRenderState = renderStatus;
var driver = renderContext.ConsoleDriver; var driver = renderContext.ConsoleDriver;
SetColor(driver, foreground, background); SetStyleColor(renderContext, foreground, background);
RenderEmpty(renderContext, position, size); RenderEmpty(renderContext, position, size);

View File

@@ -8,6 +8,7 @@ using PropertyChanged.SourceGenerator;
using TerminalUI.Color; using TerminalUI.Color;
using TerminalUI.ConsoleDrivers; using TerminalUI.ConsoleDrivers;
using TerminalUI.Models; using TerminalUI.Models;
using TerminalUI.TextFormat;
using TerminalUI.Traits; using TerminalUI.Traits;
namespace TerminalUI.Controls; namespace TerminalUI.Controls;
@@ -175,12 +176,12 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
bool renderResult; bool renderResult;
if (Background != null || Foreground != null) if (Background != null || Foreground != null)
{ {
var newRenderContext = new RenderContext( var newRenderContext = renderContext with
renderContext.ConsoleDriver, {
renderContext.ForceRerender, Foreground = Foreground ?? renderContext.Foreground,
Foreground ?? renderContext.Foreground, Background = Background ?? renderContext.Background
Background ?? renderContext.Background, };
renderContext.Statistics);
renderResult = RenderMethod(newRenderContext, position, size); renderResult = RenderMethod(newRenderContext, position, size);
} }
else else
@@ -202,7 +203,7 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
protected void RenderEmpty(in RenderContext renderContext, Position position, Size size) protected void RenderEmpty(in RenderContext renderContext, Position position, Size size)
{ {
var driver = renderContext.ConsoleDriver; var driver = renderContext.ConsoleDriver;
driver.ResetColor(); driver.ResetStyle();
var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width); var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width);
for (var i = 0; i < size.Height; i++) for (var i = 0; i < size.Height; i++)
@@ -309,13 +310,24 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
driver.Write(contentString); driver.Write(contentString);
} }
} }
protected void SetColor(IConsoleDriver driver, IColor? foreground, IColor? background)
protected void SetStyleColor(
in RenderContext renderContext,
IColor? foreground = null,
IColor? background = null,
ITextFormat? textFormat = null)
{ {
var driver = renderContext.ConsoleDriver;
driver.ResetColor(); driver.ResetColor();
if (textFormat is { } t)
{
t.ApplyFormat(driver, renderContext.TextFormat);
}
if (foreground is not null) if (foreground is not null)
{ {
driver.SetForegroundColor(foreground); driver.SetForegroundColor(foreground);
} }
if (background is not null) if (background is not null)
{ {
driver.SetBackgroundColor(background); driver.SetBackgroundColor(background);
@@ -324,11 +336,9 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
protected void SetColorsForDriver(in RenderContext renderContext) protected void SetColorsForDriver(in RenderContext renderContext)
{ {
var driver = renderContext.ConsoleDriver;
var foreground = Foreground ?? renderContext.Foreground; var foreground = Foreground ?? renderContext.Foreground;
var background = Background ?? renderContext.Background; var background = Background ?? renderContext.Background;
SetColor(driver, foreground, background); SetStyleColor(renderContext, foreground, background);
} }
public TChild CreateChild<TChild>() where TChild : IView<T>, new() public TChild CreateChild<TChild>() where TChild : IView<T>, new()

View File

@@ -6,7 +6,8 @@ namespace TerminalUI.ExpressionTrackers;
public abstract class ExpressionTrackerBase : IExpressionTracker public abstract class ExpressionTrackerBase : IExpressionTracker
{ {
private object? _currentValue; private object? _currentValue;
public List<string> TrackedPropertyNames { get; } = new(); private readonly List<IExpressionTracker> _propertyProxyTrackers = new();
private readonly List<string> _trackedPropertyNames = new();
protected bool SubscribeToValueChanges { get; set; } = true; protected bool SubscribeToValueChanges { get; set; } = true;
@@ -15,10 +16,22 @@ public abstract class ExpressionTrackerBase : IExpressionTracker
public object? GetValue() => _currentValue; public object? GetValue() => _currentValue;
protected abstract object? ComputeValue(); protected abstract object? ComputeValue();
protected void SubscribeToTracker(IExpressionTracker? expressionTracker) protected void SubscribeToTracker(IExpressionTracker? expressionTracker, bool proxyPropertyChanged = false)
{ {
if (expressionTracker is null) return; if (expressionTracker is null) return;
expressionTracker.Update += UpdateValueAndChangeTrackers; expressionTracker.Update += UpdateValueAndChangeTrackers;
if (proxyPropertyChanged)
{
expressionTracker.PropertyChanged += OnPropertyChanged;
_propertyProxyTrackers.Add(expressionTracker);
foreach (var propertyName in _trackedPropertyNames)
{
expressionTracker.TrackProperty(propertyName);
}
}
} }
protected void UpdateValueAndChangeTrackers() => UpdateValueAndChangeTrackers(true); protected void UpdateValueAndChangeTrackers() => UpdateValueAndChangeTrackers(true);
@@ -74,9 +87,24 @@ public abstract class ExpressionTrackerBase : IExpressionTracker
{ {
if (e.PropertyName is null) return; if (e.PropertyName is null) return;
if (TrackedPropertyNames.Contains(e.PropertyName)) if (_trackedPropertyNames.Contains(e.PropertyName))
{ {
PropertyChanged?.Invoke(e.PropertyName); PropertyChanged?.Invoke(e.PropertyName);
} }
} }
protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(propertyName);
public void TrackProperty(string propertyName)
{
if (!_trackedPropertyNames.Contains(propertyName))
{
_trackedPropertyNames.Add(propertyName);
}
foreach (var propertyProxyTracker in _propertyProxyTrackers)
{
propertyProxyTracker.TrackProperty(propertyName);
}
}
} }

View File

@@ -2,8 +2,8 @@
public interface IExpressionTracker public interface IExpressionTracker
{ {
List<string> TrackedPropertyNames { get; }
event Action<string>? PropertyChanged; event Action<string>? PropertyChanged;
event Action<bool>? Update; event Action<bool>? Update;
object? GetValue(); object? GetValue();
void TrackProperty(string propertyName);
} }

View File

@@ -28,7 +28,7 @@ public sealed class MemberTracker : ExpressionTrackerBase
if (memberExpression.Member is PropertyInfo propertyInfo) if (memberExpression.Member is PropertyInfo propertyInfo)
{ {
_memberName = propertyInfo.Name; _memberName = propertyInfo.Name;
parentTracker?.TrackedPropertyNames.Add(propertyInfo.Name); parentTracker?.TrackProperty(propertyInfo.Name);
if (propertyInfo.GetMethod is { } getMethod) if (propertyInfo.GetMethod is { } getMethod)
{ {
@@ -44,7 +44,7 @@ public sealed class MemberTracker : ExpressionTrackerBase
else if (memberExpression.Member is FieldInfo fieldInfo) else if (memberExpression.Member is FieldInfo fieldInfo)
{ {
_memberName = fieldInfo.Name; _memberName = fieldInfo.Name;
parentTracker?.TrackedPropertyNames.Add(fieldInfo.Name); parentTracker?.TrackProperty(fieldInfo.Name);
_valueProvider = () => CallFieldInfo(fieldInfo); _valueProvider = () => CallFieldInfo(fieldInfo);
} }

View File

@@ -23,7 +23,7 @@ public class UnaryTracker : ExpressionTrackerBase
_ => throw new NotSupportedException($"Unary expression of type {unaryExpression.NodeType} is not supported.") _ => throw new NotSupportedException($"Unary expression of type {unaryExpression.NodeType} is not supported.")
}; };
SubscribeToTracker(operandTracker); SubscribeToTracker(operandTracker, true);
UpdateValueAndChangeTrackers(); UpdateValueAndChangeTrackers();
} }

View File

@@ -1,6 +1,7 @@
using System.Diagnostics; using System.Diagnostics;
using TerminalUI.Color; using TerminalUI.Color;
using TerminalUI.ConsoleDrivers; using TerminalUI.ConsoleDrivers;
using TerminalUI.TextFormat;
namespace TerminalUI.Models; namespace TerminalUI.Models;
@@ -8,19 +9,21 @@ namespace TerminalUI.Models;
public readonly ref struct RenderContext public readonly ref struct RenderContext
{ {
private static int _renderId; private static int _renderId;
public readonly int RenderId; public int RenderId { get; init; }
public readonly IConsoleDriver ConsoleDriver; public IConsoleDriver ConsoleDriver { get; init; }
public readonly bool ForceRerender; public bool ForceRerender { get; init; }
public readonly IColor? Foreground; public IColor? Foreground { get; init; }
public readonly IColor? Background; public IColor? Background { get; init; }
public readonly RenderStatistics Statistics; public RenderStatistics Statistics { get; init; }
public TextFormatContext TextFormat { get; init; }
public RenderContext( public RenderContext(
IConsoleDriver consoleDriver, IConsoleDriver consoleDriver,
bool forceRerender, bool forceRerender,
IColor? foreground, IColor? foreground,
IColor? background, IColor? background,
RenderStatistics statistics) RenderStatistics statistics,
TextFormatContext textFormat)
{ {
RenderId = _renderId++; RenderId = _renderId++;
@@ -29,6 +32,7 @@ public readonly ref struct RenderContext
Foreground = foreground; Foreground = foreground;
Background = background; Background = background;
Statistics = statistics; Statistics = statistics;
TextFormat = textFormat;
} }
public static RenderContext Empty => public static RenderContext Empty =>
@@ -37,6 +41,7 @@ public readonly ref struct RenderContext
false, false,
null, null,
null, null,
new RenderStatistics() new RenderStatistics(),
new TextFormatContext(false)
); );
} }

View File

@@ -1,5 +1,6 @@
using TerminalUI.Controls; using TerminalUI.Controls;
using TerminalUI.Models; using TerminalUI.Models;
using TerminalUI.TextFormat;
using TerminalUI.Traits; using TerminalUI.Traits;
namespace TerminalUI; namespace TerminalUI;
@@ -83,7 +84,8 @@ public class RenderEngine : IRenderEngine
true, true,
null, null,
null, null,
new RenderStatistics() new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence)
), ),
initialPosition, initialPosition,
size); size);
@@ -95,7 +97,8 @@ public class RenderEngine : IRenderEngine
false, false,
null, null,
null, null,
new RenderStatistics() new RenderStatistics(),
new TextFormatContext(driver.SupportsAnsiEscapeSequence)
), ),
initialPosition, initialPosition,
size); size);

View File

@@ -0,0 +1,25 @@
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public class AnsiFormat : ITextFormat
{
public bool IsUnderline { get; set; }
public bool IsItalic { get; set; }
public bool IsBold { get; set; }
public bool CanApply(TextFormatContext context) => context.SupportsAnsi;
public void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context)
{
if (!context.SupportsAnsi) return;
if (IsUnderline)
consoleDriver.Write("\x01b[4m");
if (IsItalic)
consoleDriver.Write("\x01b[3m");
if (IsBold)
consoleDriver.Write("\x01b[1m");
}
}

View File

@@ -0,0 +1,13 @@
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public readonly struct EmptyFormat : ITextFormat
{
public static EmptyFormat Instance => new();
public bool CanApply(TextFormatContext context) => true;
public void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context)
{
}
}

View File

@@ -0,0 +1,23 @@
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public class FormatCollection : ITextFormat
{
public List<ITextFormat> Formats { get; } = new();
public Func<IReadOnlyList<ITextFormat>, TextFormatContext, bool> CanApplyPredicate { get; set; } = DefaultCanApplyPredicate;
public bool CanApply(TextFormatContext context) => CanApplyPredicate(Formats, context);
public void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context)
{
foreach (var format in Formats)
{
format.ApplyFormat(consoleDriver, context);
}
}
public static bool DefaultCanApplyPredicate(IReadOnlyList<ITextFormat> formats, TextFormatContext context)
=> formats.Any(format => format.CanApply(context));
}

View File

@@ -0,0 +1,9 @@
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public interface ITextFormat
{
bool CanApply(TextFormatContext context);
void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context);
}

View File

@@ -0,0 +1,23 @@
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public class OrFormat : ITextFormat
{
public required ITextFormat Format1 { get; set; }
public required ITextFormat Format2 { get; set; }
public bool CanApply(TextFormatContext context) => true;
public void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context)
{
if (Format1.CanApply(context))
{
Format1.ApplyFormat(consoleDriver, context);
}
else
{
Format2.ApplyFormat(consoleDriver, context);
}
}
}

View File

@@ -0,0 +1,20 @@
using TerminalUI.Color;
using TerminalUI.ConsoleDrivers;
namespace TerminalUI.TextFormat;
public class SimpleFormat : ITextFormat
{
public IColor? Foreground { get; init; }
public IColor? Background { get; init; }
public bool CanApply(TextFormatContext context) => true;
public void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context)
{
if (Foreground is not null)
consoleDriver.SetForegroundColor(Foreground);
if (Background is not null)
consoleDriver.SetBackgroundColor(Background);
}
}

View File

@@ -0,0 +1,11 @@
namespace TerminalUI.TextFormat;
public readonly struct TextFormatContext
{
public readonly bool SupportsAnsi;
public TextFormatContext(bool supportsAnsi)
{
SupportsAnsi = supportsAnsi;
}
}