diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 87086b91..2f54c442 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -125,9 +125,15 @@ public Pixel Blend(Pixel pixelAbove) newBackground = Background; break; case PixelBackgroundMode.Shaded: + // shade the current pixel (newForeground, newBackground) = Shade(); + + // blend the pixelAbove foreground into the shaded pixel newForeground = newForeground.Blend(pixelAbove.Foreground); - break; + + // resulting in new pixel with shaded background and blended foreground + return new Pixel(newForeground, newBackground); + default: throw new ArgumentOutOfRangeException(nameof(pixelAbove)); } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index ebc6ad3e..92b85a70 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Avalonia.Media; @@ -14,13 +13,13 @@ public PixelForeground() { Symbol = new SimpleSymbol(" "); Color = Colors.Transparent; - Weight = FontWeight.Normal; - Style = FontStyle.Normal; + Weight = null; + Style = null; TextDecoration = null; } public PixelForeground(ISymbol symbol, Color color, - FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, + FontWeight? weight = null, FontStyle? style = null, TextDecorationLocation? textDecoration = null) { ArgumentNullException.ThrowIfNull(symbol); @@ -36,15 +35,13 @@ public PixelForeground(ISymbol symbol, Color color, [JsonConverter(typeof(ColorConverter))] public Color Color { get; init; } - [DefaultValue(FontWeight.Normal)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - public FontWeight Weight { get; init; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public FontWeight? Weight { get; init; } - [DefaultValue(FontStyle.Normal)] - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] - public FontStyle Style { get; init; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public FontStyle? Style { get; init; } - [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public TextDecorationLocation? TextDecoration { get; init; } public bool Equals(PixelForeground other) @@ -68,10 +65,15 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground) ISymbol symbolAbove = pixelAboveForeground.Symbol; ArgumentNullException.ThrowIfNull(symbolAbove); - ISymbol newSymbol = Symbol.Blend(ref symbolAbove); + if (pixelAboveForeground.Color == Colors.Transparent) + // if pixelAbove is transparent then the foreground below should be unchanged. + return this; - return new PixelForeground(newSymbol, pixelAboveForeground.Color, pixelAboveForeground.Weight, - pixelAboveForeground.Style, pixelAboveForeground.TextDecoration); + return new PixelForeground(Symbol.Blend(ref symbolAbove), + pixelAboveForeground.Color, + pixelAboveForeground.Weight ?? Weight, + pixelAboveForeground.Style ?? Style, + pixelAboveForeground.TextDecoration ?? TextDecoration); } public override bool Equals([NotNullWhen(true)] object obj) @@ -81,7 +83,8 @@ public override bool Equals([NotNullWhen(true)] object obj) public override int GetHashCode() { - return HashCode.Combine(Symbol, Color, (int)Weight, (int)Style, TextDecoration); + return HashCode.Combine(Symbol, Color, (int)(Weight ?? FontWeight.Normal), (int)(Style ?? FontStyle.Normal), + TextDecoration); } public static bool operator ==(PixelForeground left, PixelForeground right) diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 40d56366..9d5b4555 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -156,8 +156,8 @@ private struct FlushingBuffer private readonly StringBuilder _stringBuilder; private Color _lastBackgroundColor; private Color _lastForegroundColor; - private FontStyle _lastStyle = FontStyle.Normal; - private FontWeight _lastWeight = FontWeight.Normal; + private FontStyle? _lastStyle; + private FontWeight? _lastWeight; private TextDecorationLocation? _lastTextDecoration; private PixelBufferCoordinate _currentBufferPoint; private PixelBufferCoordinate _lastBufferPointStart; diff --git a/src/Consolonia.Core/Dummy/DummyConsole.cs b/src/Consolonia.Core/Dummy/DummyConsole.cs index ba552142..b5ad80d3 100644 --- a/src/Consolonia.Core/Dummy/DummyConsole.cs +++ b/src/Consolonia.Core/Dummy/DummyConsole.cs @@ -54,8 +54,8 @@ public void PauseIO(Task task) { } - public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationLocation? textDecoration, string str) + public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle? style, + FontWeight? weight, TextDecorationLocation? textDecoration, string str) { } diff --git a/src/Consolonia.Core/Infrastructure/IConsole.cs b/src/Consolonia.Core/Infrastructure/IConsole.cs index fa7ccf92..fb49195a 100644 --- a/src/Consolonia.Core/Infrastructure/IConsole.cs +++ b/src/Consolonia.Core/Infrastructure/IConsole.cs @@ -26,8 +26,8 @@ public interface IConsole : IDisposable void SetCaretPosition(PixelBufferCoordinate bufferPoint); PixelBufferCoordinate GetCaretPosition(); - void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationLocation? textDecoration, string str); + void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle? style, + FontWeight? weight, TextDecorationLocation? textDecoration, string str); void WriteText(string str); diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index af58de54..fa3f9dfc 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -97,8 +97,8 @@ public PixelBufferCoordinate GetCaretPosition() return _headBufferPoint; } - public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationLocation? textDecoration, string str) + public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle? style, + FontWeight? weight, TextDecorationLocation? textDecoration, string str) { PauseTask?.Wait(); SetCaretPosition(bufferPoint); diff --git a/src/Consolonia.Designer/ConsolePreview.cs b/src/Consolonia.Designer/ConsolePreview.cs index e2cd21b6..92a40bb9 100644 --- a/src/Consolonia.Designer/ConsolePreview.cs +++ b/src/Consolonia.Designer/ConsolePreview.cs @@ -373,9 +373,9 @@ private class TextBlockComposer private readonly StringBuilder _textBuilder; private Color _lastBackgroundColor; private Color _lastForegroundColor; - private FontStyle _lastStyle = FontStyle.Normal; + private FontStyle? _lastStyle; private TextDecorationLocation? _lastTextDecorations; - private FontWeight _lastWeight = FontWeight.Normal; + private FontWeight? _lastWeight; private double _textRunCharWidth; public TextBlockComposer(StackPanel panel, double charWidth) @@ -433,8 +433,8 @@ public void Flush() Text = text, Foreground = new SolidColorBrush(_lastForegroundColor), Background = new SolidColorBrush(_lastBackgroundColor), - FontWeight = _lastWeight, - FontStyle = _lastStyle, + FontWeight = _lastWeight ?? FontWeight.Normal, + FontStyle = _lastStyle ?? FontStyle.Normal, FontSize = 17, TextDecorations = _lastTextDecorations switch { diff --git a/src/Consolonia.NUnit/UnitTestConsole.cs b/src/Consolonia.NUnit/UnitTestConsole.cs index 6fdad0cb..c42cd357 100644 --- a/src/Consolonia.NUnit/UnitTestConsole.cs +++ b/src/Consolonia.NUnit/UnitTestConsole.cs @@ -55,8 +55,8 @@ PixelBufferCoordinate IConsole.GetCaretPosition() return _fakeCaretPosition; } - void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationLocation? textDecoration, string str) + void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle? style, + FontWeight? weight, TextDecorationLocation? textDecoration, string str) { (ushort x, ushort y) = bufferPoint; diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index adf93160..73ebefec 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -17,9 +17,9 @@ public void Constructor() Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Transparent)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo(" ")); Assert.That(pixelForeground.Symbol.Width, Is.EqualTo(1)); - Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); - Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); - Assert.That(pixelForeground.TextDecoration, Is.Null); + Assert.IsNull(pixelForeground.Weight); + Assert.IsNull(pixelForeground.Style); + Assert.IsNull(pixelForeground.TextDecoration); } [Test] @@ -29,9 +29,9 @@ public void ConstructorWithSymbol() var pixelForeground = new PixelForeground(symbol, Colors.Red); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("a")); - Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); - Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); - Assert.That(pixelForeground.TextDecoration, Is.Null); + Assert.IsNull(pixelForeground.Weight); + Assert.IsNull(pixelForeground.Style); + Assert.IsNull(pixelForeground.TextDecoration); } [Test] @@ -42,8 +42,8 @@ public void ConstructorWithSymbolAndWeight() Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("a")); Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Bold)); - Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); - Assert.That(pixelForeground.TextDecoration, Is.Null); + Assert.IsNull(pixelForeground.Style); + Assert.IsNull(pixelForeground.TextDecoration); } [Test] @@ -53,9 +53,9 @@ public void ConstructorWithSymbolAndStyle() var pixelForeground = new PixelForeground(symbol, Colors.Red, style: FontStyle.Italic); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("a")); - Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); + Assert.IsNull(pixelForeground.Weight); Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Italic)); - Assert.That(pixelForeground.TextDecoration, Is.Null); + Assert.IsNull(pixelForeground.TextDecoration); } [Test] @@ -66,8 +66,8 @@ public void ConstructorWithSymbolAndTextDecorations() var pixelForeground = new PixelForeground(symbol, Colors.Red, textDecoration: textDecoration); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("a")); - Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); - Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); + Assert.IsNull(pixelForeground.Weight); + Assert.IsNull(pixelForeground.Style); Assert.That(pixelForeground.TextDecoration, Is.EqualTo(TextDecorationLocation.Underline)); } @@ -79,9 +79,9 @@ public void ConstructorWithWideCharacter() var pixelForeground = new PixelForeground(symbol, Colors.Red); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("🎵")); - Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); - Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); - Assert.That(pixelForeground.TextDecoration, Is.Null); + Assert.IsNull(pixelForeground.Weight); + Assert.IsNull(pixelForeground.Style); + Assert.IsNull(pixelForeground.TextDecoration); } [Test] diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index b2d19f13..eeae1b37 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -59,9 +59,9 @@ public void ConstructorDrawingBoxSymbolAndColor() new PixelBackground(Colors.Blue)); Assert.That(pixel.Foreground.Symbol.Text, Is.EqualTo("┼")); Assert.That(pixel.Foreground.Color, Is.EqualTo(Colors.Red)); - Assert.That(pixel.Foreground.Style, Is.EqualTo(FontStyle.Normal)); - Assert.That(pixel.Foreground.Weight, Is.EqualTo(FontWeight.Normal)); - Assert.That(pixel.Foreground.TextDecoration, Is.Null); + Assert.IsNull(pixel.Foreground.Style); + Assert.IsNull(pixel.Foreground.Weight); + Assert.IsNull(pixel.Foreground.TextDecoration); Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Blue)); Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } @@ -142,6 +142,42 @@ public void BlendColoredBackground() Assert.That(newPixel.Background.Color, Is.EqualTo(Colors.Blue)); } + [Test] + public void BlendShadedBackground() + { + var pixel = new Pixel(new PixelForeground(new SimpleSymbol("x"), Colors.Gray), + new PixelBackground(Colors.White)); + var pixel2 = new Pixel(new PixelBackground(PixelBackgroundMode.Shaded)); + Pixel newPixel = pixel.Blend(pixel2); + Assert.True(newPixel.Foreground.Symbol.Text == "x"); + // foreground should be lighter than original + Assert.True(newPixel.Foreground.Color.R < pixel.Foreground.Color.R && + newPixel.Foreground.Color.G < pixel.Foreground.Color.G && + newPixel.Foreground.Color.B < pixel.Foreground.Color.B); + // background should be darker than original + Assert.True(newPixel.Background.Color.R < pixel.Background.Color.R && + newPixel.Background.Color.G < pixel.Background.Color.G && + newPixel.Background.Color.B < pixel.Background.Color.B); + } + + [Test] + public void BlendShadedBackground2() + { + var pixel = new Pixel(new PixelForeground(new SimpleSymbol("x"), Colors.Gray), + new PixelBackground(Colors.Black)); + var pixel2 = new Pixel(new PixelBackground(PixelBackgroundMode.Shaded)); + Pixel newPixel = pixel.Blend(pixel2); + Assert.True(newPixel.Foreground.Symbol.Text == "x"); + // foreground should be darker than original + Assert.True(newPixel.Foreground.Color.R < pixel.Foreground.Color.R && + newPixel.Foreground.Color.G < pixel.Foreground.Color.G && + newPixel.Foreground.Color.B < pixel.Foreground.Color.B); + // background should be darker than original + Assert.True(newPixel.Background.Color.R > pixel.Background.Color.R && + newPixel.Background.Color.G > pixel.Background.Color.G && + newPixel.Background.Color.B > pixel.Background.Color.B); + } + [Test] public void HashCode() {