diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs index 8d28a7e..c93a9c9 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Dialogs.cs @@ -8,6 +8,7 @@ using GeneralInputKey; using TerminalUI.Controls; using TerminalUI.Extensions; using TerminalUI.Models; +using TerminalUI.TextFormat; using TerminalUI.Traits; using TerminalUI.ViewExtensions; @@ -22,11 +23,26 @@ public class Dialogs private Action? _readInputChildHandlerUnSubscriber; + + private readonly ITextFormat _specialItemNamePartFormat; + public Dialogs(IRootViewModel rootViewModel, ITheme theme) { _rootViewModel = rootViewModel; _theme = theme; + _specialItemNamePartFormat = new OrFormat + { + Format1 = new AnsiFormat + { + IsUnderline = true + }, + Format2 = new SimpleFormat + { + Foreground = _theme.DefaultForegroundAccentColor + } + }; + rootViewModel.FocusReadInputElement += element => { _inputElementToFocus = element; @@ -185,16 +201,15 @@ public class Dialogs ColumnDefinitionsObject = "* *", ChildInitializer = { - new ItemsControl() + new ItemsControl { Orientation = Orientation.Horizontal, ItemTemplate = ItemNamePartItemTemplate }.Setup(i => i.Bind( i, dc => ((DoubleItemNamePartListPreview) dc).ItemNameParts1, - c => c.ItemsSource, - v => v)), - new ItemsControl() + c => c.ItemsSource)), + new ItemsControl { Orientation = Orientation.Horizontal, Extensions = @@ -226,8 +241,8 @@ public class Dialogs ); textBlock.Bind( textBlock, - dc => dc.IsSpecial ? _theme.DefaultForegroundAccentColor : null, - tb => tb.Foreground + dc => dc.IsSpecial ? _specialItemNamePartFormat : null, + tb => tb.TextFormat ); return textBlock; @@ -327,8 +342,7 @@ public class Dialogs .Setup(t => t.Bind( t, d => d.DialogService.ReadInput.Value.Inputs, - c => c.ItemsSource, - v => v + c => c.ItemsSource )); readInputs.WithKeyHandler((_, e) => diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Timeline.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Timeline.cs index 042205c..60a7b4c 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Timeline.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/Controls/Timeline.cs @@ -91,8 +91,7 @@ public class Timeline .Setup(i => i.Bind( i, dc => dc.TimelineViewModel.ParallelCommandsGroups[0].Commands, - i => i.ItemsSource, - v => v)) + i => i.ItemsSource)) } }; diff --git a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs index c1f0722..86ac62a 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI.App/MainWindow.cs @@ -185,6 +185,30 @@ public class MainWindow }, ChildInitializer = { + new StackPanel + { + Orientation = Orientation.Horizontal, + ChildInitializer = + { + new TextBlock + { + Margin = "0 0 1 0", + } + .Setup(t => t.Bind( + t, + dc => dc.AppState.SelectedTab.Value.CurrentSelectedItem.Value.Attributes, + tb => tb.Text)), + new TextBlock + { + 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 { Extensions = {new GridPositionExtension(1, 0)} diff --git a/src/ConsoleApp/FileTime.ConsoleUI/InfoProviders/ColorSchema.cs b/src/ConsoleApp/FileTime.ConsoleUI/InfoProviders/ColorSchema.cs index e3d54e6..59d8cf9 100644 --- a/src/ConsoleApp/FileTime.ConsoleUI/InfoProviders/ColorSchema.cs +++ b/src/ConsoleApp/FileTime.ConsoleUI/InfoProviders/ColorSchema.cs @@ -13,7 +13,7 @@ public static class ColorSchema public static void PrintColorSchema(ITheme theme, IConsoleDriver consoleDriver) { consoleDriver.Dispose(); - consoleDriver.ResetColor(); + consoleDriver.ResetStyle(); PrintThemeColors(theme, consoleDriver); if (theme is IColorSampleProvider colorSampleProvider) @@ -46,7 +46,7 @@ public static class ColorSchema PrintColor(consoleDriver, colorProperty.Name, color, colorTextStartX); } - consoleDriver.ResetColor(); + consoleDriver.ResetStyle(); consoleDriver.Write(Environment.NewLine); } @@ -95,13 +95,13 @@ public static class ColorSchema PrintColor(consoleDriver, key, value, colorTextStartX); } - consoleDriver.ResetColor(); + consoleDriver.ResetStyle(); consoleDriver.Write(Environment.NewLine); } private static void PrintColor(IConsoleDriver consoleDriver, string name, IColor? color, int colorTextStartX) { - consoleDriver.ResetColor(); + consoleDriver.ResetStyle(); consoleDriver.Write(name + ":"); var y = consoleDriver.GetCursorPosition().Y; consoleDriver.SetCursorPosition(new Position(colorTextStartX, y)); @@ -124,7 +124,7 @@ public static class ColorSchema consoleDriver.Write("Sample text"); } - consoleDriver.ResetColor(); + consoleDriver.ResetStyle(); consoleDriver.Write(Environment.NewLine); } } \ No newline at end of file diff --git a/src/Library/TerminalUI.Examples/Controls/ProgressBarExamples.cs b/src/Library/TerminalUI.Examples/Controls/ProgressBarExamples.cs index b37c1aa..a7b4254 100644 --- a/src/Library/TerminalUI.Examples/Controls/ProgressBarExamples.cs +++ b/src/Library/TerminalUI.Examples/Controls/ProgressBarExamples.cs @@ -2,6 +2,7 @@ using TerminalUI.ConsoleDrivers; using TerminalUI.Controls; using TerminalUI.Models; +using TerminalUI.TextFormat; namespace TerminalUI.Examples.Controls; @@ -56,7 +57,8 @@ public class ProgressBarExamples true, null, null, - new() + new(), + new TextFormatContext(true) ); progressBar.Render(renderContext, position, new Size(10, 1)); } diff --git a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs index d9f9d24..4440cbd 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/DotnetDriver.cs @@ -6,6 +6,8 @@ namespace TerminalUI.ConsoleDrivers; public class DotnetDriver : IConsoleDriver { + public bool SupportsAnsiEscapeSequence { get; protected set; } + public virtual bool Init() { Console.Clear(); @@ -14,7 +16,8 @@ public class DotnetDriver : IConsoleDriver 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() { diff --git a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs index 48a76c1..39619f0 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/IConsoleDriver.cs @@ -5,10 +5,12 @@ namespace TerminalUI.ConsoleDrivers; public interface IConsoleDriver { + bool SupportsAnsiEscapeSequence { get; } bool Init(); void Dispose(); void SetCursorPosition(Position position); void ResetColor(); + void ResetStyle(); Position GetCursorPosition(); void Write(string text); void Write(ReadOnlySpan text); diff --git a/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs b/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs index ba402c1..e555c9d 100644 --- a/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs +++ b/src/Library/TerminalUI/ConsoleDrivers/XTermDriver.cs @@ -8,6 +8,11 @@ public sealed class XTermDriver : DotnetDriver { private Position _initialCursorPosition; + public XTermDriver() + { + SupportsAnsiEscapeSequence = true; + } + public override bool Init() { _initialCursorPosition = GetCursorPosition(); @@ -27,6 +32,11 @@ public sealed class XTermDriver : DotnetDriver SetCursorPosition(_initialCursorPosition); } + public override void ResetStyle() + { + Write("\x1b[0m"); + } + public override void SetBackgroundColor(IColor background) { if (background == SpecialColor.None) return; diff --git a/src/Library/TerminalUI/Controls/Border.cs b/src/Library/TerminalUI/Controls/Border.cs index 07f1959..89cc9fe 100644 --- a/src/Library/TerminalUI/Controls/Border.cs +++ b/src/Library/TerminalUI/Controls/Border.cs @@ -71,7 +71,7 @@ public sealed partial class Border : ContentView, T>, IDisplayView if (contentRendered) { var driver = renderContext.ConsoleDriver; - driver.ResetColor(); + driver.ResetStyle(); SetColorsForDriver(renderContext); RenderTopBorder(renderContext, position, size); diff --git a/src/Library/TerminalUI/Controls/Grid.cs b/src/Library/TerminalUI/Controls/Grid.cs index f210bca..b2f0c77 100644 --- a/src/Library/TerminalUI/Controls/Grid.cs +++ b/src/Library/TerminalUI/Controls/Grid.cs @@ -170,13 +170,11 @@ public sealed class Grid : ChildContainerView, T>, IVisibilityChangeH _forceRerenderChildren.Clear(); } - var childContext = new RenderContext( - context.ConsoleDriver, - context.ForceRerender, - Foreground ?? context.Foreground, - Background ?? context.Background, - context.Statistics - ); + var childContext = context with + { + Foreground = Foreground ?? context.Foreground, + Background = Background ?? context.Background, + }; var viewsByPosition = GroupViewsByPosition(columnWidths.Length, rowHeights.Length); var anyRendered = false; @@ -214,7 +212,6 @@ public sealed class Grid : ChildContainerView, T>, IVisibilityChangeH int row, IReadOnlyList forceRerenderChildren) { - var width = columnWidths[column]; var height = rowHeights[row]; var renderSize = new Size(width, height); @@ -238,7 +235,7 @@ public sealed class Grid : ChildContainerView, T>, IVisibilityChangeH } if (renderSize.Width == 0 || renderSize.Height == 0) return false; - + if (!viewsByPosition.TryGetValue((column, row), out var children)) { RenderEmpty(context, renderPosition, renderSize); @@ -253,7 +250,8 @@ public sealed class Grid : ChildContainerView, T>, IVisibilityChangeH true, context.Foreground, context.Background, - context.Statistics + context.Statistics, + context.TextFormat ); RenderEmpty(context, renderPosition, renderSize); } @@ -270,7 +268,8 @@ public sealed class Grid : ChildContainerView, T>, IVisibilityChangeH true, context.Foreground, context.Background, - context.Statistics + context.Statistics, + context.TextFormat ); } } diff --git a/src/Library/TerminalUI/Controls/ItemsControl.cs b/src/Library/TerminalUI/Controls/ItemsControl.cs index 8a51c46..a39a8fd 100644 --- a/src/Library/TerminalUI/Controls/ItemsControl.cs +++ b/src/Library/TerminalUI/Controls/ItemsControl.cs @@ -85,7 +85,8 @@ public sealed partial class ItemsControl double width = 0; double height = 0; - foreach (var child in _children) + var children = _children.ToList(); + foreach (var child in children) { if (!child.IsVisible) continue; @@ -149,13 +150,7 @@ public sealed partial class ItemsControl if (forceRerenderChildren.Contains(child)) { - var rerenderContext = new RenderContext( - renderContext.ConsoleDriver, - true, - renderContext.Foreground, - renderContext.Background, - renderContext.Statistics - ); + var rerenderContext = renderContext with {ForceRerender = true}; neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender; } else diff --git a/src/Library/TerminalUI/Controls/ListView.cs b/src/Library/TerminalUI/Controls/ListView.cs index 903b6b1..0cc1844 100644 --- a/src/Library/TerminalUI/Controls/ListView.cs +++ b/src/Library/TerminalUI/Controls/ListView.cs @@ -286,7 +286,7 @@ public sealed partial class ListView : View : View, T> }; } - SetColor(driver, foreground, background); + SetStyleColor(renderContext, foreground, background); // Left border var textStartPosition = position; @@ -132,7 +132,7 @@ public partial class ProgressBar : View, T> // Transient character if (progressQuotientWidth < progressAvailableSpace) { - SetColor(driver, foreground, unfilledBackground); + SetStyleColor(renderContext, foreground, unfilledBackground); RenderText( transientChar, driver, @@ -147,7 +147,7 @@ public partial class ProgressBar : View, T> Span unfilledText = stackalloc char[progressRemainderWidth]; unfilledText.Fill(unfilledCharacter); - SetColor(driver, unfilledForeground, unfilledBackground); + SetStyleColor(renderContext, unfilledForeground, unfilledBackground); RenderText( unfilledText, driver, @@ -159,7 +159,7 @@ public partial class ProgressBar : View, T> // Right border if (rightCap.HasValue) { - SetColor(driver, foreground, background); + SetStyleColor(renderContext, foreground, background); RenderText( rightCap.Value, driver, diff --git a/src/Library/TerminalUI/Controls/Rectangle.cs b/src/Library/TerminalUI/Controls/Rectangle.cs index 601ccd0..80e0428 100644 --- a/src/Library/TerminalUI/Controls/Rectangle.cs +++ b/src/Library/TerminalUI/Controls/Rectangle.cs @@ -25,7 +25,7 @@ public sealed partial class Rectangle : View, T>, IDisplayView var driver = renderContext.ConsoleDriver; var s = new string(' ', size.Width); - driver.ResetColor(); + driver.ResetStyle(); if (color is not null) { driver.SetBackgroundColor(color); diff --git a/src/Library/TerminalUI/Controls/StackPanel.cs b/src/Library/TerminalUI/Controls/StackPanel.cs index 20b1502..91ef7fd 100644 --- a/src/Library/TerminalUI/Controls/StackPanel.cs +++ b/src/Library/TerminalUI/Controls/StackPanel.cs @@ -80,13 +80,7 @@ public sealed partial class StackPanel : ChildContainerView, T> if (forceRerenderChildren.Contains(child)) { - var rerenderContext = new RenderContext( - renderContext.ConsoleDriver, - true, - renderContext.Foreground, - renderContext.Background, - renderContext.Statistics - ); + var rerenderContext = renderContext with {ForceRerender = true}; neededRerender = child.Render(rerenderContext, childPosition, childSize) || neededRerender; } else diff --git a/src/Library/TerminalUI/Controls/TextBlock.cs b/src/Library/TerminalUI/Controls/TextBlock.cs index bebaa60..b8699d9 100644 --- a/src/Library/TerminalUI/Controls/TextBlock.cs +++ b/src/Library/TerminalUI/Controls/TextBlock.cs @@ -2,8 +2,8 @@ using System.Diagnostics; using PropertyChanged.SourceGenerator; using TerminalUI.Color; -using TerminalUI.Extensions; using TerminalUI.Models; +using TerminalUI.TextFormat; using TerminalUI.Traits; namespace TerminalUI.Controls; @@ -16,7 +16,8 @@ public sealed partial class TextBlock : View, T>, IDisplayView Size Size, string? Text, IColor? Foreground, - IColor? Background); + IColor? Background, + ITextFormat? TextFormat); private RenderState? _lastRenderState; private string[]? _textLines; @@ -24,17 +25,13 @@ public sealed partial class TextBlock : View, T>, IDisplayView [Notify] private string? _text = string.Empty; [Notify] private TextAlignment _textAlignment = TextAlignment.Left; + [Notify] private ITextFormat? _textFormat; public TextBlock() { - /*this.Bind( - this, - dc => dc == null ? string.Empty : dc.ToString(), - tb => tb.Text - );*/ - RerenderProperties.Add(nameof(Text)); RerenderProperties.Add(nameof(TextAlignment)); + RerenderProperties.Add(nameof(TextFormat)); ((INotifyPropertyChanged) this).PropertyChanged += (o, e) => { @@ -58,7 +55,8 @@ public sealed partial class TextBlock : View, T>, IDisplayView size, Text, foreground, - background); + background, + _textFormat); if (!renderContext.ForceRerender && !NeedsRerender(renderState)) return false; @@ -78,7 +76,7 @@ public sealed partial class TextBlock : View, T>, IDisplayView _placeholderRenderDone = false; var driver = renderContext.ConsoleDriver; - SetColor(driver, foreground, background); + SetStyleColor(renderContext, foreground, background, _textFormat); RenderText(_textLines, driver, position, size, TransformText); diff --git a/src/Library/TerminalUI/Controls/TextBox.cs b/src/Library/TerminalUI/Controls/TextBox.cs index 03b97ed..14bc106 100644 --- a/src/Library/TerminalUI/Controls/TextBox.cs +++ b/src/Library/TerminalUI/Controls/TextBox.cs @@ -104,7 +104,7 @@ public sealed partial class TextBox : View, T>, IFocusable, IDispl _lastRenderState = renderStatus; var driver = renderContext.ConsoleDriver; - SetColor(driver, foreground, background); + SetStyleColor(renderContext, foreground, background); RenderEmpty(renderContext, position, size); diff --git a/src/Library/TerminalUI/Controls/View.cs b/src/Library/TerminalUI/Controls/View.cs index 184350e..0af6fee 100644 --- a/src/Library/TerminalUI/Controls/View.cs +++ b/src/Library/TerminalUI/Controls/View.cs @@ -8,6 +8,7 @@ using PropertyChanged.SourceGenerator; using TerminalUI.Color; using TerminalUI.ConsoleDrivers; using TerminalUI.Models; +using TerminalUI.TextFormat; using TerminalUI.Traits; namespace TerminalUI.Controls; @@ -175,12 +176,12 @@ public abstract partial class View : IView where TConcrete : Vi bool renderResult; if (Background != null || Foreground != null) { - var newRenderContext = new RenderContext( - renderContext.ConsoleDriver, - renderContext.ForceRerender, - Foreground ?? renderContext.Foreground, - Background ?? renderContext.Background, - renderContext.Statistics); + var newRenderContext = renderContext with + { + Foreground = Foreground ?? renderContext.Foreground, + Background = Background ?? renderContext.Background + }; + renderResult = RenderMethod(newRenderContext, position, size); } else @@ -202,7 +203,7 @@ public abstract partial class View : IView where TConcrete : Vi protected void RenderEmpty(in RenderContext renderContext, Position position, Size size) { var driver = renderContext.ConsoleDriver; - driver.ResetColor(); + driver.ResetStyle(); var placeHolder = new string(ApplicationContext!.EmptyCharacter, size.Width); for (var i = 0; i < size.Height; i++) @@ -309,14 +310,25 @@ public abstract partial class View : IView where TConcrete : Vi 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(); + if (textFormat is { } t) + { + t.ApplyFormat(driver, renderContext.TextFormat); + } if (foreground is not null) { driver.SetForegroundColor(foreground); } - if(background is not null) + + if (background is not null) { driver.SetBackgroundColor(background); } @@ -324,11 +336,9 @@ public abstract partial class View : IView where TConcrete : Vi protected void SetColorsForDriver(in RenderContext renderContext) { - var driver = renderContext.ConsoleDriver; - var foreground = Foreground ?? renderContext.Foreground; var background = Background ?? renderContext.Background; - SetColor(driver, foreground, background); + SetStyleColor(renderContext, foreground, background); } public TChild CreateChild() where TChild : IView, new() diff --git a/src/Library/TerminalUI/ExpressionTrackers/ExpressionTrackerBase.cs b/src/Library/TerminalUI/ExpressionTrackers/ExpressionTrackerBase.cs index c40bfab..4901364 100644 --- a/src/Library/TerminalUI/ExpressionTrackers/ExpressionTrackerBase.cs +++ b/src/Library/TerminalUI/ExpressionTrackers/ExpressionTrackerBase.cs @@ -6,7 +6,8 @@ namespace TerminalUI.ExpressionTrackers; public abstract class ExpressionTrackerBase : IExpressionTracker { private object? _currentValue; - public List TrackedPropertyNames { get; } = new(); + private readonly List _propertyProxyTrackers = new(); + private readonly List _trackedPropertyNames = new(); protected bool SubscribeToValueChanges { get; set; } = true; @@ -15,10 +16,22 @@ public abstract class ExpressionTrackerBase : IExpressionTracker public object? GetValue() => _currentValue; protected abstract object? ComputeValue(); - protected void SubscribeToTracker(IExpressionTracker? expressionTracker) + protected void SubscribeToTracker(IExpressionTracker? expressionTracker, bool proxyPropertyChanged = false) { if (expressionTracker is null) return; + expressionTracker.Update += UpdateValueAndChangeTrackers; + + if (proxyPropertyChanged) + { + expressionTracker.PropertyChanged += OnPropertyChanged; + _propertyProxyTrackers.Add(expressionTracker); + + foreach (var propertyName in _trackedPropertyNames) + { + expressionTracker.TrackProperty(propertyName); + } + } } protected void UpdateValueAndChangeTrackers() => UpdateValueAndChangeTrackers(true); @@ -67,16 +80,31 @@ public abstract class ExpressionTrackerBase : IExpressionTracker } } - private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) => UpdateValueAndChangeTrackers(); private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is null) return; - if (TrackedPropertyNames.Contains(e.PropertyName)) + if (_trackedPropertyNames.Contains(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); + } + } } \ No newline at end of file diff --git a/src/Library/TerminalUI/ExpressionTrackers/IExpressionTracker.cs b/src/Library/TerminalUI/ExpressionTrackers/IExpressionTracker.cs index 4304350..92e8678 100644 --- a/src/Library/TerminalUI/ExpressionTrackers/IExpressionTracker.cs +++ b/src/Library/TerminalUI/ExpressionTrackers/IExpressionTracker.cs @@ -2,8 +2,8 @@ public interface IExpressionTracker { - List TrackedPropertyNames { get; } event Action? PropertyChanged; event Action? Update; object? GetValue(); + void TrackProperty(string propertyName); } \ No newline at end of file diff --git a/src/Library/TerminalUI/ExpressionTrackers/MemberTracker.cs b/src/Library/TerminalUI/ExpressionTrackers/MemberTracker.cs index ad2f522..64281ae 100644 --- a/src/Library/TerminalUI/ExpressionTrackers/MemberTracker.cs +++ b/src/Library/TerminalUI/ExpressionTrackers/MemberTracker.cs @@ -28,7 +28,7 @@ public sealed class MemberTracker : ExpressionTrackerBase if (memberExpression.Member is PropertyInfo propertyInfo) { _memberName = propertyInfo.Name; - parentTracker?.TrackedPropertyNames.Add(propertyInfo.Name); + parentTracker?.TrackProperty(propertyInfo.Name); if (propertyInfo.GetMethod is { } getMethod) { @@ -44,7 +44,7 @@ public sealed class MemberTracker : ExpressionTrackerBase else if (memberExpression.Member is FieldInfo fieldInfo) { _memberName = fieldInfo.Name; - parentTracker?.TrackedPropertyNames.Add(fieldInfo.Name); + parentTracker?.TrackProperty(fieldInfo.Name); _valueProvider = () => CallFieldInfo(fieldInfo); } @@ -61,15 +61,15 @@ public sealed class MemberTracker : ExpressionTrackerBase { var obj = _parentTracker?.GetValue(); if (obj is null && !propertyInfo.GetMethod!.IsStatic) return null; - + return propertyInfo.GetValue(_parentTracker?.GetValue()); } - + private object? CallFieldInfo(FieldInfo fieldInfo) { var obj = _parentTracker?.GetValue(); if (obj is null && !fieldInfo.IsStatic) return null; - + return fieldInfo.GetValue(_parentTracker?.GetValue()); } diff --git a/src/Library/TerminalUI/ExpressionTrackers/UnaryTracker.cs b/src/Library/TerminalUI/ExpressionTrackers/UnaryTracker.cs index 43c8b1c..d95dc09 100644 --- a/src/Library/TerminalUI/ExpressionTrackers/UnaryTracker.cs +++ b/src/Library/TerminalUI/ExpressionTrackers/UnaryTracker.cs @@ -23,7 +23,7 @@ public class UnaryTracker : ExpressionTrackerBase _ => throw new NotSupportedException($"Unary expression of type {unaryExpression.NodeType} is not supported.") }; - SubscribeToTracker(operandTracker); + SubscribeToTracker(operandTracker, true); UpdateValueAndChangeTrackers(); } diff --git a/src/Library/TerminalUI/Models/RenderContext.cs b/src/Library/TerminalUI/Models/RenderContext.cs index edb0711..0ab4965 100644 --- a/src/Library/TerminalUI/Models/RenderContext.cs +++ b/src/Library/TerminalUI/Models/RenderContext.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using TerminalUI.Color; using TerminalUI.ConsoleDrivers; +using TerminalUI.TextFormat; namespace TerminalUI.Models; @@ -8,19 +9,21 @@ namespace TerminalUI.Models; public readonly ref struct RenderContext { private static int _renderId; - public readonly int RenderId; - public readonly IConsoleDriver ConsoleDriver; - public readonly bool ForceRerender; - public readonly IColor? Foreground; - public readonly IColor? Background; - public readonly RenderStatistics Statistics; + public int RenderId { get; init; } + public IConsoleDriver ConsoleDriver { get; init; } + public bool ForceRerender { get; init; } + public IColor? Foreground { get; init; } + public IColor? Background { get; init; } + public RenderStatistics Statistics { get; init; } + public TextFormatContext TextFormat { get; init; } public RenderContext( IConsoleDriver consoleDriver, bool forceRerender, IColor? foreground, IColor? background, - RenderStatistics statistics) + RenderStatistics statistics, + TextFormatContext textFormat) { RenderId = _renderId++; @@ -29,6 +32,7 @@ public readonly ref struct RenderContext Foreground = foreground; Background = background; Statistics = statistics; + TextFormat = textFormat; } public static RenderContext Empty => @@ -37,6 +41,7 @@ public readonly ref struct RenderContext false, null, null, - new RenderStatistics() + new RenderStatistics(), + new TextFormatContext(false) ); } \ No newline at end of file diff --git a/src/Library/TerminalUI/RenderEngine.cs b/src/Library/TerminalUI/RenderEngine.cs index 419224b..72855ce 100644 --- a/src/Library/TerminalUI/RenderEngine.cs +++ b/src/Library/TerminalUI/RenderEngine.cs @@ -1,5 +1,6 @@ using TerminalUI.Controls; using TerminalUI.Models; +using TerminalUI.TextFormat; using TerminalUI.Traits; namespace TerminalUI; @@ -83,7 +84,8 @@ public class RenderEngine : IRenderEngine true, null, null, - new RenderStatistics() + new RenderStatistics(), + new TextFormatContext(driver.SupportsAnsiEscapeSequence) ), initialPosition, size); @@ -95,7 +97,8 @@ public class RenderEngine : IRenderEngine false, null, null, - new RenderStatistics() + new RenderStatistics(), + new TextFormatContext(driver.SupportsAnsiEscapeSequence) ), initialPosition, size); diff --git a/src/Library/TerminalUI/TextFormat/AnsiFormat.cs b/src/Library/TerminalUI/TextFormat/AnsiFormat.cs new file mode 100644 index 0000000..ad7018d --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/AnsiFormat.cs @@ -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"); + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/EmptyFormat.cs b/src/Library/TerminalUI/TextFormat/EmptyFormat.cs new file mode 100644 index 0000000..316bc1f --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/EmptyFormat.cs @@ -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) + { + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/FormatCollection.cs b/src/Library/TerminalUI/TextFormat/FormatCollection.cs new file mode 100644 index 0000000..e8bcb90 --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/FormatCollection.cs @@ -0,0 +1,23 @@ +using TerminalUI.ConsoleDrivers; + +namespace TerminalUI.TextFormat; + +public class FormatCollection : ITextFormat +{ + public List Formats { get; } = new(); + + public Func, 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 formats, TextFormatContext context) + => formats.Any(format => format.CanApply(context)); +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/ITextFormat.cs b/src/Library/TerminalUI/TextFormat/ITextFormat.cs new file mode 100644 index 0000000..47155e7 --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/ITextFormat.cs @@ -0,0 +1,9 @@ +using TerminalUI.ConsoleDrivers; + +namespace TerminalUI.TextFormat; + +public interface ITextFormat +{ + bool CanApply(TextFormatContext context); + void ApplyFormat(IConsoleDriver consoleDriver, TextFormatContext context); +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/OrFormat.cs b/src/Library/TerminalUI/TextFormat/OrFormat.cs new file mode 100644 index 0000000..938305a --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/OrFormat.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/SimpleFormat.cs b/src/Library/TerminalUI/TextFormat/SimpleFormat.cs new file mode 100644 index 0000000..e554e60 --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/SimpleFormat.cs @@ -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); + } +} \ No newline at end of file diff --git a/src/Library/TerminalUI/TextFormat/TextFormatContext.cs b/src/Library/TerminalUI/TextFormat/TextFormatContext.cs new file mode 100644 index 0000000..124857f --- /dev/null +++ b/src/Library/TerminalUI/TextFormat/TextFormatContext.cs @@ -0,0 +1,11 @@ +namespace TerminalUI.TextFormat; + +public readonly struct TextFormatContext +{ + public readonly bool SupportsAnsi; + + public TextFormatContext(bool supportsAnsi) + { + SupportsAnsi = supportsAnsi; + } +} \ No newline at end of file