From f087bf5576c29cbc895d60d3fb7150b095e8886e Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Tue, 5 Nov 2024 19:09:19 -0800 Subject: [PATCH 01/48] Add support for Surragate sequences Core Changes * Changed symbols to Rune class * Threaded Rune through the pipeline * Added gallery samples with surrogate, emoji and right to left examples. Misc changes * fixed type with strikthorugh=>strikethrough * Added debugDisplay attributes to help with debugging --- .../Drawing/DrawingContextImpl.cs | 144 +++++++++--------- .../DrawingBoxSymbol.cs | 96 ++++++------ .../PixelBufferImplementation/ISymbol.cs | 14 +- .../PixelBufferImplementation/Pixel.cs | 12 +- .../PixelBackground.cs | 2 + .../PixelForeground.cs | 6 +- .../PixelBufferImplementation/SimpleSymbol.cs | 26 ++-- src/Consolonia.Core/Drawing/RenderTarget.cs | 20 +-- .../InputLessDefaultNetConsole.cs | 22 ++- src/Consolonia.Core/Text/GlyphTypeface.cs | 2 +- .../GalleryViews/GalleryTextBlock.axaml | 9 ++ .../Consolonia.TestsCore/UnitTestConsole.cs | 4 +- 12 files changed, 198 insertions(+), 159 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 438dbe66..80ca0427 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -24,7 +24,7 @@ internal class DrawingContextImpl : IDrawingContextImpl private const byte HorizontalEndPattern = 0b0001; public const int UnderlineThickness = 10; - public const int StrikthroughThickness = 11; + public const int StrikethroughThickness = 11; private readonly Stack _clipStack = new(100); private readonly ConsoleWindow _consoleWindow; private readonly PixelBuffer _pixelBuffer; @@ -70,36 +70,36 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect new SKPaint { FilterQuality = SKFilterQuality.Medium }); for (int y = 0; y < bitmap.Info.Height; y += 2) - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // NOTE: we divide by 2 because we are working with quad pixels, - // // the bitmap has twice the horizontal and twice the vertical of the target rect. - int px = (int)targetRect.TopLeft.X + x / 2; - int py = (int)targetRect.TopLeft.Y + y / 2; - - // get the quad pixel the bitmap - var quadColors = new[] + for (int x = 0; x < bitmap.Info.Width; x += 2) { + // NOTE: we divide by 2 because we are working with quad pixels, + // // the bitmap has twice the horizontal and twice the vertical of the target rect. + int px = (int)targetRect.TopLeft.X + x / 2; + int py = (int)targetRect.TopLeft.Y + y / 2; + + // get the quad pixel the bitmap + var quadColors = new[] + { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represet the 4 pixels - char quadPixel = GetQuadPixelCharacter(quadColors); + // map it to a single char to represet the 4 pixels + char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); - Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); + // get the combined colors for the quad pixel + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); + Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), - new PixelBackground(background)); - CurrentClip.ExecuteWithClipping(new Point(px, py), - () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); - }); - } + var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), + new PixelBackground(background)); + CurrentClip.ExecuteWithClipping(new Point(px, py), + () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); + }); + } } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -150,11 +150,11 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) case VisualBrush: throw new NotImplementedException(); case ISceneBrush sceneBrush: - { - ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); - return; - } + { + ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); + return; + } } Rect r2 = r.TransformToAABB(Transform); @@ -162,19 +162,19 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) double width = r2.Width + (pen?.Thickness ?? 0); double height = r2.Height + (pen?.Thickness ?? 0); for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - { - int px = (int)(r2.TopLeft.X + x); - int py = (int)(r2.TopLeft.Y + y); - - ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); - CurrentClip.ExecuteWithClipping(new Point(px, py), () => + for (int y = 0; y < height; y++) { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), - backgroundBrush); - }); - } + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); + + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), + backgroundBrush); + }); + } } if (pen is null or { Thickness: 0 } @@ -207,7 +207,7 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) } string charactersDoDraw = - string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); + string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); DrawStringInternal(foreground, charactersDoDraw, glyphRun.GlyphTypeface); } @@ -328,7 +328,7 @@ private void ApplyTextDecorationLineInternal(ref Point head, IPen pen, Line line TextDecorationCollection textDecoration = pen.Thickness switch { UnderlineThickness => TextDecorations.Underline, - StrikthroughThickness => TextDecorations.Strikethrough, + StrikethroughThickness => TextDecorations.Strikethrough, _ => throw new ArgumentOutOfRangeException($"Unsupported thickness {pen.Thickness}") }; @@ -486,7 +486,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl } } - private void DrawStringInternal(IBrush foreground, string str, IGlyphTypeface typeface, Point origin = new()) + private void DrawStringInternal(IBrush foreground, string text, IGlyphTypeface typeface, Point origin = new()) { foreground = ConsoleBrush.FromBrush(foreground); if (foreground is not ConsoleBrush { Mode: PixelBackgroundMode.Colored } consoleBrush) @@ -500,52 +500,52 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl Point whereToDraw = origin.Transform(Transform); int currentXPosition = 0; - //todo: support surrogates - foreach (char c in str) + // Each rune maps to a pixel + foreach(var rune in text.EnumerateRunes()) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0)); Color foregroundColor = consoleBrush.Color; - switch (c) + switch (rune.Value) { case '\t': - { - const int tabSize = 8; - var consolePixel = new Pixel(' ', foregroundColor); - for (int j = 0; j < tabSize; j++) { - Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); - CurrentClip.ExecuteWithClipping(newCharacterPoint, () => + const int tabSize = 8; + var consolePixel = new Pixel(new Rune(' '), foregroundColor); + for (int j = 0; j < tabSize; j++) { - _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); + Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); + CurrentClip.ExecuteWithClipping(newCharacterPoint, () => + { + _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); + } + + currentXPosition += tabSize - 1; } - - currentXPosition += tabSize - 1; - } break; case '\n': - { - /* it's not clear if we need to draw anything. Cursor can be placed at the end of the line - var consolePixel = new Pixel(' ', foregroundColor); + { + /* it's not clear if we need to draw anything. Cursor can be placed at the end of the line + var consolePixel = new Pixel(' ', foregroundColor); - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/ - } + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/ + } break; case '\u200B': currentXPosition--; break; default: - { - var consolePixel = new Pixel(c, foregroundColor, typeface.Style, typeface.Weight); - CurrentClip.ExecuteWithClipping(characterPoint, () => { - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); - } + var consolePixel = new Pixel(rune, foregroundColor, typeface.Style, typeface.Weight); + CurrentClip.ExecuteWithClipping(characterPoint, () => + { + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); + } break; } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index b2c968e3..c248754c 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -1,10 +1,13 @@ using System; +using System.Diagnostics; +using System.Text; namespace Consolonia.Core.Drawing.PixelBufferImplementation { /// /// https://en.wikipedia.org/wiki/Box-drawing_character /// + [DebuggerDisplay("DrawingBox {Rune}")] public struct DrawingBoxSymbol : ISymbol { // all 0bXXXX_0000 are special values @@ -18,10 +21,12 @@ public DrawingBoxSymbol(byte upRightDownLeft) private byte _upRightDownLeft; + public Rune Rune => new Rune(GetBoxSymbol()); + /// /// https://en.wikipedia.org/wiki/Code_page_437 /// - char ISymbol.GetCharacter() + private char GetBoxSymbol() { //DOS linedraw characters are not ordered in any programmatic manner, and calculating a particular character shape needs to use a look-up table. from https://en.wikipedia.org/wiki/Box-drawing_character @@ -49,46 +54,46 @@ char ISymbol.GetCharacter() return horizontal ? '╪' : '╫'; default: - { - return _upRightDownLeft switch { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + return _upRightDownLeft switch + { + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } @@ -106,22 +111,21 @@ public ISymbol Blend(ref ISymbol symbolAbove) _upRightDownLeft = BoldSymbol; else _upRightDownLeft |= drawingBoxSymbol._upRightDownLeft; - return this; } - public static byte UpRightDownLeftFromPattern(byte pattern, LineStyle lineStyle) + public static DrawingBoxSymbol UpRightDownLeftFromPattern(byte pattern, LineStyle lineStyle) { - if (pattern == EmptySymbol) return EmptySymbol; + if (pattern == EmptySymbol) return new DrawingBoxSymbol(EmptySymbol); switch (lineStyle) { case LineStyle.SingleLine: - return pattern; + return new DrawingBoxSymbol(pattern); case LineStyle.Bold: - return BoldSymbol; + return new DrawingBoxSymbol(BoldSymbol); case LineStyle.DoubleLine: byte leftPart = (byte)(pattern << 4); - return (byte)(leftPart | pattern); + return new DrawingBoxSymbol((byte)(leftPart | pattern)); default: throw new ArgumentOutOfRangeException(nameof(lineStyle), lineStyle, null); } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs index bd5de0bc..40e37e9a 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs @@ -1,9 +1,21 @@ +using System.Text; + namespace Consolonia.Core.Drawing.PixelBufferImplementation { public interface ISymbol { - char GetCharacter(); + /// + /// The rune for the symbol + /// + Rune Rune { get; } + bool IsWhiteSpace(); + + /// + /// Blend 2 symbols together + /// + /// + /// ISymbol Blend(ref ISymbol symbolAbove); } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 54b3010a..866359ef 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Text; using Avalonia.Media; // ReSharper disable MemberCanBePrivate.Global @@ -6,6 +8,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { + [DebuggerDisplay("'{Foreground.Symbol.Rune}' [{Foreground.Color}, {Background.Color}]")] public readonly struct Pixel { public PixelForeground Foreground { get; } @@ -19,14 +22,9 @@ public Pixel(bool isCaret) : this(PixelBackgroundMode.Transparent) IsCaret = isCaret; } - public Pixel(char character, Color foregroundColor, FontStyle style = FontStyle.Normal, + public Pixel(Rune rune, Color foregroundColor, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal) : - this(new SimpleSymbol(character), foregroundColor, style, weight) - { - } - - public Pixel(byte drawingBoxSymbol, Color foregroundColor) : this( - new DrawingBoxSymbol(drawingBoxSymbol), foregroundColor) + this(new SimpleSymbol(rune), foregroundColor, style, weight) { } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index ac2e8eee..a904e4cc 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics; using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { + [DebuggerDisplay("[{Color}, {Mode}]")] public readonly struct PixelBackground { public PixelBackground(Color color) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index 36091826..ff32d160 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -1,15 +1,17 @@ using System; +using System.Diagnostics; using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { + [DebuggerDisplay("'{Symbol.Rune}' [{Color}]")] public readonly struct PixelForeground { public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null, Color? color = null) { ArgumentNullException.ThrowIfNull(symbol); - Symbol = symbol; + Symbol = symbol ?? new SimpleSymbol((char)0); Color = color ?? Colors.White; Weight = weight; Style = style; @@ -38,8 +40,6 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground) ISymbol symbolAbove = pixelAboveForeground.Symbol; ArgumentNullException.ThrowIfNull(symbolAbove); - if (symbolAbove.IsWhiteSpace()) return this; - ISymbol newSymbol = Symbol.Blend(ref symbolAbove); return new PixelForeground(newSymbol, pixelAboveForeground.Weight, pixelAboveForeground.Style, diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 63d6a830..8de6753e 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,27 +1,29 @@ +using System.Diagnostics; +using System.Text; + namespace Consolonia.Core.Drawing.PixelBufferImplementation { + [DebuggerDisplay("'{Rune}'")] public readonly struct SimpleSymbol : ISymbol { - public SimpleSymbol(char character) + private readonly Rune _rune; + + public SimpleSymbol(char ch) + : this(new Rune(ch)) { - _character = character; } - private readonly char _character; - - char ISymbol.GetCharacter() + public SimpleSymbol(Rune rune) { - return _character; + _rune = rune; } + public Rune Rune => _rune; + public bool IsWhiteSpace() - { - return _character == char.MinValue; - } + => Rune.IsWhiteSpace(_rune); public ISymbol Blend(ref ISymbol symbolAbove) - { - return symbolAbove; - } + => symbolAbove; } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 631f5b88..725049c0 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -21,7 +21,7 @@ internal class RenderTarget : IDrawingContextLayerImpl private PixelBuffer _bufferBuffer; private (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, char character)?[,] _cache; + textDecorations, Rune rune)?[,] _cache; internal RenderTarget(ConsoleWindow consoleWindow) { @@ -98,7 +98,7 @@ private void InitializeCache(ushort width, ushort height) { _cache = new (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, char character)?[width, height]; + textDecorations, Rune rune)?[width, height]; } private void RenderToDevice() @@ -134,10 +134,12 @@ private void RenderToDevice() pixel = new Pixel(new PixelForeground(new SimpleSymbol('░')), new PixelBackground(PixelBackgroundMode.Colored)); - (Color, Color, FontWeight Weight, FontStyle Style, TextDecorationCollection TextDecorations, char) - pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, + (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection + textDecorations, Rune rune) + pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, pixel.Foreground.Style, pixel.Foreground.TextDecorations, - pixel.Foreground.Symbol.GetCharacter()); + pixel.Foreground.Symbol.Rune); + //todo: indexOutOfRange during resize if (_cache[x, y] == pixelSpread) continue; @@ -202,10 +204,10 @@ public void WritePixel( _lastBufferPointStart = _currentBufferPoint = bufferPoint; } - char character = pixel.Foreground.Symbol.GetCharacter(); - if (char.IsControl(character) /*|| character is '保' or '哥'*/) - character = ' '; // some terminals do not print \0 - _stringBuilder.Append(character); + var rune = pixel.Foreground.Symbol.Rune; + if (Rune.IsControl(rune)) /*|| character is '保' or '哥'*/ + rune = new Rune(' '); // some terminals do not print \0 + _stringBuilder.Append(rune); _currentBufferPoint = _currentBufferPoint.WithXpp(); } diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 0025723d..96fe04f9 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -66,7 +66,7 @@ public PixelBufferCoordinate GetCaretPosition() } public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationCollection textDecorations, string str) + FontWeight weight, TextDecorationCollection textDecorations, string text) { PauseTask?.Wait(); SetCaretPosition(bufferPoint); @@ -93,15 +93,25 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light _ => foreground })); - sb.Append(str); + sb.Append(text); sb.Append(ConsoleUtils.Reset); Console.Write(sb.ToString()); - if (_headBufferPoint.X < Size.Width - str.Length) - _headBufferPoint = - new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); - else _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); + // if we have complex unicode runes then we need to calculate the position manually, EnumerateRunes is + // not enough as some runes (like '🥰') are a single rune, but are emitted as two console characters + if (text.EnumerateRunes().Count() != text.Length) + { + var (left, top) = Console.GetCursorPosition(); + _headBufferPoint = new PixelBufferCoordinate((ushort)left, (ushort)top); + } + else + { + if (_headBufferPoint.X < Size.Width - text.Length) + _headBufferPoint = new PixelBufferCoordinate((ushort)(_headBufferPoint.X + text.Length), _headBufferPoint.Y); + else + _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); + } } public event Action Resized; diff --git a/src/Consolonia.Core/Text/GlyphTypeface.cs b/src/Consolonia.Core/Text/GlyphTypeface.cs index 6ce98a97..3434e312 100644 --- a/src/Consolonia.Core/Text/GlyphTypeface.cs +++ b/src/Consolonia.Core/Text/GlyphTypeface.cs @@ -77,7 +77,7 @@ public bool TryGetTable(uint tag, out byte[] table) UnderlinePosition = -1, UnderlineThickness = DrawingContextImpl.UnderlineThickness, StrikethroughPosition = -1, - StrikethroughThickness = DrawingContextImpl.StrikthroughThickness, + StrikethroughThickness = DrawingContextImpl.StrikethroughThickness, IsFixedPitch = true }; diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index a0f44c58..521b950f 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -30,6 +30,15 @@ Text="Multiline TextBlock with TextWrapping. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> + + + + + + + + + diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 964e67a6..46e992fc 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -144,9 +144,9 @@ internal string PrintBuffer() if (i == PixelBuffer.Width - 1 && j == PixelBuffer.Height - 1) break; Pixel pixel = PixelBuffer[new PixelBufferCoordinate(i, j)]; - char character = pixel.IsCaret ? 'Ꮖ' : pixel.Foreground.Symbol.GetCharacter(); + Rune rune = pixel.IsCaret ? new Rune('Ꮖ') : pixel.Foreground.Symbol.Rune; //todo: check why cursor is not drawing - stringBuilder.Append(character); + stringBuilder.Append(rune); } stringBuilder.AppendLine(); From 9446c9b6a188604a3ef76817e6a7e01cd7ea07d6 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Tue, 5 Nov 2024 19:11:04 -0800 Subject: [PATCH 02/48] fix release build warning. --- .../Infrastructure/InputLessDefaultNetConsole.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 96fe04f9..8f408b43 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -66,7 +66,7 @@ public PixelBufferCoordinate GetCaretPosition() } public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, - FontWeight weight, TextDecorationCollection textDecorations, string text) + FontWeight weight, TextDecorationCollection textDecorations, string str) { PauseTask?.Wait(); SetCaretPosition(bufferPoint); @@ -93,22 +93,22 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light _ => foreground })); - sb.Append(text); + sb.Append(str); sb.Append(ConsoleUtils.Reset); Console.Write(sb.ToString()); // if we have complex unicode runes then we need to calculate the position manually, EnumerateRunes is // not enough as some runes (like '🥰') are a single rune, but are emitted as two console characters - if (text.EnumerateRunes().Count() != text.Length) + if (str.EnumerateRunes().Count() != str.Length) { var (left, top) = Console.GetCursorPosition(); _headBufferPoint = new PixelBufferCoordinate((ushort)left, (ushort)top); } else { - if (_headBufferPoint.X < Size.Width - text.Length) - _headBufferPoint = new PixelBufferCoordinate((ushort)(_headBufferPoint.X + text.Length), _headBufferPoint.Y); + if (_headBufferPoint.X < Size.Width - str.Length) + _headBufferPoint = new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); else _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); } From 637747cd982b5f8f3fd3921265124d7d18a566b3 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Wed, 6 Nov 2024 09:25:03 -0800 Subject: [PATCH 03/48] * fix unit tests * Fix caret --- .../Drawing/DrawingContextImpl.cs | 5 ++-- .../PixelBufferImplementation/Pixel.cs | 17 ++++++------ .../PixelBufferImplementation/PixelBuffer.cs | 26 +++++++++++++++---- .../PixelForeground.cs | 2 +- .../PixelBufferImplementation/SimpleSymbol.cs | 8 +++--- src/Consolonia.Core/Drawing/RenderTarget.cs | 2 +- .../Consolonia.TestsCore/UnitTestConsole.cs | 8 ++++-- 7 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 80ca0427..279c09d6 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -91,7 +91,7 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), + var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(new Rune(quadPixel)), color: foreground), new PixelBackground(background)); CurrentClip.ExecuteWithClipping(new Point(px, py), () => @@ -409,8 +409,7 @@ private bool IfMoveConsoleCaretMove(IPen pen, Point head) if (pen.Brush is not MoveConsoleCaretToPositionBrush) return false; - CurrentClip.ExecuteWithClipping(head, - () => { _pixelBuffer.Set((PixelBufferCoordinate)head, pixel => pixel.Blend(new Pixel(true))); }); + _pixelBuffer.SetCaretPosition((PixelBufferCoordinate)head); return true; } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 866359ef..638d3e0d 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -17,11 +17,6 @@ public readonly struct Pixel public bool IsCaret { get; } - public Pixel(bool isCaret) : this(PixelBackgroundMode.Transparent) - { - IsCaret = isCaret; - } - public Pixel(Rune rune, Color foregroundColor, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal) : this(new SimpleSymbol(rune), foregroundColor, style, weight) @@ -66,12 +61,16 @@ public Pixel Blend(Pixel pixelAbove) case PixelBackgroundMode.Colored: // merge pixelAbove into this pixel using alpha channel. Color mergedColors = MergeColors(Background.Color, pixelAbove.Background.Color); - return new Pixel(pixelAbove.Foreground, - new PixelBackground(mergedColors)); + newForeground = pixelAbove.Foreground; + newBackground = new PixelBackground(mergedColors); + break; case PixelBackgroundMode.Transparent: - newForeground = Foreground.Blend(pixelAbove.Foreground); - newBackground = Background; + // if the foreground is transparent, ignore pixelAbove foreground. + newForeground = pixelAbove.Foreground.Color != Colors.Transparent ? Foreground.Blend(pixelAbove.Foreground) : Foreground; + + // background is transparent, ignore pixelAbove background. + newBackground = Background; break; case PixelBackgroundMode.Shaded: (newForeground, newBackground) = Shade(); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index b95607af..1cedb8bf 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -11,12 +11,14 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public class PixelBuffer : IEnumerable { private readonly Pixel[,] _buffer; + private PixelBufferCoordinate _caretPosition; public PixelBuffer(ushort width, ushort height) { Width = width; Height = height; _buffer = new Pixel[width, height]; + _caretPosition = new PixelBufferCoordinate(0, 0); } public ushort Width { get; } @@ -68,6 +70,20 @@ public void Set(PixelBufferCoordinate point, Func + /// Clears old pixel caret position and sets new caret position + /// + /// + public void SetCaretPosition(PixelBufferCoordinate point) + { + var oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; + _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background, isCaret: false); + + _caretPosition = point; + var newCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; + _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(newCaretPixel.Foreground, newCaretPixel.Background, isCaret: true); + } + public void Foreach(Func replaceAction) { ForeachReadonly((point, oldPixel) => @@ -81,11 +97,11 @@ public void Foreach(Func replaceAction) public void ForeachReadonly(Action action) { for (ushort j = 0; j < Height; j++) - for (ushort i = 0; i < Width; i++) - { - Pixel pixel = this[(PixelBufferCoordinate)(i, j)]; - action(new PixelBufferCoordinate(i, j), pixel); - } + for (ushort i = 0; i < Width; i++) + { + Pixel pixel = this[(PixelBufferCoordinate)(i, j)]; + action(new PixelBufferCoordinate(i, j), pixel); + } } private (ushort x, ushort y) ToXY(int i) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index ff32d160..0a5970ca 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -11,7 +11,7 @@ public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null, Color? color = null) { ArgumentNullException.ThrowIfNull(symbol); - Symbol = symbol ?? new SimpleSymbol((char)0); + Symbol = symbol; Color = color ?? Colors.White; Weight = weight; Style = style; diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 8de6753e..5567d00e 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -6,11 +6,11 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation [DebuggerDisplay("'{Rune}'")] public readonly struct SimpleSymbol : ISymbol { - private readonly Rune _rune; + private readonly Rune _rune = new Rune('\0'); - public SimpleSymbol(char ch) - : this(new Rune(ch)) + public SimpleSymbol() { + _rune = new Rune('\0'); } public SimpleSymbol(Rune rune) @@ -24,6 +24,6 @@ public bool IsWhiteSpace() => Rune.IsWhiteSpace(_rune); public ISymbol Blend(ref ISymbol symbolAbove) - => symbolAbove; + => (symbolAbove.Rune.Value != '\0') ? symbolAbove : this; } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 725049c0..855b0fcf 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -131,7 +131,7 @@ private void RenderToDevice() if (pixel.Foreground.Symbol is null) // not using 'when' as it swallows the exceptions // buffer re-initialized after resizing - pixel = new Pixel(new PixelForeground(new SimpleSymbol('░')), + pixel = new Pixel(new PixelForeground(new SimpleSymbol(new Rune('░'))), new PixelBackground(PixelBackgroundMode.Colored)); (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 46e992fc..65bdad1b 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -58,13 +58,17 @@ void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color f { (ushort x, ushort y) = bufferPoint; - for (int i = 0; i < str.Length; i++) + int i = 0; + foreach (var rune in str.EnumerateRunes()) + { PixelBuffer.Set(new PixelBufferCoordinate((ushort)(x + i), y), _ => // ReSharper disable once AccessToModifiedClosure we are sure about inline execution new Pixel( - new PixelForeground(new SimpleSymbol(str[i]), color: foreground, style: style, weight: weight, + new PixelForeground(new SimpleSymbol(rune), color: foreground, style: style, weight: weight, textDecorations: textDecorations), new PixelBackground(PixelBackgroundMode.Colored, background))); + i++; + } } public void PauseIO(Task task) From 559fe7be20e4313f48dd30f3593393872cbe28a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 17:29:22 +0000 Subject: [PATCH 04/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Drawing/DrawingContextImpl.cs | 135 +++++++++--------- .../DrawingBoxSymbol.cs | 80 +++++------ .../PixelBufferImplementation/ISymbol.cs | 4 +- .../PixelBufferImplementation/Pixel.cs | 8 +- .../PixelBufferImplementation/PixelBuffer.cs | 22 +-- .../PixelBufferImplementation/SimpleSymbol.cs | 16 ++- src/Consolonia.Core/Drawing/RenderTarget.cs | 6 +- .../InputLessDefaultNetConsole.cs | 5 +- .../Consolonia.TestsCore/UnitTestConsole.cs | 2 +- 9 files changed, 143 insertions(+), 135 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 279c09d6..4778509d 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -70,36 +70,37 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect new SKPaint { FilterQuality = SKFilterQuality.Medium }); for (int y = 0; y < bitmap.Info.Height; y += 2) - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // NOTE: we divide by 2 because we are working with quad pixels, - // // the bitmap has twice the horizontal and twice the vertical of the target rect. - int px = (int)targetRect.TopLeft.X + x / 2; - int py = (int)targetRect.TopLeft.Y + y / 2; + for (int x = 0; x < bitmap.Info.Width; x += 2) + { + // NOTE: we divide by 2 because we are working with quad pixels, + // // the bitmap has twice the horizontal and twice the vertical of the target rect. + int px = (int)targetRect.TopLeft.X + x / 2; + int py = (int)targetRect.TopLeft.Y + y / 2; - // get the quad pixel the bitmap - var quadColors = new[] - { + // get the quad pixel the bitmap + var quadColors = new[] + { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represet the 4 pixels - char quadPixel = GetQuadPixelCharacter(quadColors); + // map it to a single char to represet the 4 pixels + char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); - Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); + // get the combined colors for the quad pixel + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); + Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel(new PixelForeground(new SimpleSymbol(new Rune(quadPixel)), color: foreground), - new PixelBackground(background)); - CurrentClip.ExecuteWithClipping(new Point(px, py), - () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); - }); - } + var imagePixel = new Pixel( + new PixelForeground(new SimpleSymbol(new Rune(quadPixel)), color: foreground), + new PixelBackground(background)); + CurrentClip.ExecuteWithClipping(new Point(px, py), + () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); + }); + } } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -150,11 +151,11 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) case VisualBrush: throw new NotImplementedException(); case ISceneBrush sceneBrush: - { - ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); - return; - } + { + ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); + return; + } } Rect r2 = r.TransformToAABB(Transform); @@ -162,19 +163,19 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) double width = r2.Width + (pen?.Thickness ?? 0); double height = r2.Height + (pen?.Thickness ?? 0); for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - { - int px = (int)(r2.TopLeft.X + x); - int py = (int)(r2.TopLeft.Y + y); + for (int y = 0; y < height; y++) + { + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); - ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); - CurrentClip.ExecuteWithClipping(new Point(px, py), () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), - backgroundBrush); - }); - } + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), + backgroundBrush); + }); + } } if (pen is null or { Thickness: 0 } @@ -207,7 +208,7 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) } string charactersDoDraw = - string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); + string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); DrawStringInternal(foreground, charactersDoDraw, glyphRun.GlyphTypeface); } @@ -500,7 +501,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl int currentXPosition = 0; // Each rune maps to a pixel - foreach(var rune in text.EnumerateRunes()) + foreach (Rune rune in text.EnumerateRunes()) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0)); Color foregroundColor = consoleBrush.Color; @@ -508,43 +509,43 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl switch (rune.Value) { case '\t': + { + const int tabSize = 8; + var consolePixel = new Pixel(new Rune(' '), foregroundColor); + for (int j = 0; j < tabSize; j++) { - const int tabSize = 8; - var consolePixel = new Pixel(new Rune(' '), foregroundColor); - for (int j = 0; j < tabSize; j++) + Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); + CurrentClip.ExecuteWithClipping(newCharacterPoint, () => { - Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); - CurrentClip.ExecuteWithClipping(newCharacterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); - } - - currentXPosition += tabSize - 1; + _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); } + + currentXPosition += tabSize - 1; + } break; case '\n': - { - /* it's not clear if we need to draw anything. Cursor can be placed at the end of the line - var consolePixel = new Pixel(' ', foregroundColor); + { + /* it's not clear if we need to draw anything. Cursor can be placed at the end of the line + var consolePixel = new Pixel(' ', foregroundColor); - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/ - } + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/ + } break; case '\u200B': currentXPosition--; break; default: + { + var consolePixel = new Pixel(rune, foregroundColor, typeface.Style, typeface.Weight); + CurrentClip.ExecuteWithClipping(characterPoint, () => { - var consolePixel = new Pixel(rune, foregroundColor, typeface.Style, typeface.Weight); - CurrentClip.ExecuteWithClipping(characterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); - } + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); + } break; } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index c248754c..bdc834ec 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -21,7 +21,7 @@ public DrawingBoxSymbol(byte upRightDownLeft) private byte _upRightDownLeft; - public Rune Rune => new Rune(GetBoxSymbol()); + public Rune Rune => new(GetBoxSymbol()); /// /// https://en.wikipedia.org/wiki/Code_page_437 @@ -54,46 +54,46 @@ private char GetBoxSymbol() return horizontal ? '╪' : '╫'; default: + { + return _upRightDownLeft switch { - return _upRightDownLeft switch - { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs index 40e37e9a..03d11c9c 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs @@ -5,14 +5,14 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public interface ISymbol { /// - /// The rune for the symbol + /// The rune for the symbol /// Rune Rune { get; } bool IsWhiteSpace(); /// - /// Blend 2 symbols together + /// Blend 2 symbols together /// /// /// diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 638d3e0d..f093689f 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -67,10 +67,12 @@ public Pixel Blend(Pixel pixelAbove) case PixelBackgroundMode.Transparent: // if the foreground is transparent, ignore pixelAbove foreground. - newForeground = pixelAbove.Foreground.Color != Colors.Transparent ? Foreground.Blend(pixelAbove.Foreground) : Foreground; - + newForeground = pixelAbove.Foreground.Color != Colors.Transparent + ? Foreground.Blend(pixelAbove.Foreground) + : Foreground; + // background is transparent, ignore pixelAbove background. - newBackground = Background; + newBackground = Background; break; case PixelBackgroundMode.Shaded: (newForeground, newBackground) = Shade(); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index 1cedb8bf..b11eac64 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -71,17 +71,19 @@ public void Set(PixelBufferCoordinate point, Func - /// Clears old pixel caret position and sets new caret position + /// Clears old pixel caret position and sets new caret position /// /// public void SetCaretPosition(PixelBufferCoordinate point) { - var oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; - _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background, isCaret: false); + Pixel oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; + _buffer[_caretPosition.X, _caretPosition.Y] = + new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background, false); _caretPosition = point; - var newCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; - _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(newCaretPixel.Foreground, newCaretPixel.Background, isCaret: true); + Pixel newCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; + _buffer[_caretPosition.X, _caretPosition.Y] = + new Pixel(newCaretPixel.Foreground, newCaretPixel.Background, true); } public void Foreach(Func replaceAction) @@ -97,11 +99,11 @@ public void Foreach(Func replaceAction) public void ForeachReadonly(Action action) { for (ushort j = 0; j < Height; j++) - for (ushort i = 0; i < Width; i++) - { - Pixel pixel = this[(PixelBufferCoordinate)(i, j)]; - action(new PixelBufferCoordinate(i, j), pixel); - } + for (ushort i = 0; i < Width; i++) + { + Pixel pixel = this[(PixelBufferCoordinate)(i, j)]; + action(new PixelBufferCoordinate(i, j), pixel); + } } private (ushort x, ushort y) ToXY(int i) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 5567d00e..5f35d4c5 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -6,24 +6,26 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation [DebuggerDisplay("'{Rune}'")] public readonly struct SimpleSymbol : ISymbol { - private readonly Rune _rune = new Rune('\0'); - public SimpleSymbol() { - _rune = new Rune('\0'); + Rune = new Rune('\0'); } public SimpleSymbol(Rune rune) { - _rune = rune; + Rune = rune; } - public Rune Rune => _rune; + public Rune Rune { get; } = new('\0'); public bool IsWhiteSpace() - => Rune.IsWhiteSpace(_rune); + { + return Rune.IsWhiteSpace(Rune); + } public ISymbol Blend(ref ISymbol symbolAbove) - => (symbolAbove.Rune.Value != '\0') ? symbolAbove : this; + { + return symbolAbove.Rune.Value != '\0' ? symbolAbove : this; + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 855b0fcf..2a6e7740 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -135,8 +135,8 @@ private void RenderToDevice() new PixelBackground(PixelBackgroundMode.Colored)); (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, Rune rune) - pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, + textDecorations, Rune rune) + pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, pixel.Foreground.Style, pixel.Foreground.TextDecorations, pixel.Foreground.Symbol.Rune); @@ -204,7 +204,7 @@ public void WritePixel( _lastBufferPointStart = _currentBufferPoint = bufferPoint; } - var rune = pixel.Foreground.Symbol.Rune; + Rune rune = pixel.Foreground.Symbol.Rune; if (Rune.IsControl(rune)) /*|| character is '保' or '哥'*/ rune = new Rune(' '); // some terminals do not print \0 _stringBuilder.Append(rune); diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 8f408b43..0fc0d2ba 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -102,13 +102,14 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light // not enough as some runes (like '🥰') are a single rune, but are emitted as two console characters if (str.EnumerateRunes().Count() != str.Length) { - var (left, top) = Console.GetCursorPosition(); + (int left, int top) = Console.GetCursorPosition(); _headBufferPoint = new PixelBufferCoordinate((ushort)left, (ushort)top); } else { if (_headBufferPoint.X < Size.Width - str.Length) - _headBufferPoint = new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); + _headBufferPoint = + new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); else _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); } diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 65bdad1b..9304e66b 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -59,7 +59,7 @@ void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color f (ushort x, ushort y) = bufferPoint; int i = 0; - foreach (var rune in str.EnumerateRunes()) + foreach (Rune rune in str.EnumerateRunes()) { PixelBuffer.Set(new PixelBufferCoordinate((ushort)(x + i), y), _ => // ReSharper disable once AccessToModifiedClosure we are sure about inline execution From ef299e5f83506933c2d5f96c801baaa20b646b1b Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Wed, 6 Nov 2024 18:08:18 -0800 Subject: [PATCH 05/48] use Text...everything works except for double char emojiis --- .../Drawing/DrawingContextImpl.cs | 9 +++--- .../PixelBufferImplementation/PixelBuffer.cs | 2 +- src/Consolonia.Core/Text/GlyphRunImpl.cs | 7 ++-- src/Consolonia.Core/Text/TextShaper.cs | 32 +++++++------------ .../GalleryViews/GalleryTextBlock.axaml | 18 +++++------ 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 279c09d6..9c2dd48c 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -5,6 +5,7 @@ using Avalonia; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; @@ -206,9 +207,9 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) return; } - string charactersDoDraw = - string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); - DrawStringInternal(foreground, charactersDoDraw, glyphRun.GlyphTypeface); + var shapedBuffer = glyphRunImpl.GlyphInfos as ShapedBuffer; + var text = shapedBuffer.Text.ToString(); + DrawStringInternal(foreground, text, glyphRun.GlyphTypeface); } public IDrawingContextLayerImpl CreateLayer(Size size) @@ -500,7 +501,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl int currentXPosition = 0; // Each rune maps to a pixel - foreach(var rune in text.EnumerateRunes()) + foreach (var rune in text.EnumerateRunes()) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0)); Color foregroundColor = consoleBrush.Color; diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index 1cedb8bf..8568f4f2 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -77,7 +77,7 @@ public void Set(PixelBufferCoordinate point, Func glyph FontRenderingEmSize = glyphTypeface.Metrics.DesignEmHeight; GlyphTypeface = glyphTypeface; BaselineOrigin = baselineOrigin; - GlyphIndices = glyphInfos.Select(info => info.GlyphIndex).ToArray(); + GlyphInfos = glyphInfos; Bounds = new Rect(new Point(0, 0), - new Size(glyphInfos.Sum(info => info.GlyphAdvance), FontRenderingEmSize)); + new Size(glyphInfos.Count, FontRenderingEmSize)); } - public ushort[] GlyphIndices { get; } + public IReadOnlyList GlyphInfos { get; } public void Dispose() { diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index 5331aeab..baa8db20 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,6 +1,5 @@ using System; using System.Linq; -using System.Text; using Avalonia.Media.TextFormatting; using Avalonia.Platform; using NullLib.ConsoleEx; @@ -11,7 +10,9 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - var glyphInfos = Convert(text.Span.ToString()); + Text = text.Span.ToString(); + + var glyphInfos = Convert(Text); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); @@ -20,26 +21,15 @@ public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions optio return shapedBuffer; } - public static GlyphInfo[] Convert(string str) + public string Text { get; set; } + + public static GlyphInfo[] Convert(string text) { - if (!str.IsNormalized(NormalizationForm.FormKC)) - str = str.Normalize(NormalizationForm.FormKC); - - // ReSharper disable once InvertIf - if (str.Any( - c => ConsoleText.IsWideChar(c) && - char.IsLetterOrDigit(c) /*todo: https://github.com/SlimeNull/NullLib.ConsoleEx/issues/2*/)) - { - StringBuilder stringBuilder = new(); - foreach (char c in str) - stringBuilder.Append(ConsoleText.IsWideChar(c) && char.IsLetterOrDigit(c) - ? '?' //todo: support wide characters - : c); - - str = stringBuilder.ToString(); - } - - return str.Select((c, index) => new GlyphInfo(c, index, 1)).ToArray(); + return text.EnumerateRunes() + .Select((rune, index) => + new GlyphInfo((ushort)rune.Value, index, ConsoleText.IsWideChar((char)rune.Value) ? 2 : 1)).ToArray(); } + + } } \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index 521b950f..3d6cdf4d 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -30,15 +30,15 @@ Text="Multiline TextBlock with TextWrapping. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est." /> - - - - - - - - - + + + + + + + + + From e0a2a525ab4534ac60708dea33708092785dce14 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 10:45:45 -0800 Subject: [PATCH 06/48] use TextShaper.Text for render text instead of trying to render from glyphinfos --- .../Drawing/DrawingContextImpl.cs | 2 +- src/Consolonia.Core/Text/TextShaper.cs | 19 +++++++------------ .../Controls/Helpers/SymbolsControl.cs | 4 +++- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 3cf0998a..964fd0de 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -208,7 +208,7 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) return; } - var shapedBuffer = glyphRunImpl.GlyphInfos as ShapedBuffer; + var shapedBuffer = (ShapedBuffer)glyphRunImpl.GlyphInfos; var text = shapedBuffer.Text.ToString(); DrawStringInternal(foreground, text, glyphRun.GlyphTypeface); } diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index baa8db20..a5271c87 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Controls.Documents; using Avalonia.Media.TextFormatting; using Avalonia.Platform; using NullLib.ConsoleEx; @@ -10,25 +12,18 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - Text = text.Span.ToString(); - - var glyphInfos = Convert(Text); + var glyphInfos = text.Span.ToString().EnumerateRunes() + .Select((rune, index) => + new GlyphInfo((ushort)rune.Value, index, ConsoleText.IsWideChar((char)rune.Value) ? 2 : 1)).ToArray(); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); - for (int i = 0; i < shapedBuffer.Length; i++) shapedBuffer[i] = glyphInfos[i]; + for (int i = 0; i < shapedBuffer.Length; i++) + shapedBuffer[i] = glyphInfos[i]; return shapedBuffer; } - public string Text { get; set; } - - public static GlyphInfo[] Convert(string text) - { - return text.EnumerateRunes() - .Select((rune, index) => - new GlyphInfo((ushort)rune.Value, index, ConsoleText.IsWideChar((char)rune.Value) ? 2 : 1)).ToArray(); - } } diff --git a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs index cf9b73c0..50f73493 100644 --- a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs +++ b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs @@ -6,6 +6,7 @@ using Avalonia.Controls; using Avalonia.Data; using Avalonia.Media; +using Avalonia.Media.TextFormatting; using Consolonia.Core.Text; using TextShaper = Consolonia.Core.Text.TextShaper; @@ -69,10 +70,11 @@ public string Text { _text = value; + var glyphs = new TextShaper().ShapeText(value.AsMemory(), new TextShaperOptions(new GlyphTypeface(), 1)); _shapedText = new GlyphRun(new GlyphTypeface(), 1, (_text ?? string.Empty).AsMemory(), - TextShaper.Convert(_text ?? string.Empty).ToImmutableArray(), + glyphs, default(Point)); } } From 352bb67f5b6a07a6793b0ab97c89ccb11dd9a2d2 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 20:16:51 -0800 Subject: [PATCH 07/48] FINALLY I have emoticons rendering correctly * Added code to measure the actual width taken for emoticons and ligatures * added GlyphMetric cache so we only have to compute glyph widths once. * Changed ISymbol to have width * Adjust positiong appropriately * Fixed bug so we are correctly hadnling \r in multiline textblocks * Added Unicode library to identify emoticons and such. * Changed DrawString to compute widths to deal with fact emoticons are wide * Changed RenderTarget to use symbol widths to properly skip overlapped cells. --- src/Consolonia.Core/Consolonia.Core.csproj | 2 +- src/Consolonia.Core/Drawing/ConsoleBrush.cs | 2 + .../Drawing/DrawingContextImpl.cs | 212 +++++++++++------- .../DrawingBoxSymbol.cs | 84 +++---- .../PixelBufferImplementation/ISymbol.cs | 9 +- .../PixelBufferImplementation/Pixel.cs | 8 +- .../PixelBufferImplementation/PixelBuffer.cs | 3 + .../PixelForeground.cs | 2 +- .../PixelBufferImplementation/SimpleSymbol.cs | 26 ++- src/Consolonia.Core/Drawing/RenderTarget.cs | 71 +++--- .../InputLessDefaultNetConsole.cs | 23 +- src/Consolonia.Core/Text/GlyphTypeface.cs | 13 +- src/Consolonia.Core/Text/TextShaper.cs | 12 +- .../GalleryViews/GalleryTextBlock.axaml | 12 +- .../Consolonia.TestsCore/UnitTestConsole.cs | 6 +- 15 files changed, 274 insertions(+), 211 deletions(-) diff --git a/src/Consolonia.Core/Consolonia.Core.csproj b/src/Consolonia.Core/Consolonia.Core.csproj index 544a42c0..48ba0dae 100644 --- a/src/Consolonia.Core/Consolonia.Core.csproj +++ b/src/Consolonia.Core/Consolonia.Core.csproj @@ -6,8 +6,8 @@ - + diff --git a/src/Consolonia.Core/Drawing/ConsoleBrush.cs b/src/Consolonia.Core/Drawing/ConsoleBrush.cs index 5bf2a3f3..ce4e7b02 100644 --- a/src/Consolonia.Core/Drawing/ConsoleBrush.cs +++ b/src/Consolonia.Core/Drawing/ConsoleBrush.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Avalonia; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; @@ -6,6 +7,7 @@ namespace Consolonia.Core.Drawing { + [DebuggerDisplay("{Color} [{Mode}]")] public class ConsoleBrush : AvaloniaObject, IImmutableBrush { public static readonly StyledProperty ColorProperty = diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 964fd0de..8eaa7097 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Avalonia; @@ -11,6 +12,7 @@ using Consolonia.Core.Infrastructure; using Consolonia.Core.InternalHelpers; using Consolonia.Core.Text; +using NeoSmart.Unicode; using SkiaSharp; namespace Consolonia.Core.Drawing @@ -26,6 +28,10 @@ internal class DrawingContextImpl : IDrawingContextImpl public const int UnderlineThickness = 10; public const int StrikethroughThickness = 11; + + // this computes once for a glyph it's width (this is only for emoticons and ligatures) + private readonly static Dictionary GlyphMetrics = new(); + private readonly Stack _clipStack = new(100); private readonly ConsoleWindow _consoleWindow; private readonly PixelBuffer _pixelBuffer; @@ -71,37 +77,37 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect new SKPaint { FilterQuality = SKFilterQuality.Medium }); for (int y = 0; y < bitmap.Info.Height; y += 2) - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // NOTE: we divide by 2 because we are working with quad pixels, - // // the bitmap has twice the horizontal and twice the vertical of the target rect. - int px = (int)targetRect.TopLeft.X + x / 2; - int py = (int)targetRect.TopLeft.Y + y / 2; - - // get the quad pixel the bitmap - var quadColors = new[] + for (int x = 0; x < bitmap.Info.Width; x += 2) { + // NOTE: we divide by 2 because we are working with quad pixels, + // // the bitmap has twice the horizontal and twice the vertical of the target rect. + int px = (int)targetRect.TopLeft.X + x / 2; + int py = (int)targetRect.TopLeft.Y + y / 2; + + // get the quad pixel the bitmap + var quadColors = new[] + { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represet the 4 pixels - char quadPixel = GetQuadPixelCharacter(quadColors); + // map it to a single char to represet the 4 pixels + char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); - Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); + // get the combined colors for the quad pixel + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); + Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel( - new PixelForeground(new SimpleSymbol(new Rune(quadPixel)), color: foreground), - new PixelBackground(background)); - CurrentClip.ExecuteWithClipping(new Point(px, py), - () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); - }); - } + var imagePixel = new Pixel( + new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), + new PixelBackground(background)); + CurrentClip.ExecuteWithClipping(new Point(px, py), + () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); + }); + } } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -152,11 +158,11 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) case VisualBrush: throw new NotImplementedException(); case ISceneBrush sceneBrush: - { - ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); - return; - } + { + ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); + return; + } } Rect r2 = r.TransformToAABB(Transform); @@ -164,19 +170,19 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) double width = r2.Width + (pen?.Thickness ?? 0); double height = r2.Height + (pen?.Thickness ?? 0); for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - { - int px = (int)(r2.TopLeft.X + x); - int py = (int)(r2.TopLeft.Y + y); - - ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); - CurrentClip.ExecuteWithClipping(new Point(px, py), () => + for (int y = 0; y < height; y++) { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), - backgroundBrush); - }); - } + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); + + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), + backgroundBrush); + }); + } } if (pen is null or { Thickness: 0 } @@ -496,57 +502,109 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl return; } - //if (!Transform.IsTranslateOnly()) ConsoloniaPlatform.RaiseNotSupported(15); + // if (!Transform.IsTranslateOnly()) ConsoloniaPlatform.RaiseNotSupported(15); Point whereToDraw = origin.Transform(Transform); int currentXPosition = 0; + int currentYPosition = 0; + + // Process text into collection of glyphs where + // a glyph is either text or a combination of chars which make up an emoji. + List glyphs = new List(); + StringBuilder emoji = new StringBuilder(); + var runes = text.EnumerateRunes(); + Rune lastRune = new Rune(); + + while (runes.MoveNext()) + { + if (lastRune.Value == Codepoints.ZWJ || + lastRune.Value == Codepoints.ORC || + Emoji.IsEmoji(runes.Current.ToString())) + { + emoji.Append(runes.Current); + } + else if (runes.Current.Value == Emoji.ZeroWidthJoiner || + runes.Current.Value == Emoji.ObjectReplacementCharacter || + runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || + runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) + { + emoji.Append(runes.Current); + } + else + { + if (emoji.Length > 0) + { + glyphs.Add(emoji.ToString()); + emoji.Clear(); + } + glyphs.Add(runes.Current.ToString()); + } + lastRune = runes.Current; + } - // Each rune maps to a pixel - foreach (var rune in text.EnumerateRunes()) + // Each glyph maps to a pixel as a starting point. + // Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels + // the x and y are adjusted accodingly. + foreach (var glyph in glyphs) { - Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0)); + Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); Color foregroundColor = consoleBrush.Color; - switch (rune.Value) + switch (glyph) { - case '\t': - { - const int tabSize = 8; - var consolePixel = new Pixel(new Rune(' '), foregroundColor); - for (int j = 0; j < tabSize; j++) + case "\t": { - Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); - CurrentClip.ExecuteWithClipping(newCharacterPoint, () => + const int tabSize = 8; + var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor); + for (int j = 0; j < tabSize; j++) { - _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); + Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); + CurrentClip.ExecuteWithClipping(newCharacterPoint, () => + { + _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); + } + + currentXPosition += tabSize - 1; } - - currentXPosition += tabSize - 1; - } break; - case '\n': - { - /* it's not clear if we need to draw anything. Cursor can be placed at the end of the line - var consolePixel = new Pixel(' ', foregroundColor); - - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel);*/ - } - break; - case '\u200B': - currentXPosition--; + case "\r": + case "\f": + case "\n": + currentXPosition = 0; + currentYPosition++; break; default: - { - var consolePixel = new Pixel(rune, foregroundColor, typeface.Style, typeface.Weight); - CurrentClip.ExecuteWithClipping(characterPoint, () => { - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); - } + ushort width = 1; + if (Emoji.IsEmoji(glyph) || (glyph.Normalize(NormalizationForm.FormKD).Length != 1)) + { + if (!GlyphMetrics.TryGetValue(glyph, out width)) + { + var (originalLeft, originalTop) = Console.GetCursorPosition(); + Console.SetCursorPosition((int)characterPoint.X, (int)characterPoint.Y); + Console.Write(glyph); + var (left, top) = Console.GetCursorPosition(); + width = (ushort)(left - (int)characterPoint.X); + Debug.WriteLine($"{glyph} {width}"); + GlyphMetrics[glyph] = width; + Console.SetCursorPosition(originalLeft, originalTop); + } + } + var symbol = new SimpleSymbol(glyph, width); + var consolePixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); + CurrentClip.ExecuteWithClipping(characterPoint, () => + { + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); + + if (symbol.Width > 1) + currentXPosition += symbol.Width; + else + currentXPosition++; + } break; } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index bdc834ec..14776cf3 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -7,7 +7,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation /// /// https://en.wikipedia.org/wiki/Box-drawing_character /// - [DebuggerDisplay("DrawingBox {Rune}")] + [DebuggerDisplay("DrawingBox {Text}")] public struct DrawingBoxSymbol : ISymbol { // all 0bXXXX_0000 are special values @@ -21,7 +21,9 @@ public DrawingBoxSymbol(byte upRightDownLeft) private byte _upRightDownLeft; - public Rune Rune => new(GetBoxSymbol()); + public string Text => GetBoxSymbol().ToString(); + + public ushort Width { get; } = 1; /// /// https://en.wikipedia.org/wiki/Code_page_437 @@ -54,46 +56,46 @@ private char GetBoxSymbol() return horizontal ? '╪' : '╫'; default: - { - return _upRightDownLeft switch { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + return _upRightDownLeft switch + { + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs index 03d11c9c..3e5c0888 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs @@ -5,9 +5,14 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public interface ISymbol { /// - /// The rune for the symbol + /// The text for the symbol (This can be single character or unicode encoding for Emoji's and the like) /// - Rune Rune { get; } + string Text { get; } + + /// + /// The number of characters the symbol takes up + /// + ushort Width { get; } bool IsWhiteSpace(); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index f093689f..73ceddf5 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -8,7 +8,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { - [DebuggerDisplay("'{Foreground.Symbol.Rune}' [{Foreground.Color}, {Background.Color}]")] + [DebuggerDisplay("'{Foreground.Symbol.Text}' [{Foreground.Color}, {Background.Color}]")] public readonly struct Pixel { public PixelForeground Foreground { get; } @@ -17,12 +17,6 @@ public readonly struct Pixel public bool IsCaret { get; } - public Pixel(Rune rune, Color foregroundColor, FontStyle style = FontStyle.Normal, - FontWeight weight = FontWeight.Normal) : - this(new SimpleSymbol(rune), foregroundColor, style, weight) - { - } - public Pixel(ISymbol symbol, Color foregroundColor, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal, TextDecorationCollection textDecorations = null) : this( new PixelForeground(symbol, weight, style, textDecorations, foregroundColor), diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index b11eac64..ded039e0 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -76,11 +76,14 @@ public void Set(PixelBufferCoordinate point, Func public void SetCaretPosition(PixelBufferCoordinate point) { + // clear old caret position by merging in IsCaret = false Pixel oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background, false); _caretPosition = point; + + // set new caret position by merging in IsCaret = true Pixel newCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; _buffer[_caretPosition.X, _caretPosition.Y] = new Pixel(newCaretPixel.Foreground, newCaretPixel.Background, true); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index 0a5970ca..35ff9d39 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -4,7 +4,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { - [DebuggerDisplay("'{Symbol.Rune}' [{Color}]")] + [DebuggerDisplay("'{Symbol.Text}' [{Color}]")] public readonly struct PixelForeground { public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 5f35d4c5..5531eac1 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,31 +1,43 @@ +using System; using System.Diagnostics; +using System.Linq; using System.Text; namespace Consolonia.Core.Drawing.PixelBufferImplementation { - [DebuggerDisplay("'{Rune}'")] + [DebuggerDisplay("'{Text}'")] public readonly struct SimpleSymbol : ISymbol { public SimpleSymbol() { - Rune = new Rune('\0'); + Text = string.Empty; + Width = 1; } - public SimpleSymbol(Rune rune) + public SimpleSymbol(char character) { - Rune = rune; + Text = character.ToString(); + Width = 1; } - public Rune Rune { get; } = new('\0'); + public SimpleSymbol(string text, ushort width) + { + Text = text; + Width = width; + } + + public string Text { get; } = string.Empty; + + public ushort Width { get; } public bool IsWhiteSpace() { - return Rune.IsWhiteSpace(Rune); + return string.IsNullOrWhiteSpace(Text); } public ISymbol Blend(ref ISymbol symbolAbove) { - return symbolAbove.Rune.Value != '\0' ? symbolAbove : this; + return !String.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 2a6e7740..b8f66e62 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -21,7 +21,7 @@ internal class RenderTarget : IDrawingContextLayerImpl private PixelBuffer _bufferBuffer; private (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, Rune rune)?[,] _cache; + textDecorations, string text)?[,] _cache; internal RenderTarget(ConsoleWindow consoleWindow) { @@ -98,7 +98,7 @@ private void InitializeCache(ushort width, ushort height) { _cache = new (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, Rune rune)?[width, height]; + textDecorations, string text)?[width, height]; } private void RenderToDevice() @@ -111,42 +111,45 @@ private void RenderToDevice() var flushingBuffer = new FlushingBuffer(_console); for (ushort y = 0; y < pixelBuffer.Height; y++) - for (ushort x = 0; x < pixelBuffer.Width; x++) { - Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; - - if (pixel.IsCaret) + for (ushort x = 0; x < pixelBuffer.Width; ) { - if (caretPosition != null) - throw new InvalidOperationException("Caret is already shown"); - caretPosition = new PixelBufferCoordinate(x, y); - } + Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; + + if (pixel.IsCaret) + { + if (caretPosition != null) + throw new InvalidOperationException("Caret is already shown"); + caretPosition = new PixelBufferCoordinate(x, y); + } - /* todo: There is not IWindowImpl.Invalidate anymore. - if (!_consoleWindow.InvalidatedRects.Any(rect => - rect.ContainsExclusive(new Point(x, y)))) continue;*/ - if (pixel.Background.Mode != PixelBackgroundMode.Colored) - throw new InvalidOperationException( - "All pixels in the buffer must have exact console color before rendering"); + /* todo: There is not IWindowImpl.Invalidate anymore. + if (!_consoleWindow.InvalidatedRects.Any(rect => + rect.ContainsExclusive(new Point(x, y)))) continue;*/ + if (pixel.Background.Mode != PixelBackgroundMode.Colored) + throw new InvalidOperationException( + "All pixels in the buffer must have exact console color before rendering"); - if (pixel.Foreground.Symbol is null) // not using 'when' as it swallows the exceptions - // buffer re-initialized after resizing - pixel = new Pixel(new PixelForeground(new SimpleSymbol(new Rune('░'))), - new PixelBackground(PixelBackgroundMode.Colored)); - (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, Rune rune) - pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, - pixel.Foreground.Style, pixel.Foreground.TextDecorations, - pixel.Foreground.Symbol.Rune); + (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection + textDecorations, string text) + pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, + pixel.Foreground.Style, pixel.Foreground.TextDecorations, + pixel.Foreground.Symbol.Text); - //todo: indexOutOfRange during resize - if (_cache[x, y] == pixelSpread) - continue; + //todo: indexOutOfRange during resize + if (_cache[x, y] == pixelSpread) + { + x++; + continue; + } - _cache[x, y] = pixelSpread; + _cache[x, y] = pixelSpread; - flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); + flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); + + x += pixel.Foreground.Symbol.Width; + } } flushingBuffer.Flush(); @@ -204,10 +207,10 @@ public void WritePixel( _lastBufferPointStart = _currentBufferPoint = bufferPoint; } - Rune rune = pixel.Foreground.Symbol.Rune; - if (Rune.IsControl(rune)) /*|| character is '保' or '哥'*/ - rune = new Rune(' '); // some terminals do not print \0 - _stringBuilder.Append(rune); + if (pixel.Foreground.Symbol.Text.Length == 0) + _stringBuilder.Append(' '); + else + _stringBuilder.Append(pixel.Foreground.Symbol.Text); _currentBufferPoint = _currentBufferPoint.WithXpp(); } diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 0fc0d2ba..47273469 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,6 +9,7 @@ using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Text; +using NeoSmart.Unicode; namespace Consolonia.Core.Infrastructure { @@ -93,26 +95,19 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light _ => foreground })); + if (Emoji.IsEmoji(str)) + Debug.WriteLine(str); + sb.Append(str); sb.Append(ConsoleUtils.Reset); Console.Write(sb.ToString()); - // if we have complex unicode runes then we need to calculate the position manually, EnumerateRunes is - // not enough as some runes (like '🥰') are a single rune, but are emitted as two console characters - if (str.EnumerateRunes().Count() != str.Length) - { - (int left, int top) = Console.GetCursorPosition(); - _headBufferPoint = new PixelBufferCoordinate((ushort)left, (ushort)top); - } + if (_headBufferPoint.X < Size.Width - str.Length) + _headBufferPoint = + new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); else - { - if (_headBufferPoint.X < Size.Width - str.Length) - _headBufferPoint = - new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); - else - _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); - } + _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); } public event Action Resized; diff --git a/src/Consolonia.Core/Text/GlyphTypeface.cs b/src/Consolonia.Core/Text/GlyphTypeface.cs index 3434e312..a3c78bb2 100644 --- a/src/Consolonia.Core/Text/GlyphTypeface.cs +++ b/src/Consolonia.Core/Text/GlyphTypeface.cs @@ -26,15 +26,16 @@ public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) public ushort GetGlyph(uint codepoint) { - checked - { - return (ushort)codepoint; - } + // we just return placeholder chars as we are pushing the raw + // text to the console + return (ushort)'X'; } public bool TryGetGlyph(uint codepoint, out ushort glyph) { - glyph = (ushort)codepoint; + // we just return placeholder chars as we are pushing the raw + // text to the console + glyph = (ushort)'X'; return true; } @@ -42,7 +43,7 @@ public ushort[] GetGlyphs(ReadOnlySpan codepoints) { checked { - return codepoints.ToArray().Select(u => (ushort)u).ToArray(); + return codepoints.ToArray().Select(u => (ushort)'X').ToArray(); } } diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index a5271c87..acdd077c 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,30 +1,26 @@ using System; using System.Linq; -using System.Runtime.InteropServices; -using Avalonia.Controls.Documents; using Avalonia.Media.TextFormatting; +using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; -using NullLib.ConsoleEx; namespace Consolonia.Core.Text { + public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { var glyphInfos = text.Span.ToString().EnumerateRunes() .Select((rune, index) => - new GlyphInfo((ushort)rune.Value, index, ConsoleText.IsWideChar((char)rune.Value) ? 2 : 1)).ToArray(); + new GlyphInfo((ushort)'X', index, 1)).ToArray(); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); - for (int i = 0; i < shapedBuffer.Length; i++) + for (int i = 0; i < shapedBuffer.Length; i++) shapedBuffer[i] = glyphInfos[i]; return shapedBuffer; } - - - } } \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index 3d6cdf4d..ad25d1b9 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -29,16 +29,8 @@ - - - - - - - - - + Text="Special characters: Élève naïve: “𝔉𝔞𝔫𝔠𝔶” ligatures like ff, fi and fl numbers ½, ¼; symbols like @, #, and €; emoji 😊 and math symbols Ω, ∑, √…"/> + diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 9304e66b..50d7a573 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -64,7 +64,7 @@ void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color f PixelBuffer.Set(new PixelBufferCoordinate((ushort)(x + i), y), _ => // ReSharper disable once AccessToModifiedClosure we are sure about inline execution new Pixel( - new PixelForeground(new SimpleSymbol(rune), color: foreground, style: style, weight: weight, + new PixelForeground(new SimpleSymbol(rune.ToString(), 1), color: foreground, style: style, weight: weight, textDecorations: textDecorations), new PixelBackground(PixelBackgroundMode.Colored, background))); i++; @@ -148,9 +148,9 @@ internal string PrintBuffer() if (i == PixelBuffer.Width - 1 && j == PixelBuffer.Height - 1) break; Pixel pixel = PixelBuffer[new PixelBufferCoordinate(i, j)]; - Rune rune = pixel.IsCaret ? new Rune('Ꮖ') : pixel.Foreground.Symbol.Rune; + string text = pixel.IsCaret ? "Ꮖ" : pixel.Foreground.Symbol.Text; //todo: check why cursor is not drawing - stringBuilder.Append(rune); + stringBuilder.Append(text); } stringBuilder.AppendLine(); From adeb5eac80f687e5d32ad42a329c960b62590f3a Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 20:19:09 -0800 Subject: [PATCH 08/48] added scroll bars to textlbock gallery page --- .../GalleryViews/GalleryTextBlock.axaml | 73 ++++++++++--------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index ad25d1b9..10bd8447 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -9,43 +9,46 @@ - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + + \ No newline at end of file From c174f9bb7069b2acaa765a04c05b90d463a158fb Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 20:27:19 -0800 Subject: [PATCH 09/48] update sample to show that CJK wide chars work --- .../Gallery/GalleryViews/GalleryTextBlock.axaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index 10bd8447..b19ce060 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -33,6 +33,12 @@ + + + + + + From 479460bb15e408c44f15e247e509c286fdc03fc5 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 20:38:39 -0800 Subject: [PATCH 10/48] removed unused using statements --- .../Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs | 1 - .../Drawing/PixelBufferImplementation/ISymbol.cs | 2 -- src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs | 1 - .../Drawing/PixelBufferImplementation/SimpleSymbol.cs | 2 -- src/Consolonia.Core/Text/GlyphRunImpl.cs | 2 -- src/Consolonia.Core/Text/TextShaper.cs | 1 - 6 files changed, 9 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 14776cf3..1596674c 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Text; namespace Consolonia.Core.Drawing.PixelBufferImplementation { diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs index 3e5c0888..95a02937 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace Consolonia.Core.Drawing.PixelBufferImplementation { public interface ISymbol diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 73ceddf5..01528ef7 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Text; using Avalonia.Media; // ReSharper disable MemberCanBePrivate.Global diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 5531eac1..d4b989f6 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,7 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; -using System.Text; namespace Consolonia.Core.Drawing.PixelBufferImplementation { diff --git a/src/Consolonia.Core/Text/GlyphRunImpl.cs b/src/Consolonia.Core/Text/GlyphRunImpl.cs index 357bd1ac..56ea4b47 100644 --- a/src/Consolonia.Core/Text/GlyphRunImpl.cs +++ b/src/Consolonia.Core/Text/GlyphRunImpl.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; using Avalonia; using Avalonia.Media; using Avalonia.Media.TextFormatting; diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index acdd077c..943fd5bf 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Avalonia.Media.TextFormatting; -using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; namespace Consolonia.Core.Text From cc417f8e7461d143b99c31c4615242787b4c9702 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Thu, 7 Nov 2024 21:09:59 -0800 Subject: [PATCH 11/48] update unit tests for textblock --- .../Drawing/DrawingContextImpl.cs | 27 +++++++++++++------ .../Consolonia.GalleryTests/TextBlockTests.cs | 12 ++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 8eaa7097..5204feff 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using Avalonia; @@ -582,14 +583,24 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl { if (!GlyphMetrics.TryGetValue(glyph, out width)) { - var (originalLeft, originalTop) = Console.GetCursorPosition(); - Console.SetCursorPosition((int)characterPoint.X, (int)characterPoint.Y); - Console.Write(glyph); - var (left, top) = Console.GetCursorPosition(); - width = (ushort)(left - (int)characterPoint.X); - Debug.WriteLine($"{glyph} {width}"); - GlyphMetrics[glyph] = width; - Console.SetCursorPosition(originalLeft, originalTop); + try + { + + var (originalLeft, originalTop) = Console.GetCursorPosition(); + Console.SetCursorPosition((int)characterPoint.X, (int)characterPoint.Y); + Console.Write(glyph); + var (left, top) = Console.GetCursorPosition(); + width = (ushort)(left - (int)characterPoint.X); + Debug.WriteLine($"{glyph} {width}"); + GlyphMetrics[glyph] = width; + Console.SetCursorPosition(originalLeft, originalTop); + } + catch (IOException) + { + // IOException happens when running unit tests TODO: We should emulate this better + width = 1; + GlyphMetrics[glyph] = width; + } } } var symbol = new SimpleSymbol(glyph, width); diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index d1beb95e..51c8c3d9 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -1,4 +1,7 @@ +using System.Drawing; using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; using Consolonia.GalleryTests.Base; using Consolonia.TestsCore; using NUnit.Framework; @@ -18,9 +21,12 @@ protected override Task PerformSingleTest() "Right aligned text│", // multiline - "│Lorem ipsum dolor sit amet, consectetur adipiscing elit.│", - @"│Vivamus magna. Cras in mi at felis aliquet congue. Ut a │", - @"│est eget ligula molestie gravida. Curabitur massa. Donec│"); + "│Lorem ipsum dolor sit amet, consectetur adipiscing", + "│elit. Vivamus magna. Cras in mi at felis aliquet", + "│congue. Ut a est eget ligula molestie gravida.", + // special chars, emojis, etc. + "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞","🎵", "“𝔉𝔞𝔫𝔠𝔶”","ff","fi","½"); + } } } \ No newline at end of file From 7e3624783a68983bd98b593cbc6785e31462d677 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 07:31:36 -0800 Subject: [PATCH 12/48] lint feedback, adopt WcWidth for accurate calculation of char width moved width calculation into SimpleSymbol --- src/Consolonia.Core/Consolonia.Core.csproj | 1 + .../Drawing/DrawingContextImpl.cs | 32 +------ .../PixelBufferImplementation/PixelBuffer.cs | 2 +- .../PixelBufferImplementation/SimpleSymbol.cs | 39 +++++++-- src/Consolonia.Core/Helpers/Extensions.cs | 13 +++ .../InputLessDefaultNetConsole.cs | 3 - src/Consolonia.Core/Text/GlyphTypeface.cs | 9 +- src/Consolonia.Core/Text/TextShaper.cs | 3 +- .../GalleryViews/GalleryTextBlock.axaml | 84 +++++++++---------- .../Controls/Helpers/SymbolsControl.cs | 1 - .../Consolonia.GalleryTests/TextBlockTests.cs | 11 ++- .../Consolonia.TestsCore/UnitTestConsole.cs | 2 +- 12 files changed, 101 insertions(+), 99 deletions(-) diff --git a/src/Consolonia.Core/Consolonia.Core.csproj b/src/Consolonia.Core/Consolonia.Core.csproj index 48ba0dae..00b7142a 100644 --- a/src/Consolonia.Core/Consolonia.Core.csproj +++ b/src/Consolonia.Core/Consolonia.Core.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 5204feff..170725b3 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; using System.Text; using Avalonia; @@ -30,9 +28,6 @@ internal class DrawingContextImpl : IDrawingContextImpl public const int UnderlineThickness = 10; public const int StrikethroughThickness = 11; - // this computes once for a glyph it's width (this is only for emoticons and ligatures) - private readonly static Dictionary GlyphMetrics = new(); - private readonly Stack _clipStack = new(100); private readonly ConsoleWindow _consoleWindow; private readonly PixelBuffer _pixelBuffer; @@ -578,32 +573,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl break; default: { - ushort width = 1; - if (Emoji.IsEmoji(glyph) || (glyph.Normalize(NormalizationForm.FormKD).Length != 1)) - { - if (!GlyphMetrics.TryGetValue(glyph, out width)) - { - try - { - - var (originalLeft, originalTop) = Console.GetCursorPosition(); - Console.SetCursorPosition((int)characterPoint.X, (int)characterPoint.Y); - Console.Write(glyph); - var (left, top) = Console.GetCursorPosition(); - width = (ushort)(left - (int)characterPoint.X); - Debug.WriteLine($"{glyph} {width}"); - GlyphMetrics[glyph] = width; - Console.SetCursorPosition(originalLeft, originalTop); - } - catch (IOException) - { - // IOException happens when running unit tests TODO: We should emulate this better - width = 1; - GlyphMetrics[glyph] = width; - } - } - } - var symbol = new SimpleSymbol(glyph, width); + var symbol = new SimpleSymbol(glyph); var consolePixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); CurrentClip.ExecuteWithClipping(characterPoint, () => { diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index ded039e0..1e3a2888 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -79,7 +79,7 @@ public void SetCaretPosition(PixelBufferCoordinate point) // clear old caret position by merging in IsCaret = false Pixel oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; _buffer[_caretPosition.X, _caretPosition.Y] = - new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background, false); + new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background /*, false*/); _caretPosition = point; diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index d4b989f6..7642b4e4 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,5 +1,8 @@ using System; using System.Diagnostics; +using System.Text; +using NeoSmart.Unicode; +using Wcwidth; namespace Consolonia.Core.Drawing.PixelBufferImplementation { @@ -8,20 +11,26 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { public SimpleSymbol() { + // we use String.Empty to represent an empty symbol. It still takes up space, but it's invisible Text = string.Empty; Width = 1; } public SimpleSymbol(char character) + :this(character.ToString()) { - Text = character.ToString(); - Width = 1; } - public SimpleSymbol(string text, ushort width) + public SimpleSymbol(string glyph) { - Text = text; - Width = width; + Text = glyph; + Width = MeasureGlyph(Text); + } + + public SimpleSymbol(Rune rune) + { + Text = rune.ToString(); + Width = MeasureGlyph(Text); } public string Text { get; } = string.Empty; @@ -37,5 +46,25 @@ public ISymbol Blend(ref ISymbol symbolAbove) { return !String.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; } + + + private static ushort MeasureGlyph(string glyph) + { + ushort width = 0; + ushort lastWidth = 0; + foreach (var rune in glyph.EnumerateRunes()) + { + var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); + if (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter) + width -= lastWidth; + else + width += runeWidth; + + if (runeWidth > 0) + lastWidth = runeWidth; + } + + return width; + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 5c2f4f27..0519cafe 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -1,6 +1,8 @@ using System; using Avalonia; using Avalonia.Reactive; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Infrastructure; namespace Consolonia.Core.Helpers { @@ -12,5 +14,16 @@ public static void SubscribeAction( { observable.Subscribe(new AnonymousObserver>(action)); } + + public static void Print(this IConsole console, PixelBufferCoordinate point, Pixel pixel) + { + console.Print(point, + pixel.Background.Color, + pixel.Foreground.Color, + pixel.Foreground.Style, + pixel.Foreground.Weight, + pixel.Foreground.TextDecorations, + pixel.Foreground.Symbol.Text); + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 47273469..5274bd10 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -95,9 +95,6 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light _ => foreground })); - if (Emoji.IsEmoji(str)) - Debug.WriteLine(str); - sb.Append(str); sb.Append(ConsoleUtils.Reset); diff --git a/src/Consolonia.Core/Text/GlyphTypeface.cs b/src/Consolonia.Core/Text/GlyphTypeface.cs index a3c78bb2..e77657f9 100644 --- a/src/Consolonia.Core/Text/GlyphTypeface.cs +++ b/src/Consolonia.Core/Text/GlyphTypeface.cs @@ -28,23 +28,20 @@ public ushort GetGlyph(uint codepoint) { // we just return placeholder chars as we are pushing the raw // text to the console - return (ushort)'X'; + return 'X'; } public bool TryGetGlyph(uint codepoint, out ushort glyph) { // we just return placeholder chars as we are pushing the raw // text to the console - glyph = (ushort)'X'; + glyph = 'X'; return true; } public ushort[] GetGlyphs(ReadOnlySpan codepoints) { - checked - { - return codepoints.ToArray().Select(u => (ushort)'X').ToArray(); - } + return codepoints.ToArray().Select(_ => (ushort)'X').ToArray(); } public int GetGlyphAdvance(ushort glyph) diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index 943fd5bf..b554c00a 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -11,8 +11,7 @@ public class TextShaper : ITextShaperImpl public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { var glyphInfos = text.Span.ToString().EnumerateRunes() - .Select((rune, index) => - new GlyphInfo((ushort)'X', index, 1)).ToArray(); + .Select((_, index) => new GlyphInfo('X', index, 1)).ToArray(); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml index b19ce060..dedfcae2 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBlock.axaml @@ -10,51 +10,49 @@ - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs index 50f73493..c36e05c9 100644 --- a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs +++ b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Immutable; using System.Globalization; using System.Linq; using Avalonia; diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index 51c8c3d9..2bf64905 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -1,10 +1,10 @@ -using System.Drawing; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Media; using Consolonia.GalleryTests.Base; using Consolonia.TestsCore; using NUnit.Framework; +using NUnit.Framework.Internal; namespace Consolonia.GalleryTests { @@ -21,11 +21,10 @@ protected override Task PerformSingleTest() "Right aligned text│", // multiline - "│Lorem ipsum dolor sit amet, consectetur adipiscing", - "│elit. Vivamus magna. Cras in mi at felis aliquet", - "│congue. Ut a est eget ligula molestie gravida.", - // special chars, emojis, etc. - "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞","🎵", "“𝔉𝔞𝔫𝔠𝔶”","ff","fi","½"); + "│Vivamus magna. Cras in mi at felis aliquet congue. Ut a │", + "│est eget ligula molestie gravida. Curabitur massa. Donec│", + // special chars, emojis, etc. + "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞", "🎵", "“𝔉𝔞𝔫𝔠𝔶”", "ff", "fi", "½"); } } diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 50d7a573..2a6952b9 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -64,7 +64,7 @@ void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color f PixelBuffer.Set(new PixelBufferCoordinate((ushort)(x + i), y), _ => // ReSharper disable once AccessToModifiedClosure we are sure about inline execution new Pixel( - new PixelForeground(new SimpleSymbol(rune.ToString(), 1), color: foreground, style: style, weight: weight, + new PixelForeground(new SimpleSymbol(rune), color: foreground, style: style, weight: weight, textDecorations: textDecorations), new PixelBackground(PixelBackgroundMode.Colored, background))); i++; From 88ebb08c8592d45071cb183ae375f8fad770ea7a Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 07:41:07 -0800 Subject: [PATCH 13/48] properly calculate GlyphInfos in textshaper --- .../Drawing/DrawingContextImpl.cs | 37 +---------- .../PixelBufferImplementation/SimpleSymbol.cs | 25 +------- src/Consolonia.Core/Helpers/Extensions.cs | 64 +++++++++++++++++++ src/Consolonia.Core/Text/TextShaper.cs | 6 +- 4 files changed, 73 insertions(+), 59 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 170725b3..8d21772a 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -8,6 +8,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Helpers; using Consolonia.Core.Infrastructure; using Consolonia.Core.InternalHelpers; using Consolonia.Core.Text; @@ -504,44 +505,10 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl int currentXPosition = 0; int currentYPosition = 0; - // Process text into collection of glyphs where - // a glyph is either text or a combination of chars which make up an emoji. - List glyphs = new List(); - StringBuilder emoji = new StringBuilder(); - var runes = text.EnumerateRunes(); - Rune lastRune = new Rune(); - - while (runes.MoveNext()) - { - if (lastRune.Value == Codepoints.ZWJ || - lastRune.Value == Codepoints.ORC || - Emoji.IsEmoji(runes.Current.ToString())) - { - emoji.Append(runes.Current); - } - else if (runes.Current.Value == Emoji.ZeroWidthJoiner || - runes.Current.Value == Emoji.ObjectReplacementCharacter || - runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || - runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) - { - emoji.Append(runes.Current); - } - else - { - if (emoji.Length > 0) - { - glyphs.Add(emoji.ToString()); - emoji.Clear(); - } - glyphs.Add(runes.Current.ToString()); - } - lastRune = runes.Current; - } - // Each glyph maps to a pixel as a starting point. // Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels // the x and y are adjusted accodingly. - foreach (var glyph in glyphs) + foreach (var glyph in text.GetGlyphs()) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); Color foregroundColor = consoleBrush.Color; diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 7642b4e4..eff282ad 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using Consolonia.Core.Helpers; using NeoSmart.Unicode; using Wcwidth; @@ -24,13 +25,13 @@ public SimpleSymbol(char character) public SimpleSymbol(string glyph) { Text = glyph; - Width = MeasureGlyph(Text); + Width = Text.MeasureGlyph(); } public SimpleSymbol(Rune rune) { Text = rune.ToString(); - Width = MeasureGlyph(Text); + Width = Text.MeasureGlyph(); } public string Text { get; } = string.Empty; @@ -46,25 +47,5 @@ public ISymbol Blend(ref ISymbol symbolAbove) { return !String.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; } - - - private static ushort MeasureGlyph(string glyph) - { - ushort width = 0; - ushort lastWidth = 0; - foreach (var rune in glyph.EnumerateRunes()) - { - var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); - if (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter) - width -= lastWidth; - else - width += runeWidth; - - if (runeWidth > 0) - lastWidth = runeWidth; - } - - return width; - } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 0519cafe..a74e7eec 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; +using System.Text; using Avalonia; using Avalonia.Reactive; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; +using NeoSmart.Unicode; +using Wcwidth; namespace Consolonia.Core.Helpers { @@ -25,5 +29,65 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix pixel.Foreground.TextDecorations, pixel.Foreground.Symbol.Text); } + + /// + /// Process text into collection of glyphs where a glyph is either text or a combination of chars which make up an emoji. + /// + /// + /// + public static IList GetGlyphs(this string text) + { + List glyphs = new List(); + StringBuilder emoji = new StringBuilder(); + var runes = text.EnumerateRunes(); + Rune lastRune = new Rune(); + + while (runes.MoveNext()) + { + if (lastRune.Value == Codepoints.ZWJ || + lastRune.Value == Codepoints.ORC || + Emoji.IsEmoji(runes.Current.ToString())) + { + emoji.Append(runes.Current); + } + else if (runes.Current.Value == Emoji.ZeroWidthJoiner || + runes.Current.Value == Emoji.ObjectReplacementCharacter || + runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || + runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) + { + emoji.Append(runes.Current); + } + else + { + if (emoji.Length > 0) + { + glyphs.Add(emoji.ToString()); + emoji.Clear(); + } + glyphs.Add(runes.Current.ToString()); + } + lastRune = runes.Current; + } + return glyphs; + } + + public static ushort MeasureGlyph(this string glyph) + { + ushort width = 0; + ushort lastWidth = 0; + foreach (var rune in glyph.EnumerateRunes()) + { + var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); + if (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter) + width -= lastWidth; + else + width += runeWidth; + + if (runeWidth > 0) + lastWidth = runeWidth; + } + + return width; + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index b554c00a..e079fa46 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -2,6 +2,7 @@ using System.Linq; using Avalonia.Media.TextFormatting; using Avalonia.Platform; +using Consolonia.Core.Helpers; namespace Consolonia.Core.Text { @@ -10,8 +11,9 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - var glyphInfos = text.Span.ToString().EnumerateRunes() - .Select((_, index) => new GlyphInfo('X', index, 1)).ToArray(); + var glyphInfos = text.Span.ToString().GetGlyphs() + .Select((glyph, index) => new GlyphInfo('X', index, glyph.MeasureGlyph())) + .ToArray(); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); From 5891381bbe16cd1de705de64d2fd1a47de6d8ac7 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 07:55:41 -0800 Subject: [PATCH 14/48] fix bugs found by coderabbit. Nice job code rabbit! --- .../PixelBufferImplementation/SimpleSymbol.cs | 6 ++---- src/Consolonia.Core/Helpers/Extensions.cs | 13 +++++++++++-- .../Infrastructure/InputLessDefaultNetConsole.cs | 8 ++++---- src/Consolonia.Core/Text/TextShaper.cs | 10 ++++------ src/Tests/Consolonia.GalleryTests/TextBlockTests.cs | 2 -- 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index eff282ad..f54d639c 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -2,8 +2,6 @@ using System.Diagnostics; using System.Text; using Consolonia.Core.Helpers; -using NeoSmart.Unicode; -using Wcwidth; namespace Consolonia.Core.Drawing.PixelBufferImplementation { @@ -25,13 +23,13 @@ public SimpleSymbol(char character) public SimpleSymbol(string glyph) { Text = glyph; - Width = Text.MeasureGlyph(); + Width = Text.MeasureText(); } public SimpleSymbol(Rune rune) { Text = rune.ToString(); - Width = Text.MeasureGlyph(); + Width = Text.MeasureText(); } public string Text { get; } = string.Empty; diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index a74e7eec..38edc490 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -68,14 +68,23 @@ public static IList GetGlyphs(this string text) } lastRune = runes.Current; } + if (emoji.Length > 0) + { + glyphs.Add(emoji.ToString()); + } return glyphs; } - public static ushort MeasureGlyph(this string glyph) + /// + /// Measure text for actual width + /// + /// + /// + public static ushort MeasureText(this string text) { ushort width = 0; ushort lastWidth = 0; - foreach (var rune in glyph.EnumerateRunes()) + foreach (var rune in text.EnumerateRunes()) { var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); if (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 5274bd10..6354bc5b 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -8,8 +7,8 @@ using Avalonia.Input.Raw; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Helpers; using Consolonia.Core.Text; -using NeoSmart.Unicode; namespace Consolonia.Core.Infrastructure { @@ -100,9 +99,10 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light Console.Write(sb.ToString()); - if (_headBufferPoint.X < Size.Width - str.Length) + var textWidth = str.MeasureText(); + if (_headBufferPoint.X < Size.Width - textWidth) _headBufferPoint = - new PixelBufferCoordinate((ushort)(_headBufferPoint.X + str.Length), _headBufferPoint.Y); + new PixelBufferCoordinate((ushort)(_headBufferPoint.X + textWidth), _headBufferPoint.Y); else _headBufferPoint = (PixelBufferCoordinate)((ushort)0, (ushort)(_headBufferPoint.Y + 1)); } diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index e079fa46..3c9bf9aa 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Consolonia.Core.Helpers; @@ -11,15 +10,14 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - var glyphInfos = text.Span.ToString().GetGlyphs() - .Select((glyph, index) => new GlyphInfo('X', index, glyph.MeasureGlyph())) - .ToArray(); + var glyphs = text.Span.ToString().GetGlyphs(); - var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, + var shapedBuffer = new ShapedBuffer(text, glyphs.Count, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); for (int i = 0; i < shapedBuffer.Length; i++) - shapedBuffer[i] = glyphInfos[i]; + shapedBuffer[i] = new GlyphInfo('X', i, glyphs[i].MeasureText()); + return shapedBuffer; } } diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index 2bf64905..c9f458b9 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -1,6 +1,4 @@ using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Media; using Consolonia.GalleryTests.Base; using Consolonia.TestsCore; using NUnit.Framework; From e4e247bbb4020714209a92b1bf317d139c533f1c Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 08:16:49 -0800 Subject: [PATCH 15/48] remove using --- src/Consolonia.Core/Drawing/DrawingContextImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 8d21772a..845d1c1e 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -12,7 +12,6 @@ using Consolonia.Core.Infrastructure; using Consolonia.Core.InternalHelpers; using Consolonia.Core.Text; -using NeoSmart.Unicode; using SkiaSharp; namespace Consolonia.Core.Drawing From 532ac5242f16ab5ee738baf98589ba6e6bfa4744 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 09:35:03 -0800 Subject: [PATCH 16/48] add detection of complex emojii --- src/Consolonia.Core/Helpers/Extensions.cs | 66 ++++++++++++------- .../Infrastructure/DefaultNetConsole.cs | 2 - .../Infrastructure/IConsole.cs | 2 + .../InputLessDefaultNetConsole.cs | 11 ++++ .../WindowsConsole.cs | 2 - 5 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 38edc490..d5298f28 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -21,12 +21,12 @@ public static void SubscribeAction( public static void Print(this IConsole console, PixelBufferCoordinate point, Pixel pixel) { - console.Print(point, - pixel.Background.Color, - pixel.Foreground.Color, - pixel.Foreground.Style, - pixel.Foreground.Weight, - pixel.Foreground.TextDecorations, + console.Print(point, + pixel.Background.Color, + pixel.Foreground.Color, + pixel.Foreground.Style, + pixel.Foreground.Weight, + pixel.Foreground.TextDecorations, pixel.Foreground.Symbol.Text); } @@ -37,6 +37,8 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix /// public static IList GetGlyphs(this string text) { + IConsole console = AvaloniaLocator.Current.GetService(); + List glyphs = new List(); StringBuilder emoji = new StringBuilder(); var runes = text.EnumerateRunes(); @@ -44,29 +46,40 @@ public static IList GetGlyphs(this string text) while (runes.MoveNext()) { - if (lastRune.Value == Codepoints.ZWJ || - lastRune.Value == Codepoints.ORC || - Emoji.IsEmoji(runes.Current.ToString())) - { - emoji.Append(runes.Current); - } - else if (runes.Current.Value == Emoji.ZeroWidthJoiner || - runes.Current.Value == Emoji.ObjectReplacementCharacter || - runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || - runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) + if (console.SupportsComplexEmoji) { - emoji.Append(runes.Current); + if (lastRune.Value == Codepoints.ZWJ || + lastRune.Value == Codepoints.ORC || + Emoji.IsEmoji(runes.Current.ToString())) + { + emoji.Append(runes.Current); + } + else if (runes.Current.Value == Emoji.ZeroWidthJoiner || + runes.Current.Value == Emoji.ObjectReplacementCharacter || + runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || + runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) + { + emoji.Append(runes.Current); + } + else + { + if (emoji.Length > 0) + { + glyphs.Add(emoji.ToString()); + emoji.Clear(); + } + glyphs.Add(runes.Current.ToString()); + } + lastRune = runes.Current; } else { - if (emoji.Length > 0) - { - glyphs.Add(emoji.ToString()); - emoji.Clear(); - } - glyphs.Add(runes.Current.ToString()); + if (runes.Current.Value != Emoji.ZeroWidthJoiner || + runes.Current.Value != Emoji.ObjectReplacementCharacter || + runes.Current.Value != Codepoints.VariationSelectors.EmojiSymbol || + runes.Current.Value != Codepoints.VariationSelectors.TextSymbol) + glyphs.Add(runes.Current.ToString()); } - lastRune = runes.Current; } if (emoji.Length > 0) { @@ -82,12 +95,15 @@ public static IList GetGlyphs(this string text) /// public static ushort MeasureText(this string text) { + IConsole console = AvaloniaLocator.Current.GetService(); + ushort width = 0; ushort lastWidth = 0; foreach (var rune in text.EnumerateRunes()) { var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); - if (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter) + if (console.SupportsComplexEmoji && + (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter)) width -= lastWidth; else width += runeWidth; diff --git a/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs index 5b99fc93..3cd1bf69 100644 --- a/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs @@ -42,8 +42,6 @@ public class DefaultNetConsole : InputLessDefaultNetConsole public DefaultNetConsole() { - Console.OutputEncoding = Encoding.UTF8; - StartSizeCheckTimerAsync(); StartInputReading(); } diff --git a/src/Consolonia.Core/Infrastructure/IConsole.cs b/src/Consolonia.Core/Infrastructure/IConsole.cs index a3a89346..744710bd 100644 --- a/src/Consolonia.Core/Infrastructure/IConsole.cs +++ b/src/Consolonia.Core/Infrastructure/IConsole.cs @@ -31,5 +31,7 @@ void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground event Action FocusEvent; void PauseIO(Task task); void ClearOutput(); + + bool SupportsComplexEmoji { get; } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 6354bc5b..78982238 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -19,7 +19,16 @@ public class InputLessDefaultNetConsole : IConsole protected InputLessDefaultNetConsole() { + // detect emoji composition support + Console.OutputEncoding = Encoding.UTF8; + Console.SetCursorPosition(0, 0); + Console.Write("👨‍👩‍👧‍👦"); + var (left, _) = Console.GetCursorPosition(); + Console.Clear(); + + SupportsComplexEmoji = left == 2; Console.CursorVisible = false; + ActualizeSize(); } @@ -40,6 +49,8 @@ public bool CaretVisible public PixelBufferSize Size { get; private set; } + public bool SupportsComplexEmoji { get; private set; } + public void SetTitle(string title) { Console.Title = title; diff --git a/src/Consolonia.PlatformSupport/WindowsConsole.cs b/src/Consolonia.PlatformSupport/WindowsConsole.cs index ccc4e4d5..8648018a 100644 --- a/src/Consolonia.PlatformSupport/WindowsConsole.cs +++ b/src/Consolonia.PlatformSupport/WindowsConsole.cs @@ -56,8 +56,6 @@ public class Win32Console : InputLessDefaultNetConsole public Win32Console() { - Console.OutputEncoding = Encoding.UTF8; - _windowsConsole = new WindowsConsole(); StartEventLoop(); From b7c1e3553695d24cffb1b999c3b555502aae376e Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 09:43:37 -0800 Subject: [PATCH 17/48] fix calc of glyph --- src/Consolonia.Core/Helpers/Extensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index d5298f28..2d7d611b 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -74,9 +74,9 @@ public static IList GetGlyphs(this string text) } else { - if (runes.Current.Value != Emoji.ZeroWidthJoiner || - runes.Current.Value != Emoji.ObjectReplacementCharacter || - runes.Current.Value != Codepoints.VariationSelectors.EmojiSymbol || + if (runes.Current.Value != Emoji.ZeroWidthJoiner && + runes.Current.Value != Emoji.ObjectReplacementCharacter && + runes.Current.Value != Codepoints.VariationSelectors.EmojiSymbol && runes.Current.Value != Codepoints.VariationSelectors.TextSymbol) glyphs.Add(runes.Current.ToString()); } From 8cab697e724ce0e0007fc33d51068031e3c469f6 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 09:47:22 -0800 Subject: [PATCH 18/48] update unitestconsole --- src/Tests/Consolonia.TestsCore/UnitTestConsole.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 2a6952b9..0bb8d668 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -38,6 +38,8 @@ public void Dispose() PixelBufferSize IConsole.Size => _size; bool IConsole.CaretVisible { get; set; } + public bool SupportsComplexEmoji => true; + public void SetTitle(string title) { Console.WriteLine($"Title changed to {title}"); From 78710e5b96360b57ad3382f1674ab38d54a72384 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 09:54:03 -0800 Subject: [PATCH 19/48] add comment --- src/Consolonia.Core/Infrastructure/IConsole.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Consolonia.Core/Infrastructure/IConsole.cs b/src/Consolonia.Core/Infrastructure/IConsole.cs index 744710bd..a5c1da37 100644 --- a/src/Consolonia.Core/Infrastructure/IConsole.cs +++ b/src/Consolonia.Core/Infrastructure/IConsole.cs @@ -32,6 +32,9 @@ void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground void PauseIO(Task task); void ClearOutput(); + /// + /// This is true if console supports composing multiple emojis together (like: 👨‍👩‍👧‍👦). + /// bool SupportsComplexEmoji { get; } } } \ No newline at end of file From 2be4221e87ed6166032d7bda76f8d60750ba4c95 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 10:00:09 -0800 Subject: [PATCH 20/48] fix --- src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs | 1 - src/Consolonia.PlatformSupport/WindowsConsole.cs | 1 - src/Tests/Consolonia.GalleryTests/TextBlockTests.cs | 3 +++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs index 3cd1bf69..47cf878a 100644 --- a/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/DefaultNetConsole.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Text; using System.Threading; using System.Threading.Tasks; using Avalonia.Input; diff --git a/src/Consolonia.PlatformSupport/WindowsConsole.cs b/src/Consolonia.PlatformSupport/WindowsConsole.cs index 8648018a..d88d7d57 100644 --- a/src/Consolonia.PlatformSupport/WindowsConsole.cs +++ b/src/Consolonia.PlatformSupport/WindowsConsole.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; using Avalonia; using Avalonia.Input; diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index c9f458b9..1aa06da4 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -6,6 +6,9 @@ namespace Consolonia.GalleryTests { + /// + /// Unit test for TextBlock view + /// [TestFixture] internal class TextBlockTests : GalleryTestsBaseBase { From 265eb64d729b1d63cff1308b0a4afddf6b8fd4f9 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 10:03:29 -0800 Subject: [PATCH 21/48] ah, finally figured out the using issue. Visual studio didn't consider the Nunit.Framework.Internal as a unused reference but the linter does. This was driving me crazy! --- src/Tests/Consolonia.GalleryTests/TextBlockTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index 1aa06da4..fe852ab5 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -2,7 +2,6 @@ using Consolonia.GalleryTests.Base; using Consolonia.TestsCore; using NUnit.Framework; -using NUnit.Framework.Internal; namespace Consolonia.GalleryTests { From 8c764111cbb3cd0aa9d78897db5717f9aa24ae7e Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Fri, 8 Nov 2024 10:10:35 -0800 Subject: [PATCH 22/48] argh. Lint is killing me! --- .../Infrastructure/InputLessDefaultNetConsole.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 78982238..3d0a17bb 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -14,6 +14,7 @@ namespace Consolonia.Core.Infrastructure { public class InputLessDefaultNetConsole : IConsole { + private const string TestEmoji = "👨‍👩‍👧‍👦"; private bool _caretVisible; private PixelBufferCoordinate _headBufferPoint; @@ -22,7 +23,9 @@ protected InputLessDefaultNetConsole() // detect emoji composition support Console.OutputEncoding = Encoding.UTF8; Console.SetCursorPosition(0, 0); - Console.Write("👨‍👩‍👧‍👦"); +#pragma warning disable CA1303 // Do not pass literals as localized parameters + Console.Write(TestEmoji); +#pragma warning restore CA1303 // Do not pass literals as localized parameters var (left, _) = Console.GetCursorPosition(); Console.Clear(); From 0b478fc4935f9b0d2d084f00933093c1e51cc1e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Nov 2024 18:13:24 +0000 Subject: [PATCH 23/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Drawing/DrawingContextImpl.cs | 137 +++++++++--------- .../DrawingBoxSymbol.cs | 78 +++++----- .../PixelBufferImplementation/ISymbol.cs | 2 +- .../PixelBufferImplementation/SimpleSymbol.cs | 5 +- src/Consolonia.Core/Drawing/RenderTarget.cs | 54 ++++--- src/Consolonia.Core/Helpers/Extensions.cs | 39 +++-- .../Infrastructure/IConsole.cs | 11 +- .../InputLessDefaultNetConsole.cs | 6 +- src/Consolonia.Core/Text/TextShaper.cs | 3 +- .../Controls/Helpers/SymbolsControl.cs | 3 +- .../Consolonia.GalleryTests/TextBlockTests.cs | 5 +- 11 files changed, 170 insertions(+), 173 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 845d1c1e..ce1b97f4 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -73,37 +73,37 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect new SKPaint { FilterQuality = SKFilterQuality.Medium }); for (int y = 0; y < bitmap.Info.Height; y += 2) - for (int x = 0; x < bitmap.Info.Width; x += 2) - { - // NOTE: we divide by 2 because we are working with quad pixels, - // // the bitmap has twice the horizontal and twice the vertical of the target rect. - int px = (int)targetRect.TopLeft.X + x / 2; - int py = (int)targetRect.TopLeft.Y + y / 2; + for (int x = 0; x < bitmap.Info.Width; x += 2) + { + // NOTE: we divide by 2 because we are working with quad pixels, + // // the bitmap has twice the horizontal and twice the vertical of the target rect. + int px = (int)targetRect.TopLeft.X + x / 2; + int py = (int)targetRect.TopLeft.Y + y / 2; - // get the quad pixel the bitmap - var quadColors = new[] - { + // get the quad pixel the bitmap + var quadColors = new[] + { bitmap.GetPixel(x, y), bitmap.GetPixel(x + 1, y), bitmap.GetPixel(x, y + 1), bitmap.GetPixel(x + 1, y + 1) }; - // map it to a single char to represet the 4 pixels - char quadPixel = GetQuadPixelCharacter(quadColors); + // map it to a single char to represet the 4 pixels + char quadPixel = GetQuadPixelCharacter(quadColors); - // get the combined colors for the quad pixel - Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); - Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); + // get the combined colors for the quad pixel + Color foreground = GetForegroundColorForQuadPixel(quadColors, quadPixel); + Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); - var imagePixel = new Pixel( - new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), - new PixelBackground(background)); - CurrentClip.ExecuteWithClipping(new Point(px, py), - () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); - }); - } + var imagePixel = new Pixel( + new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), + new PixelBackground(background)); + CurrentClip.ExecuteWithClipping(new Point(px, py), + () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (existingPixel, _) => existingPixel.Blend(imagePixel), imagePixel.Background.Color); + }); + } } public void DrawBitmap(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) @@ -154,11 +154,11 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) case VisualBrush: throw new NotImplementedException(); case ISceneBrush sceneBrush: - { - ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); - return; - } + { + ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); + return; + } } Rect r2 = r.TransformToAABB(Transform); @@ -166,19 +166,19 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) double width = r2.Width + (pen?.Thickness ?? 0); double height = r2.Height + (pen?.Thickness ?? 0); for (int x = 0; x < width; x++) - for (int y = 0; y < height; y++) - { - int px = (int)(r2.TopLeft.X + x); - int py = (int)(r2.TopLeft.Y + y); + for (int y = 0; y < height; y++) + { + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); - ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); - CurrentClip.ExecuteWithClipping(new Point(px, py), () => - { - _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), - (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), - backgroundBrush); - }); - } + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => + { + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (pixel, bb) => pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))), + backgroundBrush); + }); + } } if (pen is null or { Thickness: 0 } @@ -211,7 +211,7 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) } var shapedBuffer = (ShapedBuffer)glyphRunImpl.GlyphInfos; - var text = shapedBuffer.Text.ToString(); + string text = shapedBuffer.Text.ToString(); DrawStringInternal(foreground, text, glyphRun.GlyphTypeface); } @@ -507,29 +507,30 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl // Each glyph maps to a pixel as a starting point. // Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels // the x and y are adjusted accodingly. - foreach (var glyph in text.GetGlyphs()) + foreach (string glyph in text.GetGlyphs()) { - Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); + Point characterPoint = + whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); Color foregroundColor = consoleBrush.Color; switch (glyph) { case "\t": + { + const int tabSize = 8; + var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor); + for (int j = 0; j < tabSize; j++) { - const int tabSize = 8; - var consolePixel = new Pixel(new SimpleSymbol(' '), foregroundColor); - for (int j = 0; j < tabSize; j++) + Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); + CurrentClip.ExecuteWithClipping(newCharacterPoint, () => { - Point newCharacterPoint = characterPoint.WithX(characterPoint.X + j); - CurrentClip.ExecuteWithClipping(newCharacterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); - } - - currentXPosition += tabSize - 1; + _pixelBuffer.Set((PixelBufferCoordinate)newCharacterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); } + + currentXPosition += tabSize - 1; + } break; case "\r": case "\f": @@ -538,20 +539,20 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl currentYPosition++; break; default: + { + var symbol = new SimpleSymbol(glyph); + var consolePixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); + CurrentClip.ExecuteWithClipping(characterPoint, () => { - var symbol = new SimpleSymbol(glyph); - var consolePixel = new Pixel(symbol, foregroundColor, typeface.Style, typeface.Weight); - CurrentClip.ExecuteWithClipping(characterPoint, () => - { - _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, - (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); - }); + _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, + (oldPixel, cp) => oldPixel.Blend(cp), consolePixel); + }); - if (symbol.Width > 1) - currentXPosition += symbol.Width; - else - currentXPosition++; - } + if (symbol.Width > 1) + currentXPosition += symbol.Width; + else + currentXPosition++; + } break; } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 1596674c..11971898 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -55,46 +55,46 @@ private char GetBoxSymbol() return horizontal ? '╪' : '╫'; default: + { + return _upRightDownLeft switch { - return _upRightDownLeft switch - { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs index 95a02937..e1d8909c 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/ISymbol.cs @@ -10,7 +10,7 @@ public interface ISymbol /// /// The number of characters the symbol takes up /// - ushort Width { get; } + ushort Width { get; } bool IsWhiteSpace(); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index f54d639c..997e6934 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,4 +1,3 @@ -using System; using System.Diagnostics; using System.Text; using Consolonia.Core.Helpers; @@ -16,7 +15,7 @@ public SimpleSymbol() } public SimpleSymbol(char character) - :this(character.ToString()) + : this(character.ToString()) { } @@ -43,7 +42,7 @@ public bool IsWhiteSpace() public ISymbol Blend(ref ISymbol symbolAbove) { - return !String.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; + return !string.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index b8f66e62..cc5d9ed2 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -111,45 +111,43 @@ private void RenderToDevice() var flushingBuffer = new FlushingBuffer(_console); for (ushort y = 0; y < pixelBuffer.Height; y++) + for (ushort x = 0; x < pixelBuffer.Width;) { - for (ushort x = 0; x < pixelBuffer.Width; ) - { - Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; + Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; - if (pixel.IsCaret) - { - if (caretPosition != null) - throw new InvalidOperationException("Caret is already shown"); - caretPosition = new PixelBufferCoordinate(x, y); - } + if (pixel.IsCaret) + { + if (caretPosition != null) + throw new InvalidOperationException("Caret is already shown"); + caretPosition = new PixelBufferCoordinate(x, y); + } - /* todo: There is not IWindowImpl.Invalidate anymore. + /* todo: There is not IWindowImpl.Invalidate anymore. if (!_consoleWindow.InvalidatedRects.Any(rect => rect.ContainsExclusive(new Point(x, y)))) continue;*/ - if (pixel.Background.Mode != PixelBackgroundMode.Colored) - throw new InvalidOperationException( - "All pixels in the buffer must have exact console color before rendering"); + if (pixel.Background.Mode != PixelBackgroundMode.Colored) + throw new InvalidOperationException( + "All pixels in the buffer must have exact console color before rendering"); - (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, string text) - pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, - pixel.Foreground.Style, pixel.Foreground.TextDecorations, - pixel.Foreground.Symbol.Text); + (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection + textDecorations, string text) + pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, + pixel.Foreground.Style, pixel.Foreground.TextDecorations, + pixel.Foreground.Symbol.Text); - //todo: indexOutOfRange during resize - if (_cache[x, y] == pixelSpread) - { - x++; - continue; - } + //todo: indexOutOfRange during resize + if (_cache[x, y] == pixelSpread) + { + x++; + continue; + } - _cache[x, y] = pixelSpread; + _cache[x, y] = pixelSpread; - flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); + flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); - x += pixel.Foreground.Symbol.Width; - } + x += pixel.Foreground.Symbol.Width; } flushingBuffer.Flush(); diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 2d7d611b..f85d228e 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -31,21 +31,21 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix } /// - /// Process text into collection of glyphs where a glyph is either text or a combination of chars which make up an emoji. + /// Process text into collection of glyphs where a glyph is either text or a combination of chars which make up an + /// emoji. /// /// /// public static IList GetGlyphs(this string text) { - IConsole console = AvaloniaLocator.Current.GetService(); + var console = AvaloniaLocator.Current.GetService(); - List glyphs = new List(); - StringBuilder emoji = new StringBuilder(); - var runes = text.EnumerateRunes(); - Rune lastRune = new Rune(); + var glyphs = new List(); + var emoji = new StringBuilder(); + StringRuneEnumerator runes = text.EnumerateRunes(); + var lastRune = new Rune(); while (runes.MoveNext()) - { if (console.SupportsComplexEmoji) { if (lastRune.Value == Codepoints.ZWJ || @@ -55,9 +55,9 @@ public static IList GetGlyphs(this string text) emoji.Append(runes.Current); } else if (runes.Current.Value == Emoji.ZeroWidthJoiner || - runes.Current.Value == Emoji.ObjectReplacementCharacter || - runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || - runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) + runes.Current.Value == Emoji.ObjectReplacementCharacter || + runes.Current.Value == Codepoints.VariationSelectors.EmojiSymbol || + runes.Current.Value == Codepoints.VariationSelectors.TextSymbol) { emoji.Append(runes.Current); } @@ -68,8 +68,10 @@ public static IList GetGlyphs(this string text) glyphs.Add(emoji.ToString()); emoji.Clear(); } + glyphs.Add(runes.Current.ToString()); } + lastRune = runes.Current; } else @@ -80,29 +82,26 @@ public static IList GetGlyphs(this string text) runes.Current.Value != Codepoints.VariationSelectors.TextSymbol) glyphs.Add(runes.Current.ToString()); } - } - if (emoji.Length > 0) - { - glyphs.Add(emoji.ToString()); - } + + if (emoji.Length > 0) glyphs.Add(emoji.ToString()); return glyphs; } /// - /// Measure text for actual width + /// Measure text for actual width /// /// /// public static ushort MeasureText(this string text) { - IConsole console = AvaloniaLocator.Current.GetService(); + var console = AvaloniaLocator.Current.GetService(); ushort width = 0; ushort lastWidth = 0; - foreach (var rune in text.EnumerateRunes()) + foreach (Rune rune in text.EnumerateRunes()) { - var runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); - if (console.SupportsComplexEmoji && + ushort runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); + if (console.SupportsComplexEmoji && (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter)) width -= lastWidth; else diff --git a/src/Consolonia.Core/Infrastructure/IConsole.cs b/src/Consolonia.Core/Infrastructure/IConsole.cs index a5c1da37..6ae82ce7 100644 --- a/src/Consolonia.Core/Infrastructure/IConsole.cs +++ b/src/Consolonia.Core/Infrastructure/IConsole.cs @@ -15,6 +15,12 @@ public interface IConsole : IDisposable { PixelBufferSize Size { get; } bool CaretVisible { get; set; } + + /// + /// This is true if console supports composing multiple emojis together (like: 👨‍👩‍👧‍👦). + /// + bool SupportsComplexEmoji { get; } + void SetTitle(string title); void SetCaretPosition(PixelBufferCoordinate bufferPoint); @@ -31,10 +37,5 @@ void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground event Action FocusEvent; void PauseIO(Task task); void ClearOutput(); - - /// - /// This is true if console supports composing multiple emojis together (like: 👨‍👩‍👧‍👦). - /// - bool SupportsComplexEmoji { get; } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 3d0a17bb..786455af 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -26,7 +26,7 @@ protected InputLessDefaultNetConsole() #pragma warning disable CA1303 // Do not pass literals as localized parameters Console.Write(TestEmoji); #pragma warning restore CA1303 // Do not pass literals as localized parameters - var (left, _) = Console.GetCursorPosition(); + (int left, _) = Console.GetCursorPosition(); Console.Clear(); SupportsComplexEmoji = left == 2; @@ -52,7 +52,7 @@ public bool CaretVisible public PixelBufferSize Size { get; private set; } - public bool SupportsComplexEmoji { get; private set; } + public bool SupportsComplexEmoji { get; } public void SetTitle(string title) { @@ -113,7 +113,7 @@ FontWeight.Thin or FontWeight.ExtraLight or FontWeight.Light Console.Write(sb.ToString()); - var textWidth = str.MeasureText(); + ushort textWidth = str.MeasureText(); if (_headBufferPoint.X < Size.Width - textWidth) _headBufferPoint = new PixelBufferCoordinate((ushort)(_headBufferPoint.X + textWidth), _headBufferPoint.Y); diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index 3c9bf9aa..f44b9183 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -5,7 +5,6 @@ namespace Consolonia.Core.Text { - public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) @@ -17,7 +16,7 @@ public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions optio for (int i = 0; i < shapedBuffer.Length; i++) shapedBuffer[i] = new GlyphInfo('X', i, glyphs[i].MeasureText()); - + return shapedBuffer; } } diff --git a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs index c36e05c9..04c1e199 100644 --- a/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs +++ b/src/Consolonia.Themes.TurboVision/Templates/Controls/Helpers/SymbolsControl.cs @@ -69,7 +69,8 @@ public string Text { _text = value; - var glyphs = new TextShaper().ShapeText(value.AsMemory(), new TextShaperOptions(new GlyphTypeface(), 1)); + ShapedBuffer glyphs = + new TextShaper().ShapeText(value.AsMemory(), new TextShaperOptions(new GlyphTypeface(), 1)); _shapedText = new GlyphRun(new GlyphTypeface(), 1, (_text ?? string.Empty).AsMemory(), diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index fe852ab5..8f212ea8 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -6,7 +6,7 @@ namespace Consolonia.GalleryTests { /// - /// Unit test for TextBlock view + /// Unit test for TextBlock view /// [TestFixture] internal class TextBlockTests : GalleryTestsBaseBase @@ -23,9 +23,8 @@ protected override Task PerformSingleTest() // multiline "│Vivamus magna. Cras in mi at felis aliquet congue. Ut a │", "│est eget ligula molestie gravida. Curabitur massa. Donec│", - // special chars, emojis, etc. + // special chars, emojis, etc. "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞", "🎵", "“𝔉𝔞𝔫𝔠𝔶”", "ff", "fi", "½"); - } } } \ No newline at end of file From d4fc7f1a3a91eaab8e87508d605b15c819fa38b4 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 07:28:04 -0800 Subject: [PATCH 24/48] make list readonly --- src/Consolonia.Core/Helpers/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index f85d228e..615bed6c 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -36,7 +36,7 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix /// /// /// - public static IList GetGlyphs(this string text) + public static IReadOnlyList GetGlyphs(this string text) { var console = AvaloniaLocator.Current.GetService(); From 019a80f9475877f21cc3c7dbb72fe52a33ac3c58 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:30:43 +0000 Subject: [PATCH 25/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Infrastructure/InputLessDefaultNetConsole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 722eb366..7db41923 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -28,7 +28,7 @@ protected InputLessDefaultNetConsole() #pragma warning restore CA1303 // Do not pass literals as localized parameters (int left, _) = Console.GetCursorPosition(); SupportsComplexEmoji = left == 2; - + #pragma warning disable CA1303 // Do not pass literals as localized parameters Console.Write(ConsoleUtils.EnableAlternateBuffer); #pragma warning restore CA1303 // Do not pass literals as localized parameters From a823630cc2481ede57d35b579f72e2fa1749b60a Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 07:37:11 -0800 Subject: [PATCH 26/48] cleanup --- .../Drawing/DrawingContextImpl.cs | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index ce1b97f4..274d80e2 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -306,9 +306,11 @@ private void DrawLineInternal(IPen pen, Line line) line = TransformLineInternal(line); Point head = line.PStart; - - if (IfMoveConsoleCaretMove(pen, head)) + if (pen.Brush is MoveConsoleCaretToPositionBrush) + { + _pixelBuffer.SetCaretPosition((PixelBufferCoordinate)head); return; + } if (line.Vertical == false && pen.Thickness > 1) { @@ -370,8 +372,11 @@ private void DrawRectangleLineInternal(IPen pen, Line line) Point head = line.PStart; - if (IfMoveConsoleCaretMove(pen, head)) + if (pen.Brush is MoveConsoleCaretToPositionBrush) + { + _pixelBuffer.SetCaretPosition((PixelBufferCoordinate)head); return; + } var extractColorCheckPlatformSupported = ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle); if (extractColorCheckPlatformSupported == null) @@ -402,21 +407,6 @@ private Line TransformLineInternal(Line line) return line; } - /// - /// If the pen brush is a MoveConsoleCaretToPositionBrush, move the caret - /// - /// - /// - /// - private bool IfMoveConsoleCaretMove(IPen pen, Point head) - { - if (pen.Brush is not MoveConsoleCaretToPositionBrush) - return false; - - _pixelBuffer.SetCaretPosition((PixelBufferCoordinate)head); - return true; - } - /// /// Extract color from pen brush /// From 3a5efdeb308c3f83094fa81dcf92312782881c05 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 07:41:18 -0800 Subject: [PATCH 27/48] add comments --- .../InputLessDefaultNetConsole.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 787bd6c9..010a0335 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -18,25 +18,27 @@ public class InputLessDefaultNetConsole : IConsole private bool _caretVisible; private PixelBufferCoordinate _headBufferPoint; +#pragma warning disable CA1303 // Do not pass literals as localized parameters protected InputLessDefaultNetConsole() { - // detect emoji composition support Console.OutputEncoding = Encoding.UTF8; + + // enable alternate screen so original console screen is not affected by the app + Console.Write(ConsoleUtils.EnableAlternateBuffer); + + /// Detect complex emoji support by writing a complex emoji and checking cursor position. + /// If the cursor moves 2 positions, it indicates proper rendering of composite surrogate pairs. Console.SetCursorPosition(0, 0); -#pragma warning disable CA1303 // Do not pass literals as localized parameters Console.Write(TestEmoji); -#pragma warning restore CA1303 // Do not pass literals as localized parameters (int left, _) = Console.GetCursorPosition(); SupportsComplexEmoji = left == 2; - -#pragma warning disable CA1303 // Do not pass literals as localized parameters - Console.Write(ConsoleUtils.EnableAlternateBuffer); -#pragma warning restore CA1303 // Do not pass literals as localized parameters + Console.CursorVisible = false; Console.Clear(); - + ActualizeSize(); } +#pragma warning restore CA1303 // Do not pass literals as localized parameters protected bool Disposed { get; private set; } From 40dedcc1e0bfd138b0b13763b71b11ffe0fd323f Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 08:03:11 -0800 Subject: [PATCH 28/48] removed accidental comment of /// instead of // --- .../Infrastructure/InputLessDefaultNetConsole.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 010a0335..18fc848f 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -26,8 +26,8 @@ protected InputLessDefaultNetConsole() // enable alternate screen so original console screen is not affected by the app Console.Write(ConsoleUtils.EnableAlternateBuffer); - /// Detect complex emoji support by writing a complex emoji and checking cursor position. - /// If the cursor moves 2 positions, it indicates proper rendering of composite surrogate pairs. + // Detect complex emoji support by writing a complex emoji and checking cursor position. + // If the cursor moves 2 positions, it indicates proper rendering of composite surrogate pairs. Console.SetCursorPosition(0, 0); Console.Write(TestEmoji); (int left, _) = Console.GetCursorPosition(); From 379870e6b99a946f84508ecd0143dad5eafb16eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 16:06:02 +0000 Subject: [PATCH 29/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Infrastructure/InputLessDefaultNetConsole.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 18fc848f..d23a5d3d 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -35,7 +35,7 @@ protected InputLessDefaultNetConsole() Console.CursorVisible = false; Console.Clear(); - + ActualizeSize(); } #pragma warning restore CA1303 // Do not pass literals as localized parameters From e7be04e5e507cd5277b9ba485bf1316d124e54ed Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 11:47:07 -0800 Subject: [PATCH 30/48] rename files --- src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/Assembly.cs | 0 .../Consolonia.TestsCore.csproj | 0 .../ConsoloniaAppTestBase.cs | 0 .../Consolonia.TestsCore => Consolonia.NUnit}/PixelTests.cs | 0 .../Consolonia.TestsCore => Consolonia.NUnit}/TestHelpers.cs | 0 .../Consolonia.TestsCore => Consolonia.NUnit}/UnitTestConsole.cs | 0 .../Base/GalleryTestsBaseBase.cs | 0 .../ButtonTests.cs | 0 .../CalendarPickerTests.cs | 0 .../CalendarTests.cs | 0 .../CheckBoxTests.cs | 0 .../ComboBoxTests.cs | 0 .../Consolonia.Gallery.Tests.csproj} | 0 .../DataGridTests.cs | 0 .../DialogTests.cs | 0 .../FlyoutTests.cs | 0 .../ListBoxTests.cs | 0 .../MenuTests.cs | 0 .../ProgressBarTests.cs | 0 .../RadioButtonTests.cs | 0 .../ScrollViewerTests.cs | 0 .../TabControlTests.cs | 0 .../TextBlockTests.cs | 0 .../TextBoxTests.cs | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/Assembly.cs (100%) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/Consolonia.TestsCore.csproj (100%) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/ConsoloniaAppTestBase.cs (100%) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/PixelTests.cs (100%) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/TestHelpers.cs (100%) rename src/{Tests/Consolonia.TestsCore => Consolonia.NUnit}/UnitTestConsole.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/Base/GalleryTestsBaseBase.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/ButtonTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/CalendarPickerTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/CalendarTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/CheckBoxTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/ComboBoxTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests/Consolonia.GalleryTests.csproj => Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj} (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/DataGridTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/DialogTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/FlyoutTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/ListBoxTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/MenuTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/ProgressBarTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/RadioButtonTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/ScrollViewerTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/TabControlTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/TextBlockTests.cs (100%) rename src/Tests/{Consolonia.GalleryTests => Consolonia.Gallery.Tests}/TextBoxTests.cs (100%) diff --git a/src/Tests/Consolonia.TestsCore/Assembly.cs b/src/Consolonia.NUnit/Assembly.cs similarity index 100% rename from src/Tests/Consolonia.TestsCore/Assembly.cs rename to src/Consolonia.NUnit/Assembly.cs diff --git a/src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj b/src/Consolonia.NUnit/Consolonia.TestsCore.csproj similarity index 100% rename from src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj rename to src/Consolonia.NUnit/Consolonia.TestsCore.csproj diff --git a/src/Tests/Consolonia.TestsCore/ConsoloniaAppTestBase.cs b/src/Consolonia.NUnit/ConsoloniaAppTestBase.cs similarity index 100% rename from src/Tests/Consolonia.TestsCore/ConsoloniaAppTestBase.cs rename to src/Consolonia.NUnit/ConsoloniaAppTestBase.cs diff --git a/src/Tests/Consolonia.TestsCore/PixelTests.cs b/src/Consolonia.NUnit/PixelTests.cs similarity index 100% rename from src/Tests/Consolonia.TestsCore/PixelTests.cs rename to src/Consolonia.NUnit/PixelTests.cs diff --git a/src/Tests/Consolonia.TestsCore/TestHelpers.cs b/src/Consolonia.NUnit/TestHelpers.cs similarity index 100% rename from src/Tests/Consolonia.TestsCore/TestHelpers.cs rename to src/Consolonia.NUnit/TestHelpers.cs diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Consolonia.NUnit/UnitTestConsole.cs similarity index 100% rename from src/Tests/Consolonia.TestsCore/UnitTestConsole.cs rename to src/Consolonia.NUnit/UnitTestConsole.cs diff --git a/src/Tests/Consolonia.GalleryTests/Base/GalleryTestsBaseBase.cs b/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/Base/GalleryTestsBaseBase.cs rename to src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs diff --git a/src/Tests/Consolonia.GalleryTests/ButtonTests.cs b/src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/ButtonTests.cs rename to src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/CalendarPickerTests.cs b/src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/CalendarPickerTests.cs rename to src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/CalendarTests.cs b/src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/CalendarTests.cs rename to src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/CheckBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/CheckBoxTests.cs rename to src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/ComboBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/ComboBoxTests.cs rename to src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/Consolonia.GalleryTests.csproj b/src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj similarity index 100% rename from src/Tests/Consolonia.GalleryTests/Consolonia.GalleryTests.csproj rename to src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj diff --git a/src/Tests/Consolonia.GalleryTests/DataGridTests.cs b/src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/DataGridTests.cs rename to src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/DialogTests.cs b/src/Tests/Consolonia.Gallery.Tests/DialogTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/DialogTests.cs rename to src/Tests/Consolonia.Gallery.Tests/DialogTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/FlyoutTests.cs b/src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/FlyoutTests.cs rename to src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/ListBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/ListBoxTests.cs rename to src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/MenuTests.cs b/src/Tests/Consolonia.Gallery.Tests/MenuTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/MenuTests.cs rename to src/Tests/Consolonia.Gallery.Tests/MenuTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/ProgressBarTests.cs b/src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/ProgressBarTests.cs rename to src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/RadioButtonTests.cs b/src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/RadioButtonTests.cs rename to src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/ScrollViewerTests.cs b/src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/ScrollViewerTests.cs rename to src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/TabControlTests.cs b/src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/TabControlTests.cs rename to src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/TextBlockTests.cs rename to src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs diff --git a/src/Tests/Consolonia.GalleryTests/TextBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs similarity index 100% rename from src/Tests/Consolonia.GalleryTests/TextBoxTests.cs rename to src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs From b3f061b4624560ab1d9ced74b5fb18031ac08d66 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 11:47:16 -0800 Subject: [PATCH 31/48] update unit tests --- src/Consolonia.Core/Assembly.cs | 2 +- .../PixelBackground.cs | 2 +- .../PixelBufferImplementation/SimpleSymbol.cs | 2 + src/Consolonia.Gallery/Assembly.cs | 2 +- src/Consolonia.GuiCS/Assembly.cs | 2 +- src/Consolonia.NUnit/Consolonia.NUnit.csproj | 16 +++++ src/Consolonia.NUnit/ConsoloniaAppTestBase.cs | 2 +- src/Consolonia.NUnit/TestHelpers.cs | 2 +- src/Consolonia.NUnit/UnitTestConsole.cs | 2 +- src/Consolonia.PlatformSupport/Assembly.cs | 2 +- src/Consolonia.sln | 59 +++++++++++-------- src/Tests/Consolonia.Core.Tests/Assembly.cs | 3 + .../Consolonia.Core.Tests/ColorTests.cs} | 4 +- .../Consolonia.Core.Tests.csproj} | 6 +- .../DrawingBoxSymbolTests.cs | 27 +++++++++ .../PixelBackgroundTests.cs | 12 ++++ .../PixelForegroundTests.cs | 12 ++++ src/Tests/Consolonia.Core.Tests/PixelTests.cs | 57 ++++++++++++++++++ .../SimpleSymbolTests.cs | 52 ++++++++++++++++ .../Base/GalleryTestsBaseBase.cs | 13 +--- .../Consolonia.Gallery.Tests/ButtonTests.cs | 10 ++-- .../CalendarPickerTests.cs | 12 ++-- .../Consolonia.Gallery.Tests/CalendarTests.cs | 12 ++-- .../Consolonia.Gallery.Tests/CheckBoxTests.cs | 10 ++-- .../Consolonia.Gallery.Tests/ComboBoxTests.cs | 10 ++-- .../Consolonia.Gallery.Tests.csproj | 2 +- .../Consolonia.Gallery.Tests/DataGridTests.cs | 10 ++-- .../Consolonia.Gallery.Tests/DialogTests.cs | 10 ++-- .../Consolonia.Gallery.Tests/FlyoutTests.cs | 10 ++-- .../Consolonia.Gallery.Tests/ListBoxTests.cs | 10 ++-- .../Consolonia.Gallery.Tests/MenuTests.cs | 10 ++-- .../ProgressBarTests.cs | 11 ++-- .../RadioButtonTests.cs | 10 ++-- .../ScrollViewerTests.cs | 10 ++-- .../TabControlTests.cs | 10 ++-- .../TextBlockTests.cs | 13 ++-- .../Consolonia.Gallery.Tests/TextBoxTests.cs | 10 ++-- 37 files changed, 336 insertions(+), 113 deletions(-) create mode 100644 src/Consolonia.NUnit/Consolonia.NUnit.csproj create mode 100644 src/Tests/Consolonia.Core.Tests/Assembly.cs rename src/{Consolonia.NUnit/PixelTests.cs => Tests/Consolonia.Core.Tests/ColorTests.cs} (98%) rename src/{Consolonia.NUnit/Consolonia.TestsCore.csproj => Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj} (60%) create mode 100644 src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs create mode 100644 src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs create mode 100644 src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs create mode 100644 src/Tests/Consolonia.Core.Tests/PixelTests.cs create mode 100644 src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs diff --git a/src/Consolonia.Core/Assembly.cs b/src/Consolonia.Core/Assembly.cs index 7047cf80..9ce3d967 100644 --- a/src/Consolonia.Core/Assembly.cs +++ b/src/Consolonia.Core/Assembly.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Consolonia.TestsCore")] +[assembly: InternalsVisibleTo("Consolonia.Core.Tests")] [assembly: CLSCompliant(false)] //todo: should we make it compliant? \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index ac2e8eee..feb41ee0 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -13,7 +13,7 @@ public PixelBackground(Color color) public PixelBackground(PixelBackgroundMode mode, Color? color = null) { - Color = color ?? Colors.Black; + Color = color ?? Colors.Transparent; Mode = mode; } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 63d6a830..39a27dc6 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -21,6 +21,8 @@ public bool IsWhiteSpace() public ISymbol Blend(ref ISymbol symbolAbove) { + if (symbolAbove.IsWhiteSpace()) + return this; return symbolAbove; } } diff --git a/src/Consolonia.Gallery/Assembly.cs b/src/Consolonia.Gallery/Assembly.cs index c8ea583b..edaf0372 100644 --- a/src/Consolonia.Gallery/Assembly.cs +++ b/src/Consolonia.Gallery/Assembly.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Consolonia.GalleryTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Consolonia.Gallery.Tests")] \ No newline at end of file diff --git a/src/Consolonia.GuiCS/Assembly.cs b/src/Consolonia.GuiCS/Assembly.cs index 0eee0203..3b1e50ed 100644 --- a/src/Consolonia.GuiCS/Assembly.cs +++ b/src/Consolonia.GuiCS/Assembly.cs @@ -1,7 +1,7 @@ using System; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Consolonia.TestsCore")] +[assembly: InternalsVisibleTo("Consolonia.Core.Tests")] [assembly: InternalsVisibleTo("Consolonia.PlatformSupport")] [assembly: CLSCompliant(false)] //todo: should we make it compliant? \ No newline at end of file diff --git a/src/Consolonia.NUnit/Consolonia.NUnit.csproj b/src/Consolonia.NUnit/Consolonia.NUnit.csproj new file mode 100644 index 00000000..dae1791f --- /dev/null +++ b/src/Consolonia.NUnit/Consolonia.NUnit.csproj @@ -0,0 +1,16 @@ + + + + + + true + + + + + + + + + + diff --git a/src/Consolonia.NUnit/ConsoloniaAppTestBase.cs b/src/Consolonia.NUnit/ConsoloniaAppTestBase.cs index 2aadc3a2..9bb49037 100644 --- a/src/Consolonia.NUnit/ConsoloniaAppTestBase.cs +++ b/src/Consolonia.NUnit/ConsoloniaAppTestBase.cs @@ -10,7 +10,7 @@ using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; -namespace Consolonia.TestsCore +namespace Consolonia.NUnit { [NonParallelizable /*todo: switch to semaphore like https://stackoverflow.com/a/6427425/2362847 to allow other tests to execute in parallel*/] #pragma warning disable CA1001 // we are relying on TearDown by NUnit diff --git a/src/Consolonia.NUnit/TestHelpers.cs b/src/Consolonia.NUnit/TestHelpers.cs index 7f480af6..590cd9b1 100644 --- a/src/Consolonia.NUnit/TestHelpers.cs +++ b/src/Consolonia.NUnit/TestHelpers.cs @@ -3,7 +3,7 @@ using Avalonia.Threading; using NUnit.Framework; -namespace Consolonia.TestsCore +namespace Consolonia.NUnit { public static class TestHelpers { diff --git a/src/Consolonia.NUnit/UnitTestConsole.cs b/src/Consolonia.NUnit/UnitTestConsole.cs index 964e67a6..df040ccb 100644 --- a/src/Consolonia.NUnit/UnitTestConsole.cs +++ b/src/Consolonia.NUnit/UnitTestConsole.cs @@ -13,7 +13,7 @@ using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; -namespace Consolonia.TestsCore +namespace Consolonia.NUnit { public sealed class UnitTestConsole : IConsole { diff --git a/src/Consolonia.PlatformSupport/Assembly.cs b/src/Consolonia.PlatformSupport/Assembly.cs index 7047cf80..9ce3d967 100644 --- a/src/Consolonia.PlatformSupport/Assembly.cs +++ b/src/Consolonia.PlatformSupport/Assembly.cs @@ -1,6 +1,6 @@ using System; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Consolonia.TestsCore")] +[assembly: InternalsVisibleTo("Consolonia.Core.Tests")] [assembly: CLSCompliant(false)] //todo: should we make it compliant? \ No newline at end of file diff --git a/src/Consolonia.sln b/src/Consolonia.sln index 2880923f..d81b05ac 100644 --- a/src/Consolonia.sln +++ b/src/Consolonia.sln @@ -1,18 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{E8624A2C-33B8-4D19-A0BA-1CC39FC422F7}" +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.csproj", "{E8624A2C-33B8-4D19-A0BA-1CC39FC422F7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.Core", "Consolonia.Core\Consolonia.Core.csproj", "{9ED11926-949D-4D5A-8EA7-41CAB1229C47}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.Core", "Consolonia.Core\Consolonia.Core.csproj", "{9ED11926-949D-4D5A-8EA7-41CAB1229C47}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18168988-BF89-4EE5-8F64-1FAFB68462A2}" -ProjectSection(SolutionItems) = preProject - readme.md = readme.md - .editorconfig = .editorconfig -EndProjectSection + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + readme.md = readme.md + EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.Gallery", "Consolonia.Gallery\Consolonia.Gallery.csproj", "{70643248-718D-46F8-8775-E1674787B195}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.Gallery", "Consolonia.Gallery\Consolonia.Gallery.csproj", "{70643248-718D-46F8-8775-E1674787B195}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.Themes.TurboVision", "Consolonia.Themes.TurboVision\Consolonia.Themes.TurboVision.csproj", "{850A3EF5-2E1F-4876-B3DE-D5C99FDFC135}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.Themes.TurboVision", "Consolonia.Themes.TurboVision\Consolonia.Themes.TurboVision.csproj", "{850A3EF5-2E1F-4876-B3DE-D5C99FDFC135}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{476E1582-855A-4BEB-BA7D-55302428EE62}" ProjectSection(SolutionItems) = preProject @@ -23,30 +26,32 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RepoRoot", "RepoRoot", "{09FEC8F0-8390-4E31-9C89-9174697FF341}" ProjectSection(SolutionItems) = preProject ..\.gitignore = ..\.gitignore - ..\LICENSE = ..\LICENSE - ..\README.md = ..\README.md + ..\contributing.md = ..\contributing.md ..\Directory.Build.props = ..\Directory.Build.props ..\Directory.Core.Build.props = ..\Directory.Core.Build.props - ..\contributing.md = ..\contributing.md + ..\LICENSE = ..\LICENSE + ..\README.md = ..\README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{A8CC5246-0D48-4418-B3E8-AC82ADB269E9}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{7B64DD09-94E2-47DE-8490-189B9B406A5A}" ProjectSection(SolutionItems) = preProject - ..\.github\workflows\general_build.yml = ..\.github\workflows\general_build.yml ..\.github\workflows\editorconfig.yml = ..\.github\workflows\editorconfig.yml + ..\.github\workflows\general_build.yml = ..\.github\workflows\general_build.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F756B909-46C5-43FF-8322-86EC36B51841}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.TestsCore", "Tests\Consolonia.TestsCore\Consolonia.TestsCore.csproj", "{4C915339-CE21-4E48-A97E-7F3A02AEF1FA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.Gallery.Tests", "Tests\Consolonia.Gallery.Tests\Consolonia.Gallery.Tests.csproj", "{1AE0207B-BF59-4473-97E1-79FEB7FA1AD5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.PlatformSupport", "Consolonia.PlatformSupport\Consolonia.PlatformSupport.csproj", "{175F6648-971A-4840-B2C1-0B745CF688EB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.GalleryTests", "Tests\Consolonia.GalleryTests\Consolonia.GalleryTests.csproj", "{1AE0207B-BF59-4473-97E1-79FEB7FA1AD5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.GuiCS", "Consolonia.GuiCS\Consolonia.GuiCS.csproj", "{66A9B063-A27E-4B8B-974A-C02CD838C274}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.PlatformSupport", "Consolonia.PlatformSupport\Consolonia.PlatformSupport.csproj", "{175F6648-971A-4840-B2C1-0B745CF688EB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.NUnit", "Consolonia.NUnit\Consolonia.NUnit.csproj", "{76A98F78-6513-4203-AFCC-041F92579189}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Consolonia.GuiCS", "Consolonia.GuiCS\Consolonia.GuiCS.csproj", "{66A9B063-A27E-4B8B-974A-C02CD838C274}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Consolonia.Core.Tests", "Tests\Consolonia.Core.Tests\Consolonia.Core.Tests.csproj", "{58D95751-209B-4AF4-BC76-A8E52D5CEF5E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -70,10 +75,6 @@ Global {850A3EF5-2E1F-4876-B3DE-D5C99FDFC135}.Debug|Any CPU.Build.0 = Debug|Any CPU {850A3EF5-2E1F-4876-B3DE-D5C99FDFC135}.Release|Any CPU.ActiveCfg = Release|Any CPU {850A3EF5-2E1F-4876-B3DE-D5C99FDFC135}.Release|Any CPU.Build.0 = Release|Any CPU - {4C915339-CE21-4E48-A97E-7F3A02AEF1FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C915339-CE21-4E48-A97E-7F3A02AEF1FA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C915339-CE21-4E48-A97E-7F3A02AEF1FA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C915339-CE21-4E48-A97E-7F3A02AEF1FA}.Release|Any CPU.Build.0 = Release|Any CPU {1AE0207B-BF59-4473-97E1-79FEB7FA1AD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1AE0207B-BF59-4473-97E1-79FEB7FA1AD5}.Debug|Any CPU.Build.0 = Debug|Any CPU {1AE0207B-BF59-4473-97E1-79FEB7FA1AD5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -86,13 +87,25 @@ Global {66A9B063-A27E-4B8B-974A-C02CD838C274}.Debug|Any CPU.Build.0 = Debug|Any CPU {66A9B063-A27E-4B8B-974A-C02CD838C274}.Release|Any CPU.ActiveCfg = Release|Any CPU {66A9B063-A27E-4B8B-974A-C02CD838C274}.Release|Any CPU.Build.0 = Release|Any CPU + {76A98F78-6513-4203-AFCC-041F92579189}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76A98F78-6513-4203-AFCC-041F92579189}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76A98F78-6513-4203-AFCC-041F92579189}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76A98F78-6513-4203-AFCC-041F92579189}.Release|Any CPU.Build.0 = Release|Any CPU + {58D95751-209B-4AF4-BC76-A8E52D5CEF5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58D95751-209B-4AF4-BC76-A8E52D5CEF5E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58D95751-209B-4AF4-BC76-A8E52D5CEF5E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58D95751-209B-4AF4-BC76-A8E52D5CEF5E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {4C915339-CE21-4E48-A97E-7F3A02AEF1FA} = {F756B909-46C5-43FF-8322-86EC36B51841} - {1AE0207B-BF59-4473-97E1-79FEB7FA1AD5} = {F756B909-46C5-43FF-8322-86EC36B51841} + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {A8CC5246-0D48-4418-B3E8-AC82ADB269E9} = {09FEC8F0-8390-4E31-9C89-9174697FF341} {7B64DD09-94E2-47DE-8490-189B9B406A5A} = {A8CC5246-0D48-4418-B3E8-AC82ADB269E9} + {1AE0207B-BF59-4473-97E1-79FEB7FA1AD5} = {F756B909-46C5-43FF-8322-86EC36B51841} + {58D95751-209B-4AF4-BC76-A8E52D5CEF5E} = {F756B909-46C5-43FF-8322-86EC36B51841} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {47D28717-CF68-421D-B547-585C16AE6DE7} EndGlobalSection EndGlobal diff --git a/src/Tests/Consolonia.Core.Tests/Assembly.cs b/src/Tests/Consolonia.Core.Tests/Assembly.cs new file mode 100644 index 00000000..e39f450e --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/Assembly.cs @@ -0,0 +1,3 @@ +using System; + +[assembly: CLSCompliant(false)] //todo: should we make it compliant? \ No newline at end of file diff --git a/src/Consolonia.NUnit/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/ColorTests.cs similarity index 98% rename from src/Consolonia.NUnit/PixelTests.cs rename to src/Tests/Consolonia.Core.Tests/ColorTests.cs index 5ffc2681..ca789884 100644 --- a/src/Consolonia.NUnit/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/ColorTests.cs @@ -2,10 +2,10 @@ using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; -namespace Consolonia.TestsCore +namespace Consolonia.Core.Tests { [TestFixture] - public class PixelTests + public class ColorTests { [Test] public void TestShade() diff --git a/src/Consolonia.NUnit/Consolonia.TestsCore.csproj b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj similarity index 60% rename from src/Consolonia.NUnit/Consolonia.TestsCore.csproj rename to src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj index 2f3ae527..a237a748 100644 --- a/src/Consolonia.NUnit/Consolonia.TestsCore.csproj +++ b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj @@ -10,9 +10,13 @@ + - + + + + diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs new file mode 100644 index 00000000..f164c7e4 --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -0,0 +1,27 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class DrawingBoxSymbolTests + { + [Test] + public void DrawingBoxSymbolConstructor() + { + ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); + Assert.That(symbol.GetCharacter(), Is.EqualTo('┼')); + } + + [Test] + public void DrawingBoxSymbolBlend() + { + ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); + ISymbol symbolAbove = new DrawingBoxSymbol(0b1111_0000); + ISymbol newSymbol = symbol.Blend(ref symbolAbove); + Assert.That(newSymbol.GetCharacter(), Is.EqualTo('╬')); + } + + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs new file mode 100644 index 00000000..9a68085f --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -0,0 +1,12 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class PixelBackgroundTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs new file mode 100644 index 00000000..584b2a53 --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -0,0 +1,12 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class PixelForegroundTests + { + + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs new file mode 100644 index 00000000..725aee0f --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -0,0 +1,57 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class PixelTests + { + [Test] + public void PixelConstructorColorOnly() + { + Pixel pixel = new Pixel(Colors.Red); + Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Red)); + Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); + } + + [Test] + public void PixelConstructorColorAndSymbol() + { + Pixel pixel = new Pixel(new SimpleSymbol('a'), Colors.Red); + Assert.That(pixel.Foreground.Symbol.GetCharacter(), Is.EqualTo('a')); + 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.TextDecorations, Is.Null); + Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Transparent)); + Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Transparent)); + } + + [Test] + public void PixelConstructorDrawingBoxSymbol() + { + Pixel pixel = new Pixel(0b0000_1111, Colors.Red); + Assert.That(pixel.Foreground.Symbol.GetCharacter(), 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.TextDecorations, Is.Null); + Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Transparent)); + Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Transparent)); + } + + [Test] + public void PixelConstructorDrawingBoxSymbolAndColor() + { + Pixel pixel = new Pixel(new PixelForeground(new DrawingBoxSymbol(0b0000_1111), color: Colors.Red), new PixelBackground(Colors.Blue)); + Assert.That(pixel.Foreground.Symbol.GetCharacter(), 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.TextDecorations, Is.Null); + Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Blue)); + Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); + } + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs new file mode 100644 index 00000000..c0fa3848 --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -0,0 +1,52 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class SimpleSymbolTests + { + [Test] + public void CharConstructor() + { + ISymbol symbol = new SimpleSymbol('a'); + Assert.That(symbol.GetCharacter(), Is.EqualTo('a')); + } + + [Test] + public void SimpleSymbolIsWhiteSpace() + { + Assert.That(new SimpleSymbol('\0').IsWhiteSpace(), Is.True); + Assert.That(new SimpleSymbol(' ').IsWhiteSpace(), Is.False); + Assert.That(new SimpleSymbol('a').IsWhiteSpace(), Is.False); + } + + [Test] + public void SimpleSymbolBlend() + { + ISymbol symbol = new SimpleSymbol('a'); + ISymbol symbolAbove = new SimpleSymbol('b'); + ISymbol newSymbol = symbol.Blend(ref symbolAbove); + Assert.That(newSymbol.GetCharacter(), Is.EqualTo('b')); + } + + [Test] + public void SimpleSymbolBlendWithWhiteSpace() + { + ISymbol symbol = new SimpleSymbol(' '); + ISymbol symbolAbove = new SimpleSymbol('b'); + ISymbol newSymbol = symbol.Blend(ref symbolAbove); + Assert.That(newSymbol.GetCharacter(), Is.EqualTo('b')); + } + + [Test] + public void SimpleSymbolBlendWithEmpty() + { + ISymbol symbol = new SimpleSymbol('a'); + ISymbol symbolAbove = new SimpleSymbol('\0'); + ISymbol newSymbol = symbol.Blend(ref symbolAbove); + Assert.That(newSymbol.GetCharacter(), Is.EqualTo('a')); + } + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs b/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs index 5a08e4ce..b940dcaa 100644 --- a/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs +++ b/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs @@ -6,10 +6,10 @@ using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Gallery; using Consolonia.Gallery.View; -using Consolonia.TestsCore; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests.Base +namespace Consolonia.Gallery.Tests.Base { internal abstract class GalleryTestsBaseBase : ConsoloniaAppTestBase { @@ -21,15 +21,6 @@ protected GalleryTestsBaseBase(PixelBufferSize size = default) : base(size.IsEmp Args[1] = GetType().Name[..^5]; } - [Test] - public async Task SingleTest() - { - await UITest.KeyInput(Key.Tab); - await PerformSingleTest(); - } - - protected abstract Task PerformSingleTest(); - [OneTimeSetUp] public async Task Setup() { diff --git a/src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs b/src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs index 55671c2a..97d340a3 100644 --- a/src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/ButtonTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class ButtonTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Enter); await UITest.AssertHasText("Standard _?XAML Button", "Foreground", diff --git a/src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs b/src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs index d5438066..80f41a84 100644 --- a/src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/CalendarPickerTests.cs @@ -1,16 +1,18 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class CalendarPickerTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("2/16/2022"); await UITest.KeyInput(Key.Right); await UITest.KeyInput(9, Key.Left); diff --git a/src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs b/src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs index 329b7682..d136a8da 100644 --- a/src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/CalendarTests.cs @@ -1,17 +1,19 @@ -using System.Linq; +using System.Linq; using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class CalendarTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("24 25 26 27 28 29 30"); await UITest.AssertHasText(@"April", @"2022"); await UITest.KeyInput(Key.Tab, Key.Back); diff --git a/src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs index dc1b9fb1..7a1f69ce 100644 --- a/src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/CheckBoxTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class CheckBoxTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText( "[ ].+Unchecked", "[V].+Checked", diff --git a/src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs index 9f76452a..7d5064a8 100644 --- a/src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/ComboBoxTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class ComboBoxTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("Pick an Item", "Still item", "Null object"); diff --git a/src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj b/src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj index d0bb359c..8715e73d 100644 --- a/src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj +++ b/src/Tests/Consolonia.Gallery.Tests/Consolonia.Gallery.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs b/src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs index 063b5002..9769f7e0 100644 --- a/src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/DataGridTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class DataGridTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("Co.+Region.+Population.+Area", @"Afg.+ASIA \(EX. NEAR"); await UITest.KeyInput(Key.Tab, Key.Tab); diff --git a/src/Tests/Consolonia.Gallery.Tests/DialogTests.cs b/src/Tests/Consolonia.Gallery.Tests/DialogTests.cs index 5c9c4a73..4e22c4b8 100644 --- a/src/Tests/Consolonia.Gallery.Tests/DialogTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/DialogTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class DialogTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Enter); await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("One More"); diff --git a/src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs b/src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs index 869768f6..700265f6 100644 --- a/src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/FlyoutTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class FlyoutTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Enter); await UITest.AssertHasText(@"Item 1 \>", "Item 2"); diff --git a/src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs index c4d07fe2..3b99f9f4 100644 --- a/src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/ListBoxTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class ListBoxTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Tab, RawInputModifiers.Shift); await UITest.KeyInput(Key.Tab, RawInputModifiers.Shift); await UITest.KeyInput(Key.PageDown); diff --git a/src/Tests/Consolonia.Gallery.Tests/MenuTests.cs b/src/Tests/Consolonia.Gallery.Tests/MenuTests.cs index 7ae5dcf9..8b8a76eb 100644 --- a/src/Tests/Consolonia.Gallery.Tests/MenuTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/MenuTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class MenuTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("First", "Second"); await UITest.KeyInput(Key.Enter); await UITest.AssertHasText("Standard Menu Item", @"Ctrl\+A"); diff --git a/src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs b/src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs index 9ab8b900..de17f6e9 100644 --- a/src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/ProgressBarTests.cs @@ -1,17 +1,20 @@ using System.Threading.Tasks; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Avalonia.Input; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] [Ignore( "ProgressBar has annimation, thus application becomes never idle, thus hard to determine input, layout and other jobs are done")] //todo: internal class ProgressBarTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("5%", "50%"); } } diff --git a/src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs b/src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs index 79d4408a..fd856b39 100644 --- a/src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/RadioButtonTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class RadioButtonTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText(@"\(\*\).+Option 1", @"\( \).+Option 2", @"\(■\).+Option 3"); diff --git a/src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs b/src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs index daf684d3..a9daecee 100644 --- a/src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/ScrollViewerTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class ScrollViewerTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Tab, Key.Tab, Key.Tab); await UITest.KeyInput(30, Key.Right); diff --git a/src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs b/src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs index 44451369..c2d735eb 100644 --- a/src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/TabControlTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class TabControlTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("Arch", "Leaf"); await UITest.KeyInput(Key.Right); diff --git a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs index d1beb95e..67e2f951 100644 --- a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs @@ -1,16 +1,19 @@ using System.Threading.Tasks; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Avalonia.Input; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class TextBlockTests : GalleryTestsBaseBase { - protected override Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { - return UITest.AssertHasText("This is TextBlock", + await UITest.KeyInput(Key.Tab); + await UITest.AssertHasText("This is TextBlock", "Text trimming with charac...", "Text trimming with word...", "│Left aligned text ", diff --git a/src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs index 1fef1355..bb9ee17b 100644 --- a/src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/TextBoxTests.cs @@ -1,16 +1,18 @@ using System.Threading.Tasks; using Avalonia.Input; -using Consolonia.GalleryTests.Base; -using Consolonia.TestsCore; +using Consolonia.Gallery.Tests.Base; +using Consolonia.NUnit; using NUnit.Framework; -namespace Consolonia.GalleryTests +namespace Consolonia.Gallery.Tests { [TestFixture] internal class TextBoxTests : GalleryTestsBaseBase { - protected override async Task PerformSingleTest() + [Test] + public async Task PerformSingleTest() { + await UITest.KeyInput(Key.Tab); await UITest.KeyInput(Key.Right); await UITest.AssertHasText(@"elit\. "); await UITest.KeyInput(Key.Right); From 2cdcef3425f6bb1103922401a4ab3cae7aee5523 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 13:57:05 -0800 Subject: [PATCH 32/48] Split unit tests out from consolonia support package renamed test support library to Consolonia.NUnit and turned on package renamed Consolonia.GalleryTests to Consolonia.Gallery.Tests Created Consolonia.Core.Tests library Added unit tests for * Pixels * PixelBackground * PixelForeground * SimpleSymbol * DrawingBoxSymbol * Glyph calculation * Colors Refactored Gallery.Tests * Move type attribute up into gallery test because the way it was set up you couldn't double on a unit test and go to the unit test, and you could only have 1 unit test per class. --- .../Drawing/DrawingContextImpl.cs | 6 +- .../DrawingBoxSymbol.cs | 25 ++-- .../PixelBufferImplementation/Pixel.cs | 49 +++++--- .../PixelForeground.cs | 12 +- .../PixelBufferImplementation/SimpleSymbol.cs | 4 +- src/Consolonia.Core/Drawing/RenderTarget.cs | 9 +- src/Consolonia.Core/Helpers/Extensions.cs | 6 +- src/Consolonia.Core/Text/TextShaper.cs | 5 +- src/Tests/Consolonia.Core.Tests/Assembly.cs | 22 +++- src/Tests/Consolonia.Core.Tests/ColorTests.cs | 12 +- .../Consolonia.Core.Tests.csproj | 1 + .../DrawingBoxSymbolTests.cs | 52 ++++++++- src/Tests/Consolonia.Core.Tests/GlyphTests.cs | 109 ++++++++++++++++++ .../PixelBackgroundTests.cs | 16 ++- .../PixelForegroundTests.cs | 101 ++++++++++++++++ src/Tests/Consolonia.Core.Tests/PixelTests.cs | 44 +++++-- .../SimpleSymbolTests.cs | 89 +++++++++++--- 17 files changed, 474 insertions(+), 88 deletions(-) create mode 100644 src/Tests/Consolonia.Core.Tests/GlyphTests.cs diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index 274d80e2..f036906e 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -348,10 +348,10 @@ private void ApplyTextDecorationLineInternal(ref Point head, IPen pen, Line line pixel => { var newPixelForeground = new PixelForeground(pixel.Foreground.Symbol, + pixel.Foreground.Color, pixel.Foreground.Weight, pixel.Foreground.Style, - textDecoration, - pixel.Foreground.Color); + textDecoration); return pixel.Blend(new Pixel(newPixelForeground, pixel.Background)); }); }); @@ -497,7 +497,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl // Each glyph maps to a pixel as a starting point. // Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels // the x and y are adjusted accodingly. - foreach (string glyph in text.GetGlyphs()) + foreach (string glyph in text.GetGlyphs(_consoleWindow.Console.SupportsComplexEmoji)) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 11971898..1cea2396 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -12,29 +12,29 @@ public struct DrawingBoxSymbol : ISymbol // all 0bXXXX_0000 are special values private const byte BoldSymbol = 0b0001_0000; private const byte EmptySymbol = 0b0; + private readonly byte _upRightDownLeft; public DrawingBoxSymbol(byte upRightDownLeft) { _upRightDownLeft = upRightDownLeft; + Text = GetBoxSymbol(_upRightDownLeft).ToString(); } - private byte _upRightDownLeft; - - public string Text => GetBoxSymbol().ToString(); + public string Text { get; private init; } public ushort Width { get; } = 1; /// /// https://en.wikipedia.org/wiki/Code_page_437 /// - private char GetBoxSymbol() + private static char GetBoxSymbol(byte upRightDownLeft) { //DOS linedraw characters are not ordered in any programmatic manner, and calculating a particular character shape needs to use a look-up table. from https://en.wikipedia.org/wiki/Box-drawing_character - byte leftPart = (byte)(_upRightDownLeft & 0b1111_0000); + byte leftPart = (byte)(upRightDownLeft & 0b1111_0000); bool hasLeftPart = leftPart > 0; - switch (_upRightDownLeft & 0b0000_1111) + switch (upRightDownLeft & 0b0000_1111) { case 0b0000_1000: case 0b0000_0010: @@ -56,7 +56,7 @@ private char GetBoxSymbol() default: { - return _upRightDownLeft switch + return upRightDownLeft switch { EmptySymbol => char.MinValue, BoldSymbol => '█', @@ -107,12 +107,13 @@ public ISymbol Blend(ref ISymbol symbolAbove) { if (symbolAbove.IsWhiteSpace()) return this; - if (symbolAbove is not DrawingBoxSymbol drawingBoxSymbol) return symbolAbove; + if (symbolAbove is not DrawingBoxSymbol drawingBoxSymbol) + return symbolAbove; + if (drawingBoxSymbol._upRightDownLeft == BoldSymbol || _upRightDownLeft == BoldSymbol) - _upRightDownLeft = BoldSymbol; - else - _upRightDownLeft |= drawingBoxSymbol._upRightDownLeft; - return this; + return new DrawingBoxSymbol(BoldSymbol); + + return new DrawingBoxSymbol((byte)(_upRightDownLeft | drawingBoxSymbol._upRightDownLeft)); } public static DrawingBoxSymbol UpRightDownLeftFromPattern(byte pattern, LineStyle lineStyle) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 01528ef7..c4c3ae52 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -16,34 +16,55 @@ public readonly struct Pixel public bool IsCaret { get; } - public Pixel(ISymbol symbol, Color foregroundColor, FontStyle style = FontStyle.Normal, - FontWeight weight = FontWeight.Normal, TextDecorationCollection textDecorations = null) : this( - new PixelForeground(symbol, weight, style, textDecorations, foregroundColor), + /// + /// Make a pixel foreground with transparent background + /// + /// + /// + /// + /// + /// + public Pixel(ISymbol symbol, + Color foregroundColor, + FontStyle style = FontStyle.Normal, + FontWeight weight = FontWeight.Normal, + TextDecorationCollection textDecorations = null) : this( + new PixelForeground(symbol, foregroundColor, weight, style, textDecorations), new PixelBackground(PixelBackgroundMode.Transparent)) { } - public Pixel(Color backgroundColor) : this( - new PixelBackground(PixelBackgroundMode.Colored, backgroundColor)) - { - } - - public Pixel(PixelBackgroundMode mode) : this(new PixelBackground(mode)) - { - } - - public Pixel(PixelBackground background) : this(new PixelForeground(new SimpleSymbol()), + /// + /// Make a pixel with only background color, but no foreground + /// + /// + public Pixel(PixelBackground background) : + this(new PixelForeground(new SimpleSymbol(), Colors.Transparent), background) { } - public Pixel(PixelForeground foreground, PixelBackground background, bool isCaret = false) + /// + /// Make a pixel with foreground and background + /// + /// + /// + /// + public Pixel(PixelForeground foreground, + PixelBackground background, + bool isCaret = false) { Foreground = foreground; Background = background; IsCaret = isCaret; } + /// + /// Blend the pixelAbove with the this pixel. + /// + /// + /// + /// public Pixel Blend(Pixel pixelAbove) { PixelForeground newForeground; diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index 35ff9d39..f914a812 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -7,12 +7,12 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation [DebuggerDisplay("'{Symbol.Text}' [{Color}]")] public readonly struct PixelForeground { - public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, - FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null, Color? color = null) + public PixelForeground(ISymbol symbol, Color color, + FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null) { ArgumentNullException.ThrowIfNull(symbol); Symbol = symbol; - Color = color ?? Colors.White; + Color = color; Weight = weight; Style = style; TextDecorations = textDecorations; @@ -31,7 +31,7 @@ public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, public PixelForeground Shade() { Color newColor = Color.Shade(); - return new PixelForeground(Symbol, Weight, Style, TextDecorations, newColor); + return new PixelForeground(Symbol, newColor, Weight, Style, TextDecorations); } public PixelForeground Blend(PixelForeground pixelAboveForeground) @@ -42,8 +42,8 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground) ISymbol newSymbol = Symbol.Blend(ref symbolAbove); - return new PixelForeground(newSymbol, pixelAboveForeground.Weight, pixelAboveForeground.Style, - pixelAboveForeground.TextDecorations, pixelAboveForeground.Color); + return new PixelForeground(newSymbol, pixelAboveForeground.Color, pixelAboveForeground.Weight, + pixelAboveForeground.Style, pixelAboveForeground.TextDecorations); } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 997e6934..439c93c9 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -37,12 +37,12 @@ public SimpleSymbol(Rune rune) public bool IsWhiteSpace() { - return string.IsNullOrWhiteSpace(Text); + return string.IsNullOrEmpty(Text); } public ISymbol Blend(ref ISymbol symbolAbove) { - return !string.IsNullOrEmpty(symbolAbove.Text) ? symbolAbove : this; + return symbolAbove.IsWhiteSpace() ? this : symbolAbove; } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index cc5d9ed2..498e3b4e 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -130,10 +130,11 @@ private void RenderToDevice() "All pixels in the buffer must have exact console color before rendering"); - (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, string text) - pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, - pixel.Foreground.Style, pixel.Foreground.TextDecorations, + var pixelSpread = (pixel.Background.Color, + pixel.Foreground.Color, + pixel.Foreground.Weight, + pixel.Foreground.Style, + pixel.Foreground.TextDecorations, pixel.Foreground.Symbol.Text); //todo: indexOutOfRange during resize diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 615bed6c..45c5b5f7 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -36,17 +36,15 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix /// /// /// - public static IReadOnlyList GetGlyphs(this string text) + public static IReadOnlyList GetGlyphs(this string text, bool supportsComplexEmoji) { - var console = AvaloniaLocator.Current.GetService(); - var glyphs = new List(); var emoji = new StringBuilder(); StringRuneEnumerator runes = text.EnumerateRunes(); var lastRune = new Rune(); while (runes.MoveNext()) - if (console.SupportsComplexEmoji) + if (supportsComplexEmoji) { if (lastRune.Value == Codepoints.ZWJ || lastRune.Value == Codepoints.ORC || diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index f44b9183..6b90490d 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -1,7 +1,9 @@ using System; +using Avalonia; using Avalonia.Media.TextFormatting; using Avalonia.Platform; using Consolonia.Core.Helpers; +using Consolonia.Core.Infrastructure; namespace Consolonia.Core.Text { @@ -9,7 +11,8 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - var glyphs = text.Span.ToString().GetGlyphs(); + var console = AvaloniaLocator.Current.GetService(); + var glyphs = text.Span.ToString().GetGlyphs(console.SupportsComplexEmoji); var shapedBuffer = new ShapedBuffer(text, glyphs.Count, options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); diff --git a/src/Tests/Consolonia.Core.Tests/Assembly.cs b/src/Tests/Consolonia.Core.Tests/Assembly.cs index e39f450e..16870199 100644 --- a/src/Tests/Consolonia.Core.Tests/Assembly.cs +++ b/src/Tests/Consolonia.Core.Tests/Assembly.cs @@ -1,3 +1,23 @@ using System; +using Avalonia; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Infrastructure; +using Consolonia.NUnit; +using NUnit.Framework; -[assembly: CLSCompliant(false)] //todo: should we make it compliant? \ No newline at end of file +[assembly: CLSCompliant(false)] //todo: should we make it compliant? + +namespace Consolonia.Core.Tests +{ + [SetUpFixture] + public class AllTests + { + [OneTimeSetUp] + public void OneTimeSetUp() + { + AvaloniaLocator.Current = new AvaloniaLocator() + .Bind().ToConstant(new UnitTestConsole(new PixelBufferSize(100, 100))); + } + + } +} diff --git a/src/Tests/Consolonia.Core.Tests/ColorTests.cs b/src/Tests/Consolonia.Core.Tests/ColorTests.cs index ca789884..b71be7e2 100644 --- a/src/Tests/Consolonia.Core.Tests/ColorTests.cs +++ b/src/Tests/Consolonia.Core.Tests/ColorTests.cs @@ -8,7 +8,7 @@ namespace Consolonia.Core.Tests public class ColorTests { [Test] - public void TestShade() + public void Shade() { Color foreground = Colors.Gray; Color newColor = foreground.Shade(); @@ -18,7 +18,7 @@ public void TestShade() } [Test] - public void TestBrighten() + public void Brighten() { Color foreground = Colors.Gray; Color newColor = foreground.Brighten(); @@ -28,7 +28,7 @@ public void TestBrighten() } [Test] - public void TestRelativeShade() + public void RelativeShade() { Color foreground = Colors.LightGray; Color background = Colors.DarkGray; @@ -46,7 +46,7 @@ public void TestRelativeShade() } [Test] - public void TestRelativeBrighten() + public void RelativeBrighten() { Color foreground = Colors.LightGray; Color background = Colors.DarkGray; @@ -64,7 +64,7 @@ public void TestRelativeBrighten() } [Test] - public void TestShadeBoundaries() + public void ShadeBoundaries() { Color foreground = Colors.White; Color background = Colors.Black; @@ -98,7 +98,7 @@ public void TestShadeBoundaries() } [Test] - public void TestBrightenBoundaries() + public void BrightenBoundaries() { Color foreground = Colors.Black; Color background = Colors.Black; diff --git a/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj index a237a748..178db2be 100644 --- a/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj +++ b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index f164c7e4..03dbd5d7 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -1,3 +1,4 @@ +using System.Diagnostics; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -8,19 +9,58 @@ namespace Consolonia.Core.Tests public class DrawingBoxSymbolTests { [Test] - public void DrawingBoxSymbolConstructor() + public void Constructor() { ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); - Assert.That(symbol.GetCharacter(), Is.EqualTo('┼')); + Assert.That(symbol.Text, Is.EqualTo("┼")); } [Test] - public void DrawingBoxSymbolBlend() + public void Blend() { - ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); - ISymbol symbolAbove = new DrawingBoxSymbol(0b1111_0000); + ISymbol symbol = new DrawingBoxSymbol(0b0000_0101); + Assert.That(symbol.Text, Is.EqualTo("─")); + ISymbol symbolAbove = new DrawingBoxSymbol(0b0000_1010); + Assert.That(symbolAbove.Text, Is.EqualTo("│")); ISymbol newSymbol = symbol.Blend(ref symbolAbove); - Assert.That(newSymbol.GetCharacter(), Is.EqualTo('╬')); + Assert.That(newSymbol.Text, Is.EqualTo("┼")); + } + + [Test] + public void BlendAllSymbols() + { + var symbols = new (byte, string)[] + { + (0b0000_0000, " "), + (0b0000_0001, "╵"), + (0b0000_0010, "╶"), + (0b0000_0011, "└"), + (0b0000_0100, "╷"), + (0b0000_0101, "│"), + (0b0000_0110, "┌"), + (0b0000_0111, "├"), + (0b0000_1000, "╴"), + (0b0000_1001, "┘"), + (0b0000_1010, "─"), + (0b0000_1011, "┴"), + (0b0000_1100, "┐"), + (0b0000_1101, "┤"), + (0b0000_1110, "┬"), + (0b0000_1111, "┼") + }; + + foreach (var (code1, text1) in symbols) + { + foreach (var (code2, text2) in symbols) + { + ISymbol symbol1 = new DrawingBoxSymbol(code1); + ISymbol symbol2 = new DrawingBoxSymbol(code2); + ISymbol blendedSymbol = symbol1.Blend(ref symbol2); + if (symbol1.Text != symbol2.Text) + Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); + Assert.That(blendedSymbol.Text, Is.Not.Null); + } + } } } diff --git a/src/Tests/Consolonia.Core.Tests/GlyphTests.cs b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs new file mode 100644 index 00000000..52824ac6 --- /dev/null +++ b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs @@ -0,0 +1,109 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Helpers; +using Consolonia.Core.Text; +using NUnit.Framework; + +namespace Consolonia.Core.Tests +{ + [TestFixture] + public class GlyphTests + { + [Test] + public void GetGlyphsEmptyStringReturnsEmptyList() + { + var text = string.Empty; + var glyphs = text.GetGlyphs(true); + Assert.IsEmpty(glyphs); + } + + [Test] + public void GetGlyphsSingleCharacterReturnsSingleGlyph() + { + var text = "a"; + Assert.AreEqual(1, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(1, glyphs.Count); + Assert.AreEqual("a", glyphs[0]); + } + + [Test] + public void GetGlyphsMultipleCharsReturnsMultipleGlyph() + { + var text = "hello"; + Assert.AreEqual(5, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(5, glyphs.Count); + Assert.AreEqual("h", glyphs[0]); + Assert.AreEqual("e", glyphs[1]); + Assert.AreEqual("l", glyphs[2]); + Assert.AreEqual("l", glyphs[3]); + Assert.AreEqual("o", glyphs[4]); + } + + [Test] + public void GetGlyphsComplexCharsReturnsSingleGlyph() + { + var text = "𝔉𝔞𝔫𝔠𝔶"; + Assert.AreEqual(5, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(5, glyphs.Count); + Assert.AreEqual("𝔉", glyphs[0]); + Assert.AreEqual("𝔞", glyphs[1]); + Assert.AreEqual("𝔫", glyphs[2]); + Assert.AreEqual("𝔠", glyphs[3]); + Assert.AreEqual("𝔶", glyphs[4]); + } + + [Test] + public void GetGlyphsSingleEmojiReturnsSingleGlyph() + { + var text = "👍"; + Assert.AreEqual(2, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(1, glyphs.Count); + Assert.AreEqual("👍", glyphs[0]); + } + + [Test] + public void GetGlyphsWithComplexEmoji() + { + var text = "👨‍👩‍👧‍👦"; + Assert.AreEqual(2, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(1, glyphs.Count); + Assert.AreEqual("👨‍👩‍👧‍👦", glyphs[0]); + } + + [Test] + public void GetGlyphsWithMultipleGlyphs() + { + var text = "a👍"; + Assert.AreEqual(3, text.MeasureText()); + + var glyphs = text.GetGlyphs(true); + Assert.AreEqual(2, glyphs.Count); + Assert.AreEqual("a", glyphs[0]); + Assert.AreEqual("👍", glyphs[1]); + } + + [Test] + public void GetGlyphsWithOutComplexEmojiSupport() + { + var text = "👨‍👩‍👧‍👦"; + Assert.AreEqual(2, text.MeasureText()); + + var glyphs = text.GetGlyphs(false); + Assert.AreEqual(4, glyphs.Count); + Assert.AreEqual("👨", glyphs[0]); + Assert.AreEqual("👩", glyphs[1]); + Assert.AreEqual("👧", glyphs[2]); + Assert.AreEqual("👦", glyphs[3]); + } + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index 9a68085f..8cfc8bf9 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -7,6 +7,20 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelBackgroundTests { - + [Test] + public void Constructor() + { + var pixelBackground = new PixelBackground(Colors.Red); + Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); + Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); + } + + [Test] + public void ConstructorWithMode() + { + var pixelBackground = new PixelBackground(PixelBackgroundMode.Shaded, Colors.Red); + Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); + Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Shaded)); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index 584b2a53..29fb2a14 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -1,3 +1,5 @@ +using System.Linq; +using System.Text; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -7,6 +9,105 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelForegroundTests { + [Test] + public void ConstructorWithSymbol() + { + var symbol = new SimpleSymbol('a'); + 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.TextDecorations, Is.Null); + } + [Test] + public void ConstructorWithSymbolAndWeight() + { + var symbol = new SimpleSymbol('a'); + var pixelForeground = new PixelForeground(symbol, Colors.Red, FontWeight.Bold); + 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.TextDecorations, Is.Null); + } + + [Test] + public void ConstructorWithSymbolAndStyle() + { + var symbol = new SimpleSymbol('a'); + 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.That(pixelForeground.Style, Is.EqualTo(FontStyle.Italic)); + Assert.That(pixelForeground.TextDecorations, Is.Null); + } + + [Test] + public void ConstructorWithSymbolAndTextDecorations() + { + var symbol = new SimpleSymbol('a'); + var textDecorations = TextDecorations.Underline; + var pixelForeground = new PixelForeground(symbol, Colors.Red, textDecorations: textDecorations); + 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.TextDecorations, Is.EqualTo(TextDecorations.Underline)); + } + + [Test] + public void ConstructorWithWideCharacter() + { + var rune = "🎵".EnumerateRunes().First(); + var symbol = new SimpleSymbol(rune); + 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.TextDecorations, Is.Null); + } + + [Test] + public void Blend() + { + var symbol = new SimpleSymbol('a'); + var pixelForeground = new PixelForeground(symbol, Colors.Red); + var symbolAbove = new SimpleSymbol('b'); + var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue); + var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); + Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("b")); + } + + [Test] + public void BlendComplex() + { + var symbol = new SimpleSymbol('a'); + var pixelForeground = new PixelForeground(symbol, Colors.Red, FontWeight.Light, FontStyle.Normal, TextDecorations.Strikethrough); + var symbolAbove = new SimpleSymbol('b'); + var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue, FontWeight.Bold, FontStyle.Italic, TextDecorations.Underline); + var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); + Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("b")); + Assert.That(newPixelForeground.Weight, Is.EqualTo(FontWeight.Bold)); + Assert.That(newPixelForeground.Style, Is.EqualTo(FontStyle.Italic)); + Assert.That(newPixelForeground.TextDecorations, Is.EqualTo(TextDecorations.Underline)); + } + + [Test] + public void BlendEmoji() + { + var symbol = new SimpleSymbol("🎵"); + var pixelForeground = new PixelForeground(symbol, Colors.Red); + var symbolAbove = new SimpleSymbol("🎶"); + var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue); + var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); + Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("🎶")); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index 725aee0f..f82c5c4c 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -1,5 +1,6 @@ using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.NUnit; using NUnit.Framework; namespace Consolonia.Core.Tests @@ -7,19 +8,20 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelTests { + [Test] - public void PixelConstructorColorOnly() + public void ConstructorColorOnly() { - Pixel pixel = new Pixel(Colors.Red); + Pixel pixel = new Pixel(new PixelBackground(Colors.Red)); Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Red)); Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } [Test] - public void PixelConstructorColorAndSymbol() + public void ConstructorColorAndSymbol() { - Pixel pixel = new Pixel(new SimpleSymbol('a'), Colors.Red); - Assert.That(pixel.Foreground.Symbol.GetCharacter(), Is.EqualTo('a')); + Pixel pixel = new Pixel(new SimpleSymbol("a"), Colors.Red); + Assert.That(pixel.Foreground.Symbol.Text, Is.EqualTo("a")); 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)); @@ -29,10 +31,10 @@ public void PixelConstructorColorAndSymbol() } [Test] - public void PixelConstructorDrawingBoxSymbol() + public void ConstructorDrawingBoxSymbol() { - Pixel pixel = new Pixel(0b0000_1111, Colors.Red); - Assert.That(pixel.Foreground.Symbol.GetCharacter(), Is.EqualTo('┼')); + Pixel pixel = new Pixel(new DrawingBoxSymbol(0b0000_1111), Colors.Red); + 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)); @@ -42,10 +44,10 @@ public void PixelConstructorDrawingBoxSymbol() } [Test] - public void PixelConstructorDrawingBoxSymbolAndColor() + public void ConstructorDrawingBoxSymbolAndColor() { Pixel pixel = new Pixel(new PixelForeground(new DrawingBoxSymbol(0b0000_1111), color: Colors.Red), new PixelBackground(Colors.Blue)); - Assert.That(pixel.Foreground.Symbol.GetCharacter(), Is.EqualTo('┼')); + 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)); @@ -53,5 +55,27 @@ public void PixelConstructorDrawingBoxSymbolAndColor() Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Blue)); Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } + + [Test] + public void BlendTransparentBackground() + { + Pixel pixel = new Pixel(new PixelBackground(Colors.Green)); + Pixel pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), new PixelBackground(Colors.Transparent)); + Pixel newPixel = pixel.Blend(pixel2); + Assert.That(newPixel.Foreground.Symbol.Text, Is.EqualTo("a")); + Assert.That(newPixel.Foreground.Color, Is.EqualTo(Colors.Red)); + Assert.That(newPixel.Background.Color, Is.EqualTo(Colors.Green)); + } + + [Test] + public void BlendColoredBackground() + { + Pixel pixel = new Pixel(new PixelBackground(Colors.Green)); + Pixel pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), new PixelBackground(Colors.Blue)); + Pixel newPixel = pixel.Blend(pixel2); + Assert.That(newPixel.Foreground.Symbol.Text, Is.EqualTo("a")); + Assert.That(newPixel.Foreground.Color, Is.EqualTo(Colors.Red)); + Assert.That(newPixel.Background.Color, Is.EqualTo(Colors.Blue)); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index c0fa3848..1bb80f00 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -1,5 +1,10 @@ +using System.Linq; +using System.Text; +using Avalonia; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Infrastructure; +using Consolonia.NUnit; using NUnit.Framework; namespace Consolonia.Core.Tests @@ -7,46 +12,94 @@ namespace Consolonia.Core.Tests [TestFixture] public class SimpleSymbolTests { + [Test] - public void CharConstructor() + public void ConstructorChar() { ISymbol symbol = new SimpleSymbol('a'); - Assert.That(symbol.GetCharacter(), Is.EqualTo('a')); + Assert.That(symbol.Text, Is.EqualTo("a")); + Assert.That(symbol.Width, Is.EqualTo(1)); } [Test] - public void SimpleSymbolIsWhiteSpace() + public void ConstructorString() { - Assert.That(new SimpleSymbol('\0').IsWhiteSpace(), Is.True); - Assert.That(new SimpleSymbol(' ').IsWhiteSpace(), Is.False); - Assert.That(new SimpleSymbol('a').IsWhiteSpace(), Is.False); + ISymbol symbol = new SimpleSymbol("a"); + Assert.That(symbol.Text, Is.EqualTo("a")); + Assert.That(symbol.Width, Is.EqualTo(1)); } [Test] - public void SimpleSymbolBlend() + public void ConstructorRune() { - ISymbol symbol = new SimpleSymbol('a'); - ISymbol symbolAbove = new SimpleSymbol('b'); + ISymbol symbol = new SimpleSymbol(new Rune('a')); + Assert.That(symbol.Text, Is.EqualTo("a")); + Assert.That(symbol.Width, Is.EqualTo(1)); + } + + [Test] + public void ConstructorRuneEmoji() + { + var rune = "👍".EnumerateRunes().First(); + ISymbol symbol = new SimpleSymbol(rune); + Assert.That(symbol.Text, Is.EqualTo("👍")); + Assert.That(symbol.Width, Is.EqualTo(2)); + } + + [Test] + public void ConstructorComplexEmoji() + { + ISymbol symbol = new SimpleSymbol("👨‍👩‍👧‍👦"); + Assert.That(symbol.Text, Is.EqualTo("👨‍👩‍👧‍👦")); + Assert.That(symbol.Width, Is.EqualTo(2)); + } + + [Test] + public void IsWhiteSpace() + { + Assert.That(new SimpleSymbol(string.Empty).IsWhiteSpace(), Is.True); + Assert.That(new SimpleSymbol(" ").IsWhiteSpace(), Is.False); + Assert.That(new SimpleSymbol("a").IsWhiteSpace(), Is.False); + } + + [Test] + public void Blend() + { + ISymbol symbol = new SimpleSymbol("a"); + ISymbol symbolAbove = new SimpleSymbol("b"); ISymbol newSymbol = symbol.Blend(ref symbolAbove); - Assert.That(newSymbol.GetCharacter(), Is.EqualTo('b')); + Assert.That(newSymbol.Text, Is.EqualTo("b")); + Assert.That(newSymbol.Width, Is.EqualTo(1)); } [Test] - public void SimpleSymbolBlendWithWhiteSpace() + public void BlendWithWhiteSpace() { - ISymbol symbol = new SimpleSymbol(' '); - ISymbol symbolAbove = new SimpleSymbol('b'); + ISymbol symbol = new SimpleSymbol(" "); + ISymbol symbolAbove = new SimpleSymbol("b"); ISymbol newSymbol = symbol.Blend(ref symbolAbove); - Assert.That(newSymbol.GetCharacter(), Is.EqualTo('b')); + Assert.That(newSymbol.Text, Is.EqualTo("b")); + Assert.That(newSymbol.Width, Is.EqualTo(1)); } [Test] - public void SimpleSymbolBlendWithEmpty() + public void BlendWithEmpty() { - ISymbol symbol = new SimpleSymbol('a'); - ISymbol symbolAbove = new SimpleSymbol('\0'); + ISymbol symbol = new SimpleSymbol("a"); + ISymbol symbolAbove = new SimpleSymbol(string.Empty); + ISymbol newSymbol = symbol.Blend(ref symbolAbove); + Assert.That(newSymbol.Text, Is.EqualTo("a")); + Assert.That(newSymbol.Width, Is.EqualTo(1)); + } + + [Test] + public void BlendWithEmoji() + { + ISymbol symbol = new SimpleSymbol("a"); + ISymbol symbolAbove = new SimpleSymbol("👍"); ISymbol newSymbol = symbol.Blend(ref symbolAbove); - Assert.That(newSymbol.GetCharacter(), Is.EqualTo('a')); + Assert.That(newSymbol.Text, Is.EqualTo("👍")); + Assert.That(newSymbol.Width, Is.EqualTo(2)); } } } \ No newline at end of file From 7658c8c0f154ac50db7807b3c87000b312dbc721 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 14:09:42 -0800 Subject: [PATCH 33/48] lint fixes --- src/Consolonia.Core/Helpers/Extensions.cs | 3 ++- src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs | 5 ++--- src/Tests/Consolonia.Core.Tests/GlyphTests.cs | 3 --- src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs | 1 - src/Tests/Consolonia.Core.Tests/PixelTests.cs | 1 - .../Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs | 2 -- 6 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 45c5b5f7..6ac24e9c 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -34,7 +34,8 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix /// Process text into collection of glyphs where a glyph is either text or a combination of chars which make up an /// emoji. /// - /// + /// text to get glyphs from + /// /// public static IReadOnlyList GetGlyphs(this string text, bool supportsComplexEmoji) { diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index 03dbd5d7..6cf315d5 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -49,9 +48,9 @@ public void BlendAllSymbols() (0b0000_1111, "┼") }; - foreach (var (code1, text1) in symbols) + foreach (var (code1, _) in symbols) { - foreach (var (code2, text2) in symbols) + foreach (var (code2, _) in symbols) { ISymbol symbol1 = new DrawingBoxSymbol(code1); ISymbol symbol2 = new DrawingBoxSymbol(code2); diff --git a/src/Tests/Consolonia.Core.Tests/GlyphTests.cs b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs index 52824ac6..87740604 100644 --- a/src/Tests/Consolonia.Core.Tests/GlyphTests.cs +++ b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs @@ -1,7 +1,4 @@ -using Avalonia.Media; -using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Helpers; -using Consolonia.Core.Text; using NUnit.Framework; namespace Consolonia.Core.Tests diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index 29fb2a14..19b1746c 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Text; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index f82c5c4c..55110c4c 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -1,6 +1,5 @@ using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; -using Consolonia.NUnit; using NUnit.Framework; namespace Consolonia.Core.Tests diff --git a/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs b/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs index b940dcaa..86813e8f 100644 --- a/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs +++ b/src/Tests/Consolonia.Gallery.Tests/Base/GalleryTestsBaseBase.cs @@ -1,10 +1,8 @@ using System.Threading.Tasks; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Input; using Avalonia.Threading; using Consolonia.Core.Drawing.PixelBufferImplementation; -using Consolonia.Gallery; using Consolonia.Gallery.View; using Consolonia.NUnit; using NUnit.Framework; From 19c4e98580dbfb82a902ed6aed035915d9315a76 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 14:14:32 -0800 Subject: [PATCH 34/48] fix typo --- src/Consolonia.Core/Helpers/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 6ac24e9c..826ca9b8 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -35,7 +35,7 @@ public static void Print(this IConsole console, PixelBufferCoordinate point, Pix /// emoji. /// /// text to get glyphs from - /// + /// If true, emojis like 👨‍👩‍👧‍👦 will be treated as a single glyph> /// public static IReadOnlyList GetGlyphs(this string text, bool supportsComplexEmoji) { From 0aa8d6b156eaec88e39fe05ddfa2fbbce5b00654 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 14:15:43 -0800 Subject: [PATCH 35/48] more using --- src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index 1bb80f00..8b2e9cf0 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -1,10 +1,6 @@ using System.Linq; using System.Text; -using Avalonia; -using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; -using Consolonia.Core.Infrastructure; -using Consolonia.NUnit; using NUnit.Framework; namespace Consolonia.Core.Tests From ee434d69ea094e6210276f8542819d984d77f01b Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 15:18:11 -0800 Subject: [PATCH 36/48] feedback from taskrabbit --- src/Consolonia.Core/Text/TextShaper.cs | 3 ++- .../Consolonia.Core.Tests.csproj | 1 - .../Consolonia.Core.Tests/PixelBackgroundTests.cs | 14 +++++++++----- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index 6b90490d..e612dbfd 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -11,7 +11,8 @@ public class TextShaper : ITextShaperImpl { public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions options) { - var console = AvaloniaLocator.Current.GetService(); + var console = AvaloniaLocator.Current.GetRequiredService(); + var glyphs = text.Span.ToString().GetGlyphs(console.SupportsComplexEmoji); var shapedBuffer = new ShapedBuffer(text, glyphs.Count, diff --git a/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj index 178db2be..a237a748 100644 --- a/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj +++ b/src/Tests/Consolonia.Core.Tests/Consolonia.Core.Tests.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index 8cfc8bf9..a102649c 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -1,6 +1,8 @@ using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; +using NUnit.Framework.Internal; +using static Unix.Terminal.Delegates; namespace Consolonia.Core.Tests { @@ -14,13 +16,15 @@ public void Constructor() Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } - - [Test] - public void ConstructorWithMode() + + [TestCase(PixelBackgroundMode.Transparent)] + [TestCase(PixelBackgroundMode.Colored)] + [TestCase(PixelBackgroundMode.Shaded)] + public void ConstructorWithMode(PixelBackgroundMode mode) { - var pixelBackground = new PixelBackground(PixelBackgroundMode.Shaded, Colors.Red); + var pixelBackground = new PixelBackground(mode, Colors.Red); Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); - Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Shaded)); + Assert.That(pixelBackground.Mode, Is.EqualTo(mode)); } } } \ No newline at end of file From de050260b1c2b7c7f376fefc40622c3a3f4cdc04 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 15:22:35 -0800 Subject: [PATCH 37/48] d --- src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index a102649c..b4017d32 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -2,7 +2,6 @@ using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; using NUnit.Framework.Internal; -using static Unix.Terminal.Delegates; namespace Consolonia.Core.Tests { From 89b24bd6891359bdfe8400e4b20a25a81c49514b Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 15:36:47 -0800 Subject: [PATCH 38/48] Remove this NUnit.Internal This is suggested by CodeRabbit, doesn't show up as a unused reference in visual studio but the linter says it's unused. --- src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index b4017d32..e7bb7456 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -1,7 +1,6 @@ using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; -using NUnit.Framework.Internal; namespace Consolonia.Core.Tests { From 66553d443e5aeee6a78969d1abdff97c74e7da2c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 9 Nov 2024 23:40:06 +0000 Subject: [PATCH 39/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Drawing/DrawingContextImpl.cs | 2 +- .../DrawingBoxSymbol.cs | 8 +++---- .../PixelBufferImplementation/Pixel.cs | 22 +++++++++---------- .../PixelForeground.cs | 3 ++- src/Consolonia.Core/Drawing/RenderTarget.cs | 7 +++--- src/Consolonia.NUnit/UnitTestConsole.cs | 2 +- src/Tests/Consolonia.Core.Tests/Assembly.cs | 3 +-- .../DrawingBoxSymbolTests.cs | 21 ++++++++---------- src/Tests/Consolonia.Core.Tests/GlyphTests.cs | 16 +++++++------- .../PixelBackgroundTests.cs | 2 +- .../PixelForegroundTests.cs | 17 ++++++++------ src/Tests/Consolonia.Core.Tests/PixelTests.cs | 20 +++++++++-------- .../SimpleSymbolTests.cs | 3 +-- 13 files changed, 64 insertions(+), 62 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index f036906e..6b6f8c10 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -95,7 +95,7 @@ public void DrawBitmap(IBitmapImpl source, double opacity, Rect sourceRect, Rect Color background = GetBackgroundColorForQuadPixel(quadColors, quadPixel); var imagePixel = new Pixel( - new PixelForeground(new SimpleSymbol(quadPixel), color: foreground), + new PixelForeground(new SimpleSymbol(quadPixel), foreground), new PixelBackground(background)); CurrentClip.ExecuteWithClipping(new Point(px, py), () => diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 1cea2396..688a0c64 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -34,7 +34,7 @@ private static char GetBoxSymbol(byte upRightDownLeft) byte leftPart = (byte)(upRightDownLeft & 0b1111_0000); bool hasLeftPart = leftPart > 0; - switch (upRightDownLeft & 0b0000_1111) + switch (upRightDownLeft & 0b0000_1111) { case 0b0000_1000: case 0b0000_0010: @@ -107,12 +107,12 @@ public ISymbol Blend(ref ISymbol symbolAbove) { if (symbolAbove.IsWhiteSpace()) return this; - if (symbolAbove is not DrawingBoxSymbol drawingBoxSymbol) + if (symbolAbove is not DrawingBoxSymbol drawingBoxSymbol) return symbolAbove; - + if (drawingBoxSymbol._upRightDownLeft == BoldSymbol || _upRightDownLeft == BoldSymbol) return new DrawingBoxSymbol(BoldSymbol); - + return new DrawingBoxSymbol((byte)(_upRightDownLeft | drawingBoxSymbol._upRightDownLeft)); } diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index c4c3ae52..8d6e4222 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -17,17 +17,17 @@ public readonly struct Pixel public bool IsCaret { get; } /// - /// Make a pixel foreground with transparent background + /// Make a pixel foreground with transparent background /// /// /// /// /// /// - public Pixel(ISymbol symbol, - Color foregroundColor, + public Pixel(ISymbol symbol, + Color foregroundColor, FontStyle style = FontStyle.Normal, - FontWeight weight = FontWeight.Normal, + FontWeight weight = FontWeight.Normal, TextDecorationCollection textDecorations = null) : this( new PixelForeground(symbol, foregroundColor, weight, style, textDecorations), new PixelBackground(PixelBackgroundMode.Transparent)) @@ -35,23 +35,23 @@ public Pixel(ISymbol symbol, } /// - /// Make a pixel with only background color, but no foreground + /// Make a pixel with only background color, but no foreground /// /// - public Pixel(PixelBackground background) : + public Pixel(PixelBackground background) : this(new PixelForeground(new SimpleSymbol(), Colors.Transparent), - background) + background) { } /// - /// Make a pixel with foreground and background + /// Make a pixel with foreground and background /// /// /// /// - public Pixel(PixelForeground foreground, - PixelBackground background, + public Pixel(PixelForeground foreground, + PixelBackground background, bool isCaret = false) { Foreground = foreground; @@ -60,7 +60,7 @@ public Pixel(PixelForeground foreground, } /// - /// Blend the pixelAbove with the this pixel. + /// Blend the pixelAbove with the this pixel. /// /// /// diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index f914a812..ba5e52cf 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -8,7 +8,8 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public readonly struct PixelForeground { public PixelForeground(ISymbol symbol, Color color, - FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null) + FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, + TextDecorationCollection textDecorations = null) { ArgumentNullException.ThrowIfNull(symbol); Symbol = symbol; diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 498e3b4e..aaea2e11 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -130,10 +130,11 @@ private void RenderToDevice() "All pixels in the buffer must have exact console color before rendering"); - var pixelSpread = (pixel.Background.Color, - pixel.Foreground.Color, + (Color, Color, FontWeight Weight, FontStyle Style, TextDecorationCollection TextDecorations, string Text + ) pixelSpread = (pixel.Background.Color, + pixel.Foreground.Color, pixel.Foreground.Weight, - pixel.Foreground.Style, + pixel.Foreground.Style, pixel.Foreground.TextDecorations, pixel.Foreground.Symbol.Text); diff --git a/src/Consolonia.NUnit/UnitTestConsole.cs b/src/Consolonia.NUnit/UnitTestConsole.cs index 30754759..a74040ac 100644 --- a/src/Consolonia.NUnit/UnitTestConsole.cs +++ b/src/Consolonia.NUnit/UnitTestConsole.cs @@ -66,7 +66,7 @@ void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color f PixelBuffer.Set(new PixelBufferCoordinate((ushort)(x + i), y), _ => // ReSharper disable once AccessToModifiedClosure we are sure about inline execution new Pixel( - new PixelForeground(new SimpleSymbol(rune), color: foreground, style: style, weight: weight, + new PixelForeground(new SimpleSymbol(rune), foreground, style: style, weight: weight, textDecorations: textDecorations), new PixelBackground(PixelBackgroundMode.Colored, background))); i++; diff --git a/src/Tests/Consolonia.Core.Tests/Assembly.cs b/src/Tests/Consolonia.Core.Tests/Assembly.cs index 16870199..5d141236 100644 --- a/src/Tests/Consolonia.Core.Tests/Assembly.cs +++ b/src/Tests/Consolonia.Core.Tests/Assembly.cs @@ -18,6 +18,5 @@ public void OneTimeSetUp() AvaloniaLocator.Current = new AvaloniaLocator() .Bind().ToConstant(new UnitTestConsole(new PixelBufferSize(100, 100))); } - } -} +} \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index 6cf315d5..8ec43a4f 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -48,19 +48,16 @@ public void BlendAllSymbols() (0b0000_1111, "┼") }; - foreach (var (code1, _) in symbols) + foreach ((byte code1, string _) in symbols) + foreach ((byte code2, string _) in symbols) { - foreach (var (code2, _) in symbols) - { - ISymbol symbol1 = new DrawingBoxSymbol(code1); - ISymbol symbol2 = new DrawingBoxSymbol(code2); - ISymbol blendedSymbol = symbol1.Blend(ref symbol2); - if (symbol1.Text != symbol2.Text) - Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); - Assert.That(blendedSymbol.Text, Is.Not.Null); - } + ISymbol symbol1 = new DrawingBoxSymbol(code1); + ISymbol symbol2 = new DrawingBoxSymbol(code2); + ISymbol blendedSymbol = symbol1.Blend(ref symbol2); + if (symbol1.Text != symbol2.Text) + Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); + Assert.That(blendedSymbol.Text, Is.Not.Null); } } - - } + } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/GlyphTests.cs b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs index 87740604..551bfb8e 100644 --- a/src/Tests/Consolonia.Core.Tests/GlyphTests.cs +++ b/src/Tests/Consolonia.Core.Tests/GlyphTests.cs @@ -9,7 +9,7 @@ public class GlyphTests [Test] public void GetGlyphsEmptyStringReturnsEmptyList() { - var text = string.Empty; + string text = string.Empty; var glyphs = text.GetGlyphs(true); Assert.IsEmpty(glyphs); } @@ -17,7 +17,7 @@ public void GetGlyphsEmptyStringReturnsEmptyList() [Test] public void GetGlyphsSingleCharacterReturnsSingleGlyph() { - var text = "a"; + string text = "a"; Assert.AreEqual(1, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -28,7 +28,7 @@ public void GetGlyphsSingleCharacterReturnsSingleGlyph() [Test] public void GetGlyphsMultipleCharsReturnsMultipleGlyph() { - var text = "hello"; + string text = "hello"; Assert.AreEqual(5, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -43,7 +43,7 @@ public void GetGlyphsMultipleCharsReturnsMultipleGlyph() [Test] public void GetGlyphsComplexCharsReturnsSingleGlyph() { - var text = "𝔉𝔞𝔫𝔠𝔶"; + string text = "𝔉𝔞𝔫𝔠𝔶"; Assert.AreEqual(5, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -58,7 +58,7 @@ public void GetGlyphsComplexCharsReturnsSingleGlyph() [Test] public void GetGlyphsSingleEmojiReturnsSingleGlyph() { - var text = "👍"; + string text = "👍"; Assert.AreEqual(2, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -69,7 +69,7 @@ public void GetGlyphsSingleEmojiReturnsSingleGlyph() [Test] public void GetGlyphsWithComplexEmoji() { - var text = "👨‍👩‍👧‍👦"; + string text = "👨‍👩‍👧‍👦"; Assert.AreEqual(2, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -80,7 +80,7 @@ public void GetGlyphsWithComplexEmoji() [Test] public void GetGlyphsWithMultipleGlyphs() { - var text = "a👍"; + string text = "a👍"; Assert.AreEqual(3, text.MeasureText()); var glyphs = text.GetGlyphs(true); @@ -92,7 +92,7 @@ public void GetGlyphsWithMultipleGlyphs() [Test] public void GetGlyphsWithOutComplexEmojiSupport() { - var text = "👨‍👩‍👧‍👦"; + string text = "👨‍👩‍👧‍👦"; Assert.AreEqual(2, text.MeasureText()); var glyphs = text.GetGlyphs(false); diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index e7bb7456..414d34d3 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -14,7 +14,7 @@ public void Constructor() Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } - + [TestCase(PixelBackgroundMode.Transparent)] [TestCase(PixelBackgroundMode.Colored)] [TestCase(PixelBackgroundMode.Shaded)] diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index 19b1746c..7cc87873 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Text; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -48,7 +49,7 @@ public void ConstructorWithSymbolAndStyle() public void ConstructorWithSymbolAndTextDecorations() { var symbol = new SimpleSymbol('a'); - var textDecorations = TextDecorations.Underline; + TextDecorationCollection textDecorations = TextDecorations.Underline; var pixelForeground = new PixelForeground(symbol, Colors.Red, textDecorations: textDecorations); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixelForeground.Symbol.Text, Is.EqualTo("a")); @@ -60,7 +61,7 @@ public void ConstructorWithSymbolAndTextDecorations() [Test] public void ConstructorWithWideCharacter() { - var rune = "🎵".EnumerateRunes().First(); + Rune rune = "🎵".EnumerateRunes().First(); var symbol = new SimpleSymbol(rune); var pixelForeground = new PixelForeground(symbol, Colors.Red); Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Red)); @@ -77,7 +78,7 @@ public void Blend() var pixelForeground = new PixelForeground(symbol, Colors.Red); var symbolAbove = new SimpleSymbol('b'); var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue); - var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + PixelForeground newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("b")); } @@ -86,10 +87,12 @@ public void Blend() public void BlendComplex() { var symbol = new SimpleSymbol('a'); - var pixelForeground = new PixelForeground(symbol, Colors.Red, FontWeight.Light, FontStyle.Normal, TextDecorations.Strikethrough); + var pixelForeground = new PixelForeground(symbol, Colors.Red, FontWeight.Light, FontStyle.Normal, + TextDecorations.Strikethrough); var symbolAbove = new SimpleSymbol('b'); - var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue, FontWeight.Bold, FontStyle.Italic, TextDecorations.Underline); - var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue, FontWeight.Bold, FontStyle.Italic, + TextDecorations.Underline); + PixelForeground newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("b")); Assert.That(newPixelForeground.Weight, Is.EqualTo(FontWeight.Bold)); @@ -104,7 +107,7 @@ public void BlendEmoji() var pixelForeground = new PixelForeground(symbol, Colors.Red); var symbolAbove = new SimpleSymbol("🎶"); var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue); - var newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); + PixelForeground newPixelForeground = pixelForeground.Blend(pixelForegroundAbove); Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("🎶")); } diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index 55110c4c..20ecc8e3 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -7,11 +7,10 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelTests { - [Test] public void ConstructorColorOnly() { - Pixel pixel = new Pixel(new PixelBackground(Colors.Red)); + var pixel = new Pixel(new PixelBackground(Colors.Red)); Assert.That(pixel.Background.Color, Is.EqualTo(Colors.Red)); Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } @@ -19,7 +18,7 @@ public void ConstructorColorOnly() [Test] public void ConstructorColorAndSymbol() { - Pixel pixel = new Pixel(new SimpleSymbol("a"), Colors.Red); + var pixel = new Pixel(new SimpleSymbol("a"), Colors.Red); Assert.That(pixel.Foreground.Symbol.Text, Is.EqualTo("a")); Assert.That(pixel.Foreground.Color, Is.EqualTo(Colors.Red)); Assert.That(pixel.Foreground.Style, Is.EqualTo(FontStyle.Normal)); @@ -32,7 +31,7 @@ public void ConstructorColorAndSymbol() [Test] public void ConstructorDrawingBoxSymbol() { - Pixel pixel = new Pixel(new DrawingBoxSymbol(0b0000_1111), Colors.Red); + var pixel = new Pixel(new DrawingBoxSymbol(0b0000_1111), Colors.Red); 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)); @@ -45,7 +44,8 @@ public void ConstructorDrawingBoxSymbol() [Test] public void ConstructorDrawingBoxSymbolAndColor() { - Pixel pixel = new Pixel(new PixelForeground(new DrawingBoxSymbol(0b0000_1111), color: Colors.Red), new PixelBackground(Colors.Blue)); + var pixel = new Pixel(new PixelForeground(new DrawingBoxSymbol(0b0000_1111), Colors.Red), + 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)); @@ -58,8 +58,9 @@ public void ConstructorDrawingBoxSymbolAndColor() [Test] public void BlendTransparentBackground() { - Pixel pixel = new Pixel(new PixelBackground(Colors.Green)); - Pixel pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), new PixelBackground(Colors.Transparent)); + var pixel = new Pixel(new PixelBackground(Colors.Green)); + var pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Transparent)); Pixel newPixel = pixel.Blend(pixel2); Assert.That(newPixel.Foreground.Symbol.Text, Is.EqualTo("a")); Assert.That(newPixel.Foreground.Color, Is.EqualTo(Colors.Red)); @@ -69,8 +70,9 @@ public void BlendTransparentBackground() [Test] public void BlendColoredBackground() { - Pixel pixel = new Pixel(new PixelBackground(Colors.Green)); - Pixel pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), new PixelBackground(Colors.Blue)); + var pixel = new Pixel(new PixelBackground(Colors.Green)); + var pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue)); Pixel newPixel = pixel.Blend(pixel2); Assert.That(newPixel.Foreground.Symbol.Text, Is.EqualTo("a")); Assert.That(newPixel.Foreground.Color, Is.EqualTo(Colors.Red)); diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index 8b2e9cf0..b967e5d5 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -8,7 +8,6 @@ namespace Consolonia.Core.Tests [TestFixture] public class SimpleSymbolTests { - [Test] public void ConstructorChar() { @@ -36,7 +35,7 @@ public void ConstructorRune() [Test] public void ConstructorRuneEmoji() { - var rune = "👍".EnumerateRunes().First(); + Rune rune = "👍".EnumerateRunes().First(); ISymbol symbol = new SimpleSymbol(rune); Assert.That(symbol.Text, Is.EqualTo("👍")); Assert.That(symbol.Width, Is.EqualTo(2)); From 7ff4812e9f9a2e81e2de013bf3861c3b58e56d5a Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sat, 9 Nov 2024 18:06:30 -0800 Subject: [PATCH 40/48] implement equality for Pixel, PixelForeground, PixelBackground, and symbols getting rid of grody spread operator on cache Added unit tests for all of things. --- .../DrawingBoxSymbol.cs | 96 +++++++++++-------- .../PixelBufferImplementation/Pixel.cs | 20 +++- .../PixelBackground.cs | 18 +++- .../PixelBufferCoordinate.cs | 5 + .../PixelForeground.cs | 22 ++++- .../PixelBufferImplementation/SimpleSymbol.cs | 21 +++- src/Consolonia.Core/Drawing/RenderTarget.cs | 64 ++++++------- src/Consolonia.Core/Helpers/Extensions.cs | 4 +- .../DrawingBoxSymbolTests.cs | 56 +++++++++-- .../PixelBackgroundTests.cs | 37 ++++++- .../PixelForegroundTests.cs | 29 ++++++ src/Tests/Consolonia.Core.Tests/PixelTests.cs | 32 +++++++ .../SimpleSymbolTests.cs | 38 ++++++++ 13 files changed, 347 insertions(+), 95 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 688a0c64..5f660636 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; namespace Consolonia.Core.Drawing.PixelBufferImplementation { @@ -7,7 +8,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation /// https://en.wikipedia.org/wiki/Box-drawing_character /// [DebuggerDisplay("DrawingBox {Text}")] - public struct DrawingBoxSymbol : ISymbol + public struct DrawingBoxSymbol : ISymbol, IEquatable { // all 0bXXXX_0000 are special values private const byte BoldSymbol = 0b0001_0000; @@ -55,46 +56,46 @@ private static char GetBoxSymbol(byte upRightDownLeft) return horizontal ? '╪' : '╫'; default: - { - return upRightDownLeft switch { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + return upRightDownLeft switch + { + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } @@ -132,5 +133,20 @@ public static DrawingBoxSymbol UpRightDownLeftFromPattern(byte pattern, LineStyl throw new ArgumentOutOfRangeException(nameof(lineStyle), lineStyle, null); } } + + public bool Equals(DrawingBoxSymbol other) + => _upRightDownLeft == other._upRightDownLeft; + + public override bool Equals([NotNullWhen(true)] object obj) + => obj is DrawingBoxSymbol other && this.Equals(other); + + public override int GetHashCode() + => _upRightDownLeft.GetHashCode(); + + public static bool operator ==(DrawingBoxSymbol left, DrawingBoxSymbol right) + => left.Equals(right); + + public static bool operator !=(DrawingBoxSymbol left, DrawingBoxSymbol right) + => !left.Equals(right); } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 8d6e4222..a60d0893 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Avalonia.Media; // ReSharper disable MemberCanBePrivate.Global @@ -8,7 +9,7 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { [DebuggerDisplay("'{Foreground.Symbol.Text}' [{Foreground.Color}, {Background.Color}]")] - public readonly struct Pixel + public readonly struct Pixel : IEquatable { public PixelForeground Foreground { get; } @@ -122,5 +123,22 @@ private static Color MergeColors(Color target, Color source) return new Color(0xFF, red, green, blue); } + + public bool Equals(Pixel other) + => Foreground.Equals(other.Foreground) && + Background.Equals(other.Background) && + IsCaret.Equals(IsCaret); + + public override bool Equals([NotNullWhen(true)] object obj) + => obj is Pixel other && this.Equals(other); + + public override int GetHashCode() + => HashCode.Combine(Foreground, Background, IsCaret); + + public static bool operator ==(Pixel left, Pixel right) + => left.Equals(right); + + public static bool operator !=(Pixel left, Pixel right) + => !left.Equals(right); } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index ee329e52..f2584faf 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -1,11 +1,12 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { [DebuggerDisplay("[{Color}, {Mode}]")] - public readonly struct PixelBackground + public readonly struct PixelBackground : IEquatable { public PixelBackground(Color color) { @@ -44,5 +45,20 @@ public PixelBackground Shade() return new PixelBackground(newMode, newColor); } + + public bool Equals(PixelBackground other) + => Color.Equals(other.Color) && Mode == other.Mode; + + public override bool Equals([NotNullWhen(true)] object obj) + => obj is PixelBackground other && this.Equals(other); + + public override int GetHashCode() + => HashCode.Combine(Color, Mode); + + public static bool operator ==(PixelBackground left, PixelBackground right) + => left.Equals(right); + + public static bool operator !=(PixelBackground left, PixelBackground right) + => !left.Equals(right); } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs index 24da206e..20d6f518 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs @@ -53,5 +53,10 @@ public bool Equals(PixelBufferCoordinate secondPoint) { return X == secondPoint.X && Y == secondPoint.Y; } + + public override int GetHashCode() + { + return X ^ Y; + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index ba5e52cf..c1b51426 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -1,11 +1,12 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { [DebuggerDisplay("'{Symbol.Text}' [{Color}]")] - public readonly struct PixelForeground + public readonly struct PixelForeground : IEquatable { public PixelForeground(ISymbol symbol, Color color, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, @@ -46,5 +47,24 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground) return new PixelForeground(newSymbol, pixelAboveForeground.Color, pixelAboveForeground.Weight, pixelAboveForeground.Style, pixelAboveForeground.TextDecorations); } + + public bool Equals(PixelForeground other) + => Symbol.Equals(other.Symbol) && + Color.Equals(other.Color) && + Weight == other.Weight && + Style == other.Style && + Equals(TextDecorations, other.TextDecorations); + + public override bool Equals([NotNullWhen(true)] object obj) + => obj is PixelForeground other && this.Equals(other); + + public override int GetHashCode() + => HashCode.Combine(Symbol, Color, (int)Weight, (int)Style, TextDecorations); + + public static bool operator ==(PixelForeground left, PixelForeground right) + => left.Equals(right); + + public static bool operator !=(PixelForeground left, PixelForeground right) + => !left.Equals(right); } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index 439c93c9..d19fd456 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -1,11 +1,13 @@ +using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text; using Consolonia.Core.Helpers; namespace Consolonia.Core.Drawing.PixelBufferImplementation { [DebuggerDisplay("'{Text}'")] - public readonly struct SimpleSymbol : ISymbol + public readonly struct SimpleSymbol : ISymbol, IEquatable { public SimpleSymbol() { @@ -44,5 +46,20 @@ public ISymbol Blend(ref ISymbol symbolAbove) { return symbolAbove.IsWhiteSpace() ? this : symbolAbove; } + + public bool Equals(SimpleSymbol other) + => Text.Equals(other.Text, StringComparison.Ordinal); + + public override bool Equals([NotNullWhen(true)] object obj) + => obj is SimpleSymbol other && this.Equals(other); + + public override int GetHashCode() + => Text.GetHashCode(StringComparison.Ordinal); + + public static bool operator ==(SimpleSymbol left, SimpleSymbol right) + => left.Equals(right); + + public static bool operator !=(SimpleSymbol left, SimpleSymbol right) + => !left.Equals(right); } -} \ No newline at end of file +} diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index aaea2e11..17d9c88e 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -20,8 +20,8 @@ internal class RenderTarget : IDrawingContextLayerImpl private PixelBuffer _bufferBuffer; - private (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, string text)?[,] _cache; + // cache of pixels written so we can ignore them if unchanged. + private Pixel?[,] _cache; internal RenderTarget(ConsoleWindow consoleWindow) { @@ -96,9 +96,7 @@ private void InitializeBuffer(Size size) private void InitializeCache(ushort width, ushort height) { - _cache = - new (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection - textDecorations, string text)?[width, height]; + _cache = new Pixel?[width, height]; } private void RenderToDevice() @@ -111,46 +109,38 @@ private void RenderToDevice() var flushingBuffer = new FlushingBuffer(_console); for (ushort y = 0; y < pixelBuffer.Height; y++) - for (ushort x = 0; x < pixelBuffer.Width;) - { - Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; - - if (pixel.IsCaret) + for (ushort x = 0; x < pixelBuffer.Width;) { - if (caretPosition != null) - throw new InvalidOperationException("Caret is already shown"); - caretPosition = new PixelBufferCoordinate(x, y); - } + Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; - /* todo: There is not IWindowImpl.Invalidate anymore. - if (!_consoleWindow.InvalidatedRects.Any(rect => - rect.ContainsExclusive(new Point(x, y)))) continue;*/ - if (pixel.Background.Mode != PixelBackgroundMode.Colored) - throw new InvalidOperationException( - "All pixels in the buffer must have exact console color before rendering"); + if (pixel.IsCaret) + { + if (caretPosition != null) + throw new InvalidOperationException("Caret is already shown"); + caretPosition = new PixelBufferCoordinate(x, y); + } + /* todo: There is not IWindowImpl.Invalidate anymore. + if (!_consoleWindow.InvalidatedRects.Any(rect => + rect.ContainsExclusive(new Point(x, y)))) continue;*/ + if (pixel.Background.Mode != PixelBackgroundMode.Colored) + throw new InvalidOperationException( + "All pixels in the buffer must have exact console color before rendering"); - (Color, Color, FontWeight Weight, FontStyle Style, TextDecorationCollection TextDecorations, string Text - ) pixelSpread = (pixel.Background.Color, - pixel.Foreground.Color, - pixel.Foreground.Weight, - pixel.Foreground.Style, - pixel.Foreground.TextDecorations, - pixel.Foreground.Symbol.Text); - //todo: indexOutOfRange during resize - if (_cache[x, y] == pixelSpread) - { - x++; - continue; - } + //todo: indexOutOfRange during resize + if (_cache[x, y] == pixel) + { + x++; + continue; + } - _cache[x, y] = pixelSpread; + _cache[x, y] = pixel; - flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); + flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); - x += pixel.Foreground.Symbol.Width; - } + x += pixel.Foreground.Symbol.Width; + } flushingBuffer.Flush(); diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index 826ca9b8..736519ab 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -94,13 +94,13 @@ public static IReadOnlyList GetGlyphs(this string text, bool supportsCom public static ushort MeasureText(this string text) { var console = AvaloniaLocator.Current.GetService(); - + bool supportsComplexEmoji = console?.SupportsComplexEmoji ?? false; ushort width = 0; ushort lastWidth = 0; foreach (Rune rune in text.EnumerateRunes()) { ushort runeWidth = (ushort)UnicodeCalculator.GetWidth(rune); - if (console.SupportsComplexEmoji && + if (supportsComplexEmoji && (rune.Value == Emoji.ZeroWidthJoiner || rune.Value == Emoji.ObjectReplacementCharacter)) width -= lastWidth; else diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index 8ec43a4f..b44080e1 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -49,15 +49,53 @@ public void BlendAllSymbols() }; foreach ((byte code1, string _) in symbols) - foreach ((byte code2, string _) in symbols) - { - ISymbol symbol1 = new DrawingBoxSymbol(code1); - ISymbol symbol2 = new DrawingBoxSymbol(code2); - ISymbol blendedSymbol = symbol1.Blend(ref symbol2); - if (symbol1.Text != symbol2.Text) - Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); - Assert.That(blendedSymbol.Text, Is.Not.Null); - } + foreach ((byte code2, string _) in symbols) + { + ISymbol symbol1 = new DrawingBoxSymbol(code1); + ISymbol symbol2 = new DrawingBoxSymbol(code2); + ISymbol blendedSymbol = symbol1.Blend(ref symbol2); + if (symbol1.Text != symbol2.Text) + Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); + Assert.That(blendedSymbol.Text, Is.Not.Null); + } + } + + [Test] + public void Equality() + { + var symbol = new DrawingBoxSymbol(0b0000_1111); + var symbol2 = new DrawingBoxSymbol(0b0000_1111); + Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); + Assert.That(symbol == symbol2); + } + + [Test] + public void EqualityISymbol() + { + ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); + ISymbol symbol2 = new DrawingBoxSymbol(0b0000_1111); + Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); + } + + [Test] + public void Inequality() + { + var symbol = new DrawingBoxSymbol(0b0000_1111); + var symbol2 = new DrawingBoxSymbol(0b0000_0000); + Assert.That(!symbol.Equals((object)symbol2)); + Assert.That(!symbol.Equals(symbol2)); + Assert.That(symbol != symbol2); + } + + [Test] + public void InequalityISymbol() + { + ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); + ISymbol symbol2 = new DrawingBoxSymbol(0b0000_0000); + Assert.That(!symbol.Equals((object)symbol2)); + Assert.That(!symbol.Equals(symbol2)); } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index 414d34d3..a3bd5038 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -15,14 +15,47 @@ public void Constructor() Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } + [Test] [TestCase(PixelBackgroundMode.Transparent)] [TestCase(PixelBackgroundMode.Colored)] [TestCase(PixelBackgroundMode.Shaded)] public void ConstructorWithMode(PixelBackgroundMode mode) { var pixelBackground = new PixelBackground(mode, Colors.Red); - Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); - Assert.That(pixelBackground.Mode, Is.EqualTo(mode)); + Assert.That(pixelBackground.Color.Equals(Colors.Red)); + Assert.That(pixelBackground.Mode.Equals(mode)); + } + + [Test] + public void Equality() + { + var pixelBackground = new PixelBackground(Colors.Red); + var pixelBackground2 = new PixelBackground(Colors.Red); + Assert.That(pixelBackground.Equals((object)pixelBackground2)); + Assert.That(pixelBackground.Equals(pixelBackground2)); + Assert.That(pixelBackground == pixelBackground2); + + pixelBackground = new PixelBackground(PixelBackgroundMode.Transparent, Colors.Blue); + pixelBackground2 = new PixelBackground(PixelBackgroundMode.Transparent, Colors.Blue); + Assert.That(pixelBackground.Equals((object)pixelBackground2)); + Assert.That(pixelBackground.Equals(pixelBackground2)); + Assert.That(pixelBackground == pixelBackground2); } + + [Test] + public void Inequality() + { + var pixelBackground = new PixelBackground(Colors.Red); + var pixelBackground2 = new PixelBackground(Colors.Blue); + Assert.That(!pixelBackground.Equals(pixelBackground2)); + Assert.That(pixelBackground != pixelBackground2); + + pixelBackground = new PixelBackground(PixelBackgroundMode.Colored, Colors.Red); + pixelBackground2 = new PixelBackground(PixelBackgroundMode.Transparent, Colors.Red); + Assert.That(!pixelBackground.Equals((object)pixelBackground2)); + Assert.That(!pixelBackground.Equals(pixelBackground2)); + Assert.That(pixelBackground != pixelBackground2); + } + } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index 7cc87873..d038a8b6 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -71,6 +71,35 @@ public void ConstructorWithWideCharacter() Assert.That(pixelForeground.TextDecorations, Is.Null); } + [Test] + public void Equality() + { + var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + var pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Normal, null); + Assert.That(pixelForeground.Equals((object)pixelForeground2)); + Assert.That(pixelForeground.Equals(pixelForeground2)); + Assert.That(pixelForeground == pixelForeground2, Is.True); + } + + [Test] + public void Inequality() + { + var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + foreach (var variation in new PixelForeground[] + { + new(new SimpleSymbol('b'), Colors.Red, FontWeight.Normal, FontStyle.Normal, null), + new(new SimpleSymbol('a'), Colors.Blue, FontWeight.Normal, FontStyle.Normal, null), + new(new SimpleSymbol('a'), Colors.Red, FontWeight.Bold, FontStyle.Normal, null), + new(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Italic, null), + new(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Normal, TextDecorations.Underline), + }) + { + Assert.That(!pixelForeground.Equals((object)variation)); + Assert.That(!pixelForeground.Equals(variation)); + Assert.That(pixelForeground != variation, Is.True); + } + } + [Test] public void Blend() { diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index 20ecc8e3..ff619b17 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -55,6 +55,38 @@ public void ConstructorDrawingBoxSymbolAndColor() Assert.That(pixel.Background.Mode, Is.EqualTo(PixelBackgroundMode.Colored)); } + [Test] + public void Equality() + { + var pixel = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue)); + var pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue)); + Assert.That(pixel.Equals((object)pixel2)); + Assert.That(pixel.Equals(pixel2)); + Assert.That(pixel == pixel2); + } + + [Test] + public void NotEqual() + { + var pixel = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue)); + var pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('b'), Colors.Red), + new PixelBackground(Colors.Blue)); + Assert.That(!pixel.Equals((object)pixel2)); + Assert.That(!pixel.Equals(pixel2)); + Assert.That(pixel != pixel2); + + pixel = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue)); + pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Blue), + new PixelBackground(Colors.Blue)); + Assert.That(!pixel.Equals((object)pixel2)); + Assert.That(!pixel.Equals(pixel2)); + Assert.That(pixel != pixel2); + } + [Test] public void BlendTransparentBackground() { diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index b967e5d5..c4526ca5 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -49,6 +49,44 @@ public void ConstructorComplexEmoji() Assert.That(symbol.Width, Is.EqualTo(2)); } + [Test] + public void Equality() + { + var symbol = new SimpleSymbol("a"); + var symbol2 = new SimpleSymbol("a"); + Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); + Assert.That(symbol == symbol2); + } + + [Test] + public void EqualityISymbol() + { + ISymbol symbol = new SimpleSymbol("a"); + ISymbol symbol2 = new SimpleSymbol("a"); + Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); + } + + [Test] + public void Inequality() + { + var symbol = new SimpleSymbol("a"); + var symbol2 = new SimpleSymbol("b"); + Assert.That(!symbol.Equals((object)symbol2)); + Assert.That(!symbol.Equals(symbol2)); + Assert.That(symbol != symbol2); + } + + [Test] + public void InequalityISymbol() + { + ISymbol symbol = new SimpleSymbol("a"); + ISymbol symbol2 = new SimpleSymbol("b"); + Assert.That(!symbol.Equals((object)symbol2)); + Assert.That(!symbol.Equals(symbol2)); + } + [Test] public void IsWhiteSpace() { From d8586672e3bd875419f114b00d6fc85ac5f566b7 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 07:28:56 -0800 Subject: [PATCH 41/48] add hashcode unit test --- .../DrawingBoxSymbolTests.cs | 20 ++++++++++-- .../PixelBackgroundTests.cs | 16 ++++++++++ .../PixelForegroundTests.cs | 32 ++++++++++++++----- src/Tests/Consolonia.Core.Tests/PixelTests.cs | 12 +++++++ .../SimpleSymbolTests.cs | 19 +++++++++-- 5 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index b44080e1..54a57a18 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Diagnostics; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -75,7 +76,7 @@ public void EqualityISymbol() { ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); ISymbol symbol2 = new DrawingBoxSymbol(0b0000_1111); - Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); Assert.That(symbol.Equals(symbol2)); } @@ -94,8 +95,23 @@ public void InequalityISymbol() { ISymbol symbol = new DrawingBoxSymbol(0b0000_1111); ISymbol symbol2 = new DrawingBoxSymbol(0b0000_0000); - Assert.That(!symbol.Equals((object)symbol2)); Assert.That(!symbol.Equals(symbol2)); + Assert.That(!symbol.Equals(symbol2)); + } + + [Test] + public void Hash() + { + var set = new HashSet(); + set.Add(new DrawingBoxSymbol(0b0000_1111)); + set.Add(new DrawingBoxSymbol(0b0000_1111)); + Assert.That(set.Count, Is.EqualTo(1)); + + var set2 = new HashSet(); + set2.Add(new DrawingBoxSymbol(0b0000_1111)); + set2.Add(new DrawingBoxSymbol(0b0000_1111)); + Assert.That(set2.Count, Is.EqualTo(1)); } + } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index a3bd5038..5d06745e 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -57,5 +57,21 @@ public void Inequality() Assert.That(pixelBackground != pixelBackground2); } + [Test] + public void HashCode() + { + var pixelBackground = new PixelBackground(Colors.Red); + var pixelBackground2 = new PixelBackground(Colors.Red); + Assert.That(pixelBackground.GetHashCode(), Is.EqualTo(pixelBackground2.GetHashCode())); + + pixelBackground = new PixelBackground(PixelBackgroundMode.Transparent, Colors.Blue); + pixelBackground2 = new PixelBackground(PixelBackgroundMode.Transparent, Colors.Blue); + Assert.That(pixelBackground.GetHashCode(), Is.EqualTo(pixelBackground2.GetHashCode())); + + // inequal hashcode + pixelBackground = new PixelBackground(Colors.Red); + pixelBackground2 = new PixelBackground(Colors.Blue); + Assert.That(pixelBackground.GetHashCode(), Is.Not.EqualTo(pixelBackground2.GetHashCode())); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index d038a8b6..bb8a39ec 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -75,7 +75,7 @@ public void ConstructorWithWideCharacter() public void Equality() { var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); - var pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Normal, null); + var pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Red); Assert.That(pixelForeground.Equals((object)pixelForeground2)); Assert.That(pixelForeground.Equals(pixelForeground2)); Assert.That(pixelForeground == pixelForeground2, Is.True); @@ -87,11 +87,11 @@ public void Inequality() var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); foreach (var variation in new PixelForeground[] { - new(new SimpleSymbol('b'), Colors.Red, FontWeight.Normal, FontStyle.Normal, null), - new(new SimpleSymbol('a'), Colors.Blue, FontWeight.Normal, FontStyle.Normal, null), - new(new SimpleSymbol('a'), Colors.Red, FontWeight.Bold, FontStyle.Normal, null), - new(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Italic, null), - new(new SimpleSymbol('a'), Colors.Red, FontWeight.Normal, FontStyle.Normal, TextDecorations.Underline), + new(new SimpleSymbol('b'), Colors.Red), + new(new SimpleSymbol('a'), Colors.Blue), + new(new SimpleSymbol('a'), Colors.Red, FontWeight.Bold), + new(new SimpleSymbol('a'), Colors.Red, style: FontStyle.Italic), + new(new SimpleSymbol('a'), Colors.Red, textDecorations: TextDecorations.Underline), }) { Assert.That(!pixelForeground.Equals((object)variation)); @@ -116,8 +116,7 @@ public void Blend() public void BlendComplex() { var symbol = new SimpleSymbol('a'); - var pixelForeground = new PixelForeground(symbol, Colors.Red, FontWeight.Light, FontStyle.Normal, - TextDecorations.Strikethrough); + var pixelForeground = new PixelForeground(symbol, Colors.Red); var symbolAbove = new SimpleSymbol('b'); var pixelForegroundAbove = new PixelForeground(symbolAbove, Colors.Blue, FontWeight.Bold, FontStyle.Italic, TextDecorations.Underline); @@ -140,5 +139,22 @@ public void BlendEmoji() Assert.That(newPixelForeground.Color, Is.EqualTo(Colors.Blue)); Assert.That(newPixelForeground.Symbol.Text, Is.EqualTo("🎶")); } + + [Test] + public void HashCode() + { + var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + var pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + Assert.That(pixelForeground.GetHashCode(), Is.EqualTo(pixelForeground2.GetHashCode())); + + // inequal test + pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + pixelForeground2 = new PixelForeground(new SimpleSymbol('b'), Colors.Red); + Assert.That(pixelForeground.GetHashCode(), Is.Not.EqualTo(pixelForeground2.GetHashCode())); + + pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); + pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Blue); + Assert.That(pixelForeground.GetHashCode(), Is.Not.EqualTo(pixelForeground2.GetHashCode())); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index ff619b17..ea72de9e 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; using NUnit.Framework; @@ -110,5 +111,16 @@ public void BlendColoredBackground() Assert.That(newPixel.Foreground.Color, Is.EqualTo(Colors.Red)); Assert.That(newPixel.Background.Color, Is.EqualTo(Colors.Blue)); } + + [Test] + public void HashCode() + { + var set = new HashSet(); + set.Add(new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue))); + set.Add(new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), + new PixelBackground(Colors.Blue))); + Assert.That(set.Count, Is.EqualTo(1)); + } } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index c4526ca5..965eba6f 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Text; using Consolonia.Core.Drawing.PixelBufferImplementation; @@ -64,7 +65,7 @@ public void EqualityISymbol() { ISymbol symbol = new SimpleSymbol("a"); ISymbol symbol2 = new SimpleSymbol("a"); - Assert.That(symbol.Equals((object)symbol2)); + Assert.That(symbol.Equals(symbol2)); Assert.That(symbol.Equals(symbol2)); } @@ -83,8 +84,22 @@ public void InequalityISymbol() { ISymbol symbol = new SimpleSymbol("a"); ISymbol symbol2 = new SimpleSymbol("b"); - Assert.That(!symbol.Equals((object)symbol2)); Assert.That(!symbol.Equals(symbol2)); + Assert.That(!symbol.Equals(symbol2)); + } + + [Test] + public void HashCode() + { + var set = new HashSet(); + set.Add(new SimpleSymbol("a")); + set.Add(new SimpleSymbol("a")); + Assert.That(set.Count, Is.EqualTo(1)); + + var set2 = new HashSet(); + set2.Add(new SimpleSymbol("a")); + set2.Add(new SimpleSymbol("a")); + Assert.That(set2.Count, Is.EqualTo(1)); } [Test] From 2e4dd41407deb8806041fe42547c1c1f608e51de Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 15:31:54 +0000 Subject: [PATCH 42/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../DrawingBoxSymbol.cs | 100 ++++++++++-------- .../PixelBufferImplementation/Pixel.cs | 24 +++-- .../PixelBackground.cs | 22 ++-- .../PixelForeground.cs | 28 +++-- .../PixelBufferImplementation/SimpleSymbol.cs | 22 ++-- src/Consolonia.Core/Drawing/RenderTarget.cs | 50 ++++----- .../DrawingBoxSymbolTests.cs | 19 ++-- .../PixelForegroundTests.cs | 18 ++-- src/Tests/Consolonia.Core.Tests/PixelTests.cs | 8 +- .../SimpleSymbolTests.cs | 4 +- 10 files changed, 172 insertions(+), 123 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 5f660636..e6dd1ab4 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -56,46 +56,46 @@ private static char GetBoxSymbol(byte upRightDownLeft) return horizontal ? '╪' : '╫'; default: + { + return upRightDownLeft switch { - return upRightDownLeft switch - { - EmptySymbol => char.MinValue, - BoldSymbol => '█', - 0b0000_1001 => '┘', - 0b1000_1001 => '╜', - 0b0001_1001 => '╛', - 0b1001_1001 => '╝', - 0b0000_0011 => '┐', - 0b0010_0011 => '╖', - 0b0001_0011 => '╕', - 0b0011_0011 => '╗', - 0b0000_0110 => '┌', - 0b0100_0110 => '╒', - 0b0010_0110 => '╓', - 0b0110_0110 => '╔', - 0b0000_1100 => '└', - 0b0100_1100 => '╘', - 0b1000_1100 => '╙', - 0b1100_1100 => '╚', - 0b0000_1110 => '├', - 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', - 0b0100_1110 => '╞', - 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', - 0b0000_1011 => '┤', - 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', - 0b0001_1011 => '╡', - 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', - 0b0000_1101 => '┴', - 0b1000_1101 => '╨', - 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', - 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', - 0b0000_0111 => '┬', - 0b0010_0111 => '╥', - 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', - 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', - _ => throw new InvalidOperationException() - }; - } + EmptySymbol => char.MinValue, + BoldSymbol => '█', + 0b0000_1001 => '┘', + 0b1000_1001 => '╜', + 0b0001_1001 => '╛', + 0b1001_1001 => '╝', + 0b0000_0011 => '┐', + 0b0010_0011 => '╖', + 0b0001_0011 => '╕', + 0b0011_0011 => '╗', + 0b0000_0110 => '┌', + 0b0100_0110 => '╒', + 0b0010_0110 => '╓', + 0b0110_0110 => '╔', + 0b0000_1100 => '└', + 0b0100_1100 => '╘', + 0b1000_1100 => '╙', + 0b1100_1100 => '╚', + 0b0000_1110 => '├', + 0b1000_1110 or 0b0010_1110 or 0b1010_1110 => '╟', + 0b0100_1110 => '╞', + 0b1100_1110 or 0b0110_1110 or 0b1110_1110 => '╠', + 0b0000_1011 => '┤', + 0b1000_1011 or 0b0010_1011 or 0b1010_1011 => '╢', + 0b0001_1011 => '╡', + 0b1001_1011 or 0b0011_1011 or 0b1011_1011 => '╣', + 0b0000_1101 => '┴', + 0b1000_1101 => '╨', + 0b0100_1101 or 0b0001_1101 or 0b0101_1101 => '╧', + 0b1100_1101 or 0b1001_1101 or 0b1101_1101 => '╩', + 0b0000_0111 => '┬', + 0b0010_0111 => '╥', + 0b0100_0111 or 0b0001_0111 or 0b0101_0111 => '╤', + 0b0110_0111 or 0b0011_0111 or 0b0111_0111 => '╦', + _ => throw new InvalidOperationException() + }; + } } } @@ -135,18 +135,28 @@ public static DrawingBoxSymbol UpRightDownLeftFromPattern(byte pattern, LineStyl } public bool Equals(DrawingBoxSymbol other) - => _upRightDownLeft == other._upRightDownLeft; + { + return _upRightDownLeft == other._upRightDownLeft; + } public override bool Equals([NotNullWhen(true)] object obj) - => obj is DrawingBoxSymbol other && this.Equals(other); + { + return obj is DrawingBoxSymbol other && Equals(other); + } public override int GetHashCode() - => _upRightDownLeft.GetHashCode(); + { + return _upRightDownLeft.GetHashCode(); + } public static bool operator ==(DrawingBoxSymbol left, DrawingBoxSymbol right) - => left.Equals(right); - + { + return left.Equals(right); + } + public static bool operator !=(DrawingBoxSymbol left, DrawingBoxSymbol right) - => !left.Equals(right); + { + return !left.Equals(right); + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index a60d0893..0f5ae2a3 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -125,20 +125,30 @@ private static Color MergeColors(Color target, Color source) } public bool Equals(Pixel other) - => Foreground.Equals(other.Foreground) && - Background.Equals(other.Background) && - IsCaret.Equals(IsCaret); + { + return Foreground.Equals(other.Foreground) && + Background.Equals(other.Background) && + IsCaret.Equals(IsCaret); + } public override bool Equals([NotNullWhen(true)] object obj) - => obj is Pixel other && this.Equals(other); + { + return obj is Pixel other && Equals(other); + } public override int GetHashCode() - => HashCode.Combine(Foreground, Background, IsCaret); + { + return HashCode.Combine(Foreground, Background, IsCaret); + } public static bool operator ==(Pixel left, Pixel right) - => left.Equals(right); + { + return left.Equals(right); + } public static bool operator !=(Pixel left, Pixel right) - => !left.Equals(right); + { + return !left.Equals(right); + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index f2584faf..f2865c43 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -45,20 +45,30 @@ public PixelBackground Shade() return new PixelBackground(newMode, newColor); } - + public bool Equals(PixelBackground other) - => Color.Equals(other.Color) && Mode == other.Mode; + { + return Color.Equals(other.Color) && Mode == other.Mode; + } public override bool Equals([NotNullWhen(true)] object obj) - => obj is PixelBackground other && this.Equals(other); + { + return obj is PixelBackground other && Equals(other); + } public override int GetHashCode() - => HashCode.Combine(Color, Mode); + { + return HashCode.Combine(Color, Mode); + } public static bool operator ==(PixelBackground left, PixelBackground right) - => left.Equals(right); + { + return left.Equals(right); + } public static bool operator !=(PixelBackground left, PixelBackground right) - => !left.Equals(right); + { + return !left.Equals(right); + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index c1b51426..2a89eca4 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -49,22 +49,32 @@ public PixelForeground Blend(PixelForeground pixelAboveForeground) } public bool Equals(PixelForeground other) - => Symbol.Equals(other.Symbol) && - Color.Equals(other.Color) && - Weight == other.Weight && - Style == other.Style && - Equals(TextDecorations, other.TextDecorations); + { + return Symbol.Equals(other.Symbol) && + Color.Equals(other.Color) && + Weight == other.Weight && + Style == other.Style && + Equals(TextDecorations, other.TextDecorations); + } public override bool Equals([NotNullWhen(true)] object obj) - => obj is PixelForeground other && this.Equals(other); + { + return obj is PixelForeground other && Equals(other); + } public override int GetHashCode() - => HashCode.Combine(Symbol, Color, (int)Weight, (int)Style, TextDecorations); + { + return HashCode.Combine(Symbol, Color, (int)Weight, (int)Style, TextDecorations); + } public static bool operator ==(PixelForeground left, PixelForeground right) - => left.Equals(right); + { + return left.Equals(right); + } public static bool operator !=(PixelForeground left, PixelForeground right) - => !left.Equals(right); + { + return !left.Equals(right); + } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs index d19fd456..bb9da67b 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/SimpleSymbol.cs @@ -48,18 +48,28 @@ public ISymbol Blend(ref ISymbol symbolAbove) } public bool Equals(SimpleSymbol other) - => Text.Equals(other.Text, StringComparison.Ordinal); + { + return Text.Equals(other.Text, StringComparison.Ordinal); + } public override bool Equals([NotNullWhen(true)] object obj) - => obj is SimpleSymbol other && this.Equals(other); + { + return obj is SimpleSymbol other && Equals(other); + } public override int GetHashCode() - => Text.GetHashCode(StringComparison.Ordinal); + { + return Text.GetHashCode(StringComparison.Ordinal); + } public static bool operator ==(SimpleSymbol left, SimpleSymbol right) - => left.Equals(right); + { + return left.Equals(right); + } public static bool operator !=(SimpleSymbol left, SimpleSymbol right) - => !left.Equals(right); + { + return !left.Equals(right); + } } -} +} \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index 17d9c88e..680eab71 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -109,38 +109,38 @@ private void RenderToDevice() var flushingBuffer = new FlushingBuffer(_console); for (ushort y = 0; y < pixelBuffer.Height; y++) - for (ushort x = 0; x < pixelBuffer.Width;) - { - Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; + for (ushort x = 0; x < pixelBuffer.Width;) + { + Pixel pixel = pixelBuffer[(PixelBufferCoordinate)(x, y)]; - if (pixel.IsCaret) - { - if (caretPosition != null) - throw new InvalidOperationException("Caret is already shown"); - caretPosition = new PixelBufferCoordinate(x, y); - } + if (pixel.IsCaret) + { + if (caretPosition != null) + throw new InvalidOperationException("Caret is already shown"); + caretPosition = new PixelBufferCoordinate(x, y); + } - /* todo: There is not IWindowImpl.Invalidate anymore. - if (!_consoleWindow.InvalidatedRects.Any(rect => - rect.ContainsExclusive(new Point(x, y)))) continue;*/ - if (pixel.Background.Mode != PixelBackgroundMode.Colored) - throw new InvalidOperationException( - "All pixels in the buffer must have exact console color before rendering"); + /* todo: There is not IWindowImpl.Invalidate anymore. + if (!_consoleWindow.InvalidatedRects.Any(rect => + rect.ContainsExclusive(new Point(x, y)))) continue;*/ + if (pixel.Background.Mode != PixelBackgroundMode.Colored) + throw new InvalidOperationException( + "All pixels in the buffer must have exact console color before rendering"); - //todo: indexOutOfRange during resize - if (_cache[x, y] == pixel) - { - x++; - continue; - } + //todo: indexOutOfRange during resize + if (_cache[x, y] == pixel) + { + x++; + continue; + } - _cache[x, y] = pixel; + _cache[x, y] = pixel; - flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); + flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); - x += pixel.Foreground.Symbol.Width; - } + x += pixel.Foreground.Symbol.Width; + } flushingBuffer.Flush(); diff --git a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs index 54a57a18..9e4b5dcc 100644 --- a/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/DrawingBoxSymbolTests.cs @@ -50,15 +50,15 @@ public void BlendAllSymbols() }; foreach ((byte code1, string _) in symbols) - foreach ((byte code2, string _) in symbols) - { - ISymbol symbol1 = new DrawingBoxSymbol(code1); - ISymbol symbol2 = new DrawingBoxSymbol(code2); - ISymbol blendedSymbol = symbol1.Blend(ref symbol2); - if (symbol1.Text != symbol2.Text) - Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); - Assert.That(blendedSymbol.Text, Is.Not.Null); - } + foreach ((byte code2, string _) in symbols) + { + ISymbol symbol1 = new DrawingBoxSymbol(code1); + ISymbol symbol2 = new DrawingBoxSymbol(code2); + ISymbol blendedSymbol = symbol1.Blend(ref symbol2); + if (symbol1.Text != symbol2.Text) + Debug.WriteLine($"{symbol1.Text} + {symbol2.Text} => {blendedSymbol.Text}"); + Assert.That(blendedSymbol.Text, Is.Not.Null); + } } [Test] @@ -112,6 +112,5 @@ public void Hash() set2.Add(new DrawingBoxSymbol(0b0000_1111)); Assert.That(set2.Count, Is.EqualTo(1)); } - } } \ No newline at end of file diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index bb8a39ec..029315e7 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -85,14 +85,14 @@ public void Equality() public void Inequality() { var pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); - foreach (var variation in new PixelForeground[] - { - new(new SimpleSymbol('b'), Colors.Red), - new(new SimpleSymbol('a'), Colors.Blue), - new(new SimpleSymbol('a'), Colors.Red, FontWeight.Bold), - new(new SimpleSymbol('a'), Colors.Red, style: FontStyle.Italic), - new(new SimpleSymbol('a'), Colors.Red, textDecorations: TextDecorations.Underline), - }) + foreach (PixelForeground variation in new PixelForeground[] + { + new(new SimpleSymbol('b'), Colors.Red), + new(new SimpleSymbol('a'), Colors.Blue), + new(new SimpleSymbol('a'), Colors.Red, FontWeight.Bold), + new(new SimpleSymbol('a'), Colors.Red, style: FontStyle.Italic), + new(new SimpleSymbol('a'), Colors.Red, textDecorations: TextDecorations.Underline) + }) { Assert.That(!pixelForeground.Equals((object)variation)); Assert.That(!pixelForeground.Equals(variation)); @@ -151,7 +151,7 @@ public void HashCode() pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); pixelForeground2 = new PixelForeground(new SimpleSymbol('b'), Colors.Red); Assert.That(pixelForeground.GetHashCode(), Is.Not.EqualTo(pixelForeground2.GetHashCode())); - + pixelForeground = new PixelForeground(new SimpleSymbol('a'), Colors.Red); pixelForeground2 = new PixelForeground(new SimpleSymbol('a'), Colors.Blue); Assert.That(pixelForeground.GetHashCode(), Is.Not.EqualTo(pixelForeground2.GetHashCode())); diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index ea72de9e..887b403f 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -72,17 +72,17 @@ public void Equality() public void NotEqual() { var pixel = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), - new PixelBackground(Colors.Blue)); + new PixelBackground(Colors.Blue)); var pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('b'), Colors.Red), - new PixelBackground(Colors.Blue)); + new PixelBackground(Colors.Blue)); Assert.That(!pixel.Equals((object)pixel2)); Assert.That(!pixel.Equals(pixel2)); Assert.That(pixel != pixel2); pixel = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Red), - new PixelBackground(Colors.Blue)); + new PixelBackground(Colors.Blue)); pixel2 = new Pixel(new PixelForeground(new SimpleSymbol('a'), Colors.Blue), - new PixelBackground(Colors.Blue)); + new PixelBackground(Colors.Blue)); Assert.That(!pixel.Equals((object)pixel2)); Assert.That(!pixel.Equals(pixel2)); Assert.That(pixel != pixel2); diff --git a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs index 965eba6f..db91c5cb 100644 --- a/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs +++ b/src/Tests/Consolonia.Core.Tests/SimpleSymbolTests.cs @@ -87,7 +87,7 @@ public void InequalityISymbol() Assert.That(!symbol.Equals(symbol2)); Assert.That(!symbol.Equals(symbol2)); } - + [Test] public void HashCode() { @@ -95,7 +95,7 @@ public void HashCode() set.Add(new SimpleSymbol("a")); set.Add(new SimpleSymbol("a")); Assert.That(set.Count, Is.EqualTo(1)); - + var set2 = new HashSet(); set2.Add(new SimpleSymbol("a")); set2.Add(new SimpleSymbol("a")); From a03c8ec47fdaac9bf7da50af85200e7347e838a2 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 09:43:58 -0800 Subject: [PATCH 43/48] cleanup from code merge --- .../Drawing/DrawingContextImpl.cs | 2 +- .../DrawingBoxSymbol.cs | 4 ---- .../PixelBufferImplementation/Pixel.cs | 15 ++++++++++----- .../PixelBackground.cs | 7 ++++++- .../PixelBufferImplementation/PixelBuffer.cs | 19 ------------------- .../PixelForeground.cs | 9 +++++++++ src/Consolonia.Core/Helpers/Extensions.cs | 3 ++- .../PixelBackgroundTests.cs | 8 ++++++++ .../PixelForegroundTests.cs | 11 +++++++++++ src/Tests/Consolonia.Core.Tests/PixelTests.cs | 9 +++++++++ 10 files changed, 56 insertions(+), 31 deletions(-) diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index cbc1c24c..dd90c44b 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -499,7 +499,7 @@ private void DrawPixelAndMoveHead(ref Point head, Line line, LineStyle? lineStyl // Each glyph maps to a pixel as a starting point. // Emoji's and Ligatures are complex strings, so they start at a point and then overlap following pixels // the x and y are adjusted accodingly. - foreach (string glyph in text.GetGlyphs()) + foreach (string glyph in text.GetGlyphs(_consoleWindow.Console.SupportsComplexEmoji)) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition, currentYPosition)); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs index 28891f92..e6dd1ab4 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/DrawingBoxSymbol.cs @@ -25,10 +25,6 @@ public DrawingBoxSymbol(byte upRightDownLeft) public ushort Width { get; } = 1; - public string Text => GetBoxSymbol().ToString(); - - public ushort Width { get; } = 1; - /// /// https://en.wikipedia.org/wiki/Code_page_437 /// diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 9652c943..6d795700 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -17,13 +17,13 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public bool IsCaret { get; } - public Pixel(bool isCaret) + public Pixel(bool isCaret) { - Foreground = new PixelForeground(new SimpleSymbol()); - Background = new PixelBackground(PixelBackgroundMode.Transparent); + Foreground = new PixelForeground(); + Background = new PixelBackground(); IsCaret = isCaret; } - + /// /// Make a pixel foreground with transparent background /// @@ -78,6 +78,11 @@ public Pixel Blend(Pixel pixelAbove) PixelForeground newForeground; PixelBackground newBackground; + if (pixelAbove.IsCaret) + { + return new Pixel(this.Foreground, this.Background, true); + } + switch (pixelAbove.Background.Mode) { case PixelBackgroundMode.Colored: @@ -85,7 +90,7 @@ public Pixel Blend(Pixel pixelAbove) Color mergedColors = MergeColors(Background.Color, pixelAbove.Background.Color); newForeground = pixelAbove.Foreground; newBackground = new PixelBackground(mergedColors); - return new Pixel(newForeground, newBackground); + return new Pixel(newForeground, newBackground, pixelAbove.IsCaret); case PixelBackgroundMode.Transparent: // if the foreground is transparent, ignore pixelAbove foreground. diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index f2865c43..24b19739 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -8,6 +8,11 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation [DebuggerDisplay("[{Color}, {Mode}]")] public readonly struct PixelBackground : IEquatable { + public PixelBackground() + { + Mode = PixelBackgroundMode.Transparent; + Color = Colors.Transparent; + } public PixelBackground(Color color) { Mode = color.A == 0 ? PixelBackgroundMode.Transparent : PixelBackgroundMode.Colored; @@ -20,7 +25,7 @@ public PixelBackground(PixelBackgroundMode mode, Color? color = null) Mode = mode; } - public Color Color { get; } + public Color Color { get; } public PixelBackgroundMode Mode { get; } public PixelBackground Shade() diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index 1e3a2888..b5cfa20e 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -70,25 +70,6 @@ public void Set(PixelBufferCoordinate point, Func - /// Clears old pixel caret position and sets new caret position - /// - /// - public void SetCaretPosition(PixelBufferCoordinate point) - { - // clear old caret position by merging in IsCaret = false - Pixel oldCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; - _buffer[_caretPosition.X, _caretPosition.Y] = - new Pixel(oldCaretPixel.Foreground, oldCaretPixel.Background /*, false*/); - - _caretPosition = point; - - // set new caret position by merging in IsCaret = true - Pixel newCaretPixel = _buffer[_caretPosition.X, _caretPosition.Y]; - _buffer[_caretPosition.X, _caretPosition.Y] = - new Pixel(newCaretPixel.Foreground, newCaretPixel.Background, true); - } - public void Foreach(Func replaceAction) { ForeachReadonly((point, oldPixel) => diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index 2a89eca4..0780757f 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -8,6 +8,15 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation [DebuggerDisplay("'{Symbol.Text}' [{Color}]")] public readonly struct PixelForeground : IEquatable { + public PixelForeground() + { + Symbol = new SimpleSymbol(); + Color = Colors.Transparent; + Weight = FontWeight.Normal; + Style = FontStyle.Normal; + TextDecorations = null; + } + public PixelForeground(ISymbol symbol, Color color, FontWeight weight = FontWeight.Normal, FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null) diff --git a/src/Consolonia.Core/Helpers/Extensions.cs b/src/Consolonia.Core/Helpers/Extensions.cs index a686839a..0c17f106 100644 --- a/src/Consolonia.Core/Helpers/Extensions.cs +++ b/src/Consolonia.Core/Helpers/Extensions.cs @@ -91,9 +91,10 @@ public static IReadOnlyList GetGlyphs(this string text, bool supportsCom /// /// /// - public static ushort MeasureText(this string text, bool supportsComplexEmoji) + public static ushort MeasureText(this string text) { var console = AvaloniaLocator.Current.GetService(); + bool supportsComplexEmoji = console != null ? console.SupportsComplexEmoji : false; ushort width = 0; ushort lastWidth = 0; foreach (Rune rune in text.EnumerateRunes()) diff --git a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs index 5d06745e..b3e259c0 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelBackgroundTests.cs @@ -9,6 +9,14 @@ public class PixelBackgroundTests { [Test] public void Constructor() + { + var pixelBackground = new PixelBackground(); + Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Transparent)); + Assert.That(pixelBackground.Mode, Is.EqualTo(PixelBackgroundMode.Transparent)); + } + + [Test] + public void ConstructorWithColor() { var pixelBackground = new PixelBackground(Colors.Red); Assert.That(pixelBackground.Color, Is.EqualTo(Colors.Red)); diff --git a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs index 029315e7..7d6a4bae 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelForegroundTests.cs @@ -9,6 +9,17 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelForegroundTests { + [Test] + public void Constructor() + { + var pixelForeground = new PixelForeground(); + Assert.That(pixelForeground.Color, Is.EqualTo(Colors.Transparent)); + Assert.That(pixelForeground.Symbol.Text, Is.EqualTo(string.Empty)); + Assert.That(pixelForeground.Weight, Is.EqualTo(FontWeight.Normal)); + Assert.That(pixelForeground.Style, Is.EqualTo(FontStyle.Normal)); + Assert.That(pixelForeground.TextDecorations, Is.Null); + } + [Test] public void ConstructorWithSymbol() { diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index 887b403f..cc1868d5 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -8,6 +8,15 @@ namespace Consolonia.Core.Tests [TestFixture] public class PixelTests { + [Test] + public void ConstructorCaret() + { + var pixel = new Pixel(true); + Assert.That(pixel.IsCaret == true); + Assert.That(pixel.Foreground == new PixelForeground()); + Assert.That(pixel.Background == new PixelBackground()); + } + [Test] public void ConstructorColorOnly() { From 2c4c60a86ac24f42065ae291f4600f68099ec928 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 10:20:05 -0800 Subject: [PATCH 44/48] fix lint --- .../Drawing/PixelBufferImplementation/PixelBuffer.cs | 2 -- src/Tests/Consolonia.Core.Tests/PixelTests.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs index b5cfa20e..b95607af 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBuffer.cs @@ -11,14 +11,12 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public class PixelBuffer : IEnumerable { private readonly Pixel[,] _buffer; - private PixelBufferCoordinate _caretPosition; public PixelBuffer(ushort width, ushort height) { Width = width; Height = height; _buffer = new Pixel[width, height]; - _caretPosition = new PixelBufferCoordinate(0, 0); } public ushort Width { get; } diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index cc1868d5..aee876e4 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -12,7 +12,7 @@ public class PixelTests public void ConstructorCaret() { var pixel = new Pixel(true); - Assert.That(pixel.IsCaret == true); + Assert.That(pixel.IsCaret); Assert.That(pixel.Foreground == new PixelForeground()); Assert.That(pixel.Background == new PixelBackground()); } From f0c50e7393ca2b49c2208f70e129b50d54b2f1ba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 10 Nov 2024 18:24:15 +0000 Subject: [PATCH 45/48] Automated JetBrains cleanup Co-authored-by: <+@users.noreply.github.com> --- .../Drawing/PixelBufferImplementation/Pixel.cs | 5 +---- .../Drawing/PixelBufferImplementation/PixelBackground.cs | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 6d795700..7cc0a425 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -78,10 +78,7 @@ public Pixel Blend(Pixel pixelAbove) PixelForeground newForeground; PixelBackground newBackground; - if (pixelAbove.IsCaret) - { - return new Pixel(this.Foreground, this.Background, true); - } + if (pixelAbove.IsCaret) return new Pixel(Foreground, Background, true); switch (pixelAbove.Background.Mode) { diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index 24b19739..e24319a3 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -13,6 +13,7 @@ public PixelBackground() Mode = PixelBackgroundMode.Transparent; Color = Colors.Transparent; } + public PixelBackground(Color color) { Mode = color.A == 0 ? PixelBackgroundMode.Transparent : PixelBackgroundMode.Colored; @@ -25,7 +26,7 @@ public PixelBackground(PixelBackgroundMode mode, Color? color = null) Mode = mode; } - public Color Color { get; } + public Color Color { get; } public PixelBackgroundMode Mode { get; } public PixelBackground Shade() From 82addcc6651db6ce8471fbac9a9f1fbddf3c9936 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 11:28:01 -0800 Subject: [PATCH 46/48] bug fix on equality, update unit test to cover case --- .../PixelBufferImplementation/Pixel.cs | 2 +- .../PixelBufferCoordinate.cs | 3 ++- src/Consolonia.GuiCS/Size.cs | 2 +- src/Tests/Consolonia.Core.Tests/PixelTests.cs | 20 +++++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs index 6d795700..776e1219 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -140,7 +140,7 @@ public bool Equals(Pixel other) { return Foreground.Equals(other.Foreground) && Background.Equals(other.Background) && - IsCaret.Equals(IsCaret); + IsCaret.Equals(other.IsCaret); } public override bool Equals([NotNullWhen(true)] object obj) diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs index 20d6f518..2bd57735 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBufferCoordinate.cs @@ -1,3 +1,4 @@ +using System; using Avalonia; // ReSharper disable UnusedMember.Global @@ -56,7 +57,7 @@ public bool Equals(PixelBufferCoordinate secondPoint) public override int GetHashCode() { - return X ^ Y; + return HashCode.Combine(X, Y); } } } \ No newline at end of file diff --git a/src/Consolonia.GuiCS/Size.cs b/src/Consolonia.GuiCS/Size.cs index 1f7e7412..9d3d1d0a 100644 --- a/src/Consolonia.GuiCS/Size.cs +++ b/src/Consolonia.GuiCS/Size.cs @@ -206,7 +206,7 @@ public override bool Equals (object obj) public override int GetHashCode () { - return width^height; + return HashCode.Combine(width, height); } /// diff --git a/src/Tests/Consolonia.Core.Tests/PixelTests.cs b/src/Tests/Consolonia.Core.Tests/PixelTests.cs index aee876e4..3df57b39 100644 --- a/src/Tests/Consolonia.Core.Tests/PixelTests.cs +++ b/src/Tests/Consolonia.Core.Tests/PixelTests.cs @@ -97,6 +97,26 @@ public void NotEqual() Assert.That(pixel != pixel2); } + [Test] + public void EqualityCaret() + { + var pixel = new Pixel(true); + var pixel2 = new Pixel(true); + Assert.That(pixel.Equals((object)pixel2)); + Assert.That(pixel.Equals(pixel2)); + Assert.That(pixel == pixel2); + } + + [Test] + public void InequalityCaret() + { + var pixel = new Pixel(true); + var pixel2 = new Pixel(false); + Assert.That(!pixel.Equals((object)pixel2)); + Assert.That(!pixel.Equals(pixel2)); + Assert.That(pixel != pixel2); + } + [Test] public void BlendTransparentBackground() { From 14ae365b6435e88a44091010b6931558618fdcae Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 11:40:38 -0800 Subject: [PATCH 47/48] split complex test into individual test cases --- .../TextBlockTests.cs | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs index df36dc97..027edfca 100644 --- a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs @@ -13,21 +13,46 @@ namespace Consolonia.Gallery.Tests internal class TextBlockTests : GalleryTestsBaseBase { [Test] - public async Task PerformSingleTest() + public async Task TextBlock_DisplaysBasicText() { await UITest.KeyInput(Key.Tab); - await UITest.AssertHasText("This is TextBlock", + await UITest.AssertHasText("This is TextBlock"); + } + + [Test] + public async Task TextBlock_HandlesTrimming() + { + await UITest.KeyInput(Key.Tab); + await UITest.AssertHasText( "Text trimming with charac...", - "Text trimming with word...", + "Text trimming with word..."); + } + + [Test] + public async Task TextBlock_HandlesAlignment() + { + await UITest.KeyInput(Key.Tab); + await UITest.AssertHasText( "│Left aligned text ", " Center aligned text ", - "Right aligned text│", + " Right aligned text│"); + } - // multiline + [Test] + public async Task TextBlock_HandlesMultilineText() + { + await UITest.KeyInput(Key.Tab); + await UITest.AssertHasText( "│Vivamus magna. Cras in mi at felis aliquet congue. Ut a │", - "│est eget ligula molestie gravida. Curabitur massa. Donec│", - // special chars, emojis, etc. - "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞", "🎵", "“𝔉𝔞𝔫𝔠𝔶”", "ff", "fi", "½"); + "│est eget ligula molestie gravida. Curabitur massa. Donec│"); + } + + [Test] + public async Task TextBlock_HandlesSpecialCharacters() + { + await UITest.KeyInput(Key.Tab); + await UITest.AssertHasText( + "𐓏𐓘𐓻𐓘𐓻𐓟 𐒻𐓟", "𝄞", "🎵", "𝔉𝔞𝔫𝔠𝔶", "ff", "fi", "½"); } } } \ No newline at end of file From cbb342ebcd202ac4de1cb94ec47c7537d1ec8ae3 Mon Sep 17 00:00:00 2001 From: Tom Laird-McConnell Date: Sun, 10 Nov 2024 11:45:56 -0800 Subject: [PATCH 48/48] avoid linq for creating glyph and advances array fix test names --- src/Consolonia.Core/Text/GlyphTypeface.cs | 9 ++++++--- src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs | 10 +++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Consolonia.Core/Text/GlyphTypeface.cs b/src/Consolonia.Core/Text/GlyphTypeface.cs index 3d4c2d49..9f5bee0b 100644 --- a/src/Consolonia.Core/Text/GlyphTypeface.cs +++ b/src/Consolonia.Core/Text/GlyphTypeface.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Avalonia.Media; using Consolonia.Core.Drawing; @@ -41,7 +40,9 @@ public bool TryGetGlyph(uint codepoint, out ushort glyph) public ushort[] GetGlyphs(ReadOnlySpan codepoints) { - return Enumerable.Repeat(Glyph, codepoints.Length).ToArray(); + ushort[] glyphs = new ushort[codepoints.Length]; + Array.Fill(glyphs, Glyph); + return glyphs; } public int GetGlyphAdvance(ushort glyph) @@ -51,7 +52,9 @@ public int GetGlyphAdvance(ushort glyph) public int[] GetGlyphAdvances(ReadOnlySpan glyphs) { - return Enumerable.Repeat(1, glyphs.Length).ToArray(); + int[] advances = new int[glyphs.Length]; + Array.Fill(advances, 1); + return advances; } public bool TryGetTable(uint tag, out byte[] table) diff --git a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs index 027edfca..86ecf972 100644 --- a/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs +++ b/src/Tests/Consolonia.Gallery.Tests/TextBlockTests.cs @@ -13,14 +13,14 @@ namespace Consolonia.Gallery.Tests internal class TextBlockTests : GalleryTestsBaseBase { [Test] - public async Task TextBlock_DisplaysBasicText() + public async Task DisplaysBasicText() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText("This is TextBlock"); } [Test] - public async Task TextBlock_HandlesTrimming() + public async Task HandlesTrimming() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText( @@ -29,7 +29,7 @@ await UITest.AssertHasText( } [Test] - public async Task TextBlock_HandlesAlignment() + public async Task HandlesAlignment() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText( @@ -39,7 +39,7 @@ await UITest.AssertHasText( } [Test] - public async Task TextBlock_HandlesMultilineText() + public async Task HandlesMultilineText() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText( @@ -48,7 +48,7 @@ await UITest.AssertHasText( } [Test] - public async Task TextBlock_HandlesSpecialCharacters() + public async Task HandlesSpecialCharacters() { await UITest.KeyInput(Key.Tab); await UITest.AssertHasText(