diff --git a/src/Consolonia.Core/Consolonia.Core.csproj b/src/Consolonia.Core/Consolonia.Core.csproj index 695f2d90..ead17fc1 100644 --- a/src/Consolonia.Core/Consolonia.Core.csproj +++ b/src/Consolonia.Core/Consolonia.Core.csproj @@ -5,6 +5,7 @@ + diff --git a/src/Consolonia.Core/Drawing/ConsoleBrush.cs b/src/Consolonia.Core/Drawing/ConsoleBrush.cs new file mode 100644 index 00000000..f91bf5aa --- /dev/null +++ b/src/Consolonia.Core/Drawing/ConsoleBrush.cs @@ -0,0 +1,206 @@ +using System; +using Avalonia; +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using Consolonia.Core.Infrastructure; + +namespace Consolonia.Core.Drawing +{ + public class ConsoleBrush : AvaloniaObject, IImmutableBrush + { + public static readonly StyledProperty ColorProperty = + AvaloniaProperty.Register(nameof(Color)); + + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); + + // ReSharper disable once UnusedMember.Global + public ConsoleBrush(Color color, PixelBackgroundMode mode) : this(color) + { + Mode = mode; + } + + public ConsoleBrush(Color color) + { + Color = color; + } + + public ConsoleBrush() + { + } + + public PixelBackgroundMode Mode + { + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + public Color Color + { + get => GetValue(ColorProperty); + set => SetValue(ColorProperty, value); + } + + public double Opacity => 1; + public ITransform Transform => null; + public RelativePoint TransformOrigin => RelativePoint.TopLeft; + + /// + /// Convert a IBrush to a Brush. + /// + /// + /// NOTE: If it's a ConsoleBrush it will be passed through unchanged, unless mode is set then it will convert + /// consolebrush to mode + /// + /// + /// Default is Colored. + /// + public static ConsoleBrush FromBrush(IBrush brush, PixelBackgroundMode? mode = null) + { + ArgumentNullException.ThrowIfNull(brush, nameof(brush)); + + switch (brush) + { + case ConsoleBrush consoleBrush: + if (mode != null && consoleBrush.Mode != mode) + return new ConsoleBrush(consoleBrush.Color, mode.Value); + return consoleBrush; + + case LineBrush lineBrush: + switch (lineBrush.Brush) + { + case ConsoleBrush consoleBrush: + return consoleBrush; + case ISolidColorBrush br: + return new ConsoleBrush(br.Color, mode ?? PixelBackgroundMode.Colored); + default: + ConsoloniaPlatform.RaiseNotSupported(6); + return null; + } + + case ISolidColorBrush solidBrush: + return new ConsoleBrush(solidBrush.Color, mode ?? PixelBackgroundMode.Colored); + + default: + ConsoloniaPlatform.RaiseNotSupported(6); + return null; + } + } + + public static ConsoleBrush FromPosition(IBrush brush, int x, int y, int width, int height) + { + ArgumentNullException.ThrowIfNull(brush); + if (x < 0 || x > width) + throw new ArgumentOutOfRangeException(nameof(x), "x is out bounds"); + if (y < 0 || y > height) + throw new ArgumentOutOfRangeException(nameof(y), "y is out bounds"); + if (width <= 0) + throw new ArgumentOutOfRangeException(nameof(width), "Width must be positive"); + if (height <= 0) + throw new ArgumentOutOfRangeException(nameof(height), "Height must be positive"); + + switch (brush) + { + case ILinearGradientBrush gradientBrush: + { + // Calculate the relative position within the gradient + double horizontalRelativePosition = (double)x / width; + double verticalRelativePosition = (double)y / height; + + // Interpolate horizontal and vertical colors + Color horizontalColor = InterpolateColor(gradientBrush, horizontalRelativePosition); + Color verticalColor = InterpolateColor(gradientBrush, verticalRelativePosition); + + // Average the two colors to get the final color + Color color = BlendColors(horizontalColor, verticalColor); + return new ConsoleBrush(color); + } + case IRadialGradientBrush radialBrush: + { + // Calculate the normalized center coordinates + double centerX = radialBrush.Center.Point.X * width; + double centerY = radialBrush.Center.Point.Y * height; + + // Calculate the distance from the center + double dx = x - centerX; + double dy = y - centerY; + double distance = Math.Sqrt(dx * dx + dy * dy); + + // Normalize the distance based on the brush radius + double normalizedDistance = distance / (Math.Min(width, height) * radialBrush.Radius); + + // Clamp the normalized distance to [0, 1] + normalizedDistance = Math.Min(Math.Max(normalizedDistance, 0), 1); + + // Interpolate the color based on the normalized distance + Color color = InterpolateColor(radialBrush, normalizedDistance); + return new ConsoleBrush(color); + } + case IConicGradientBrush conicBrush: + { + // Calculate the relative position within the gradient + double horizontalRelativePosition = (double)x / width; + double verticalRelativePosition = (double)y / height; + + // Interpolate horizontal and vertical colors + Color horizontalColor = InterpolateColor(conicBrush, horizontalRelativePosition); + Color verticalColor = InterpolateColor(conicBrush, verticalRelativePosition); + + // Average the two colors to get the final color + Color color = BlendColors(horizontalColor, verticalColor); + return new ConsoleBrush(color); + } + + default: + return FromBrush(brush); + } + } + + // ReSharper disable once UnusedMember.Global used by Avalonia + // ReSharper disable once UnusedParameter.Global + public IBrush ProvideValue(IServiceProvider _) + { + return this; + } + + private static Color InterpolateColor(IGradientBrush brush, double relativePosition) + { + IGradientStop before = null; + IGradientStop after = null; + + foreach (IGradientStop stop in brush.GradientStops) + if (stop.Offset <= relativePosition) + { + before = stop; + } + else if (stop.Offset >= relativePosition) + { + after = stop; + break; + } + + if (before == null && after == null) + throw new ArgumentException("no gradientstops defined"); + + if (before == null) return after.Color; + if (after == null) return before.Color; + + double ratio = (relativePosition - before.Offset) / (after.Offset - before.Offset); + byte r = (byte)(before.Color.R + ratio * (after.Color.R - before.Color.R)); + byte g = (byte)(before.Color.G + ratio * (after.Color.G - before.Color.G)); + byte b = (byte)(before.Color.B + ratio * (after.Color.B - before.Color.B)); + byte a = (byte)(before.Color.A + ratio * (after.Color.A - before.Color.A)); + + return Color.FromArgb(a, r, g, b); + } + + private static Color BlendColors(Color color1, Color color2) + { + int r = (color1.R + color2.R) / 2; + int g = (color1.G + color2.G) / 2; + int b = (color1.B + color2.B) / 2; + int a = (color1.A + color2.A) / 2; + return Color.FromArgb((byte)a, (byte)r, (byte)g, (byte)b); + } + } +} \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs index ba90be07..2b5d9f4a 100644 --- a/src/Consolonia.Core/Drawing/DrawingContextImpl.cs +++ b/src/Consolonia.Core/Drawing/DrawingContextImpl.cs @@ -3,6 +3,7 @@ using System.Linq; using Avalonia; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.Platform; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; @@ -119,38 +120,33 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) case ISceneBrush sceneBrush: { ISceneBrushContent sceneBrushContent = sceneBrush.CreateContent(); - sceneBrushContent!.Render(this, Matrix.Identity); + if (sceneBrushContent != null) sceneBrushContent.Render(this, Matrix.Identity); return; } } - if (brush is not FourBitColorBrush backgroundBrush) - { - ConsoloniaPlatform.RaiseNotSupported(9, brush, pen, rect, boxShadows); - return; - } + Rect r2 = r.TransformToAABB(Transform); + 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++) { - Rect r2 = r.TransformToAABB(Transform); + int px = (int)(r2.TopLeft.X + x); + int py = (int)(r2.TopLeft.Y + y); - (double x, double y) = r2.TopLeft; - for (int i = 0; i < r2.Width + (pen?.Thickness ?? 0); i++) - for (int j = 0; j < r2.Height + (pen?.Thickness ?? 0); j++) + ConsoleBrush backgroundBrush = ConsoleBrush.FromPosition(brush, x, y, (int)width, (int)height); + CurrentClip.ExecuteWithClipping(new Point(px, py), () => { - int px = (int)(x + i); - int py = (int)(y + j); - 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); - }); - } + _pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py), + (pixel, bb) => { return pixel.Blend(new Pixel(new PixelBackground(bb.Mode, bb.Color))); }, + backgroundBrush); + }); } } - if (pen is null or { Thickness: 0 } or { Brush: null }) return; + if (pen is null or { Thickness: 0 } + or { Brush: null }) return; DrawLineInternal(pen, new Line(r.TopLeft, false, (int)r.Width)); DrawLineInternal(pen, new Line(r.BottomLeft, false, (int)r.Width)); @@ -180,7 +176,7 @@ public void DrawGlyphRun(IBrush foreground, IGlyphRunImpl glyphRun) string charactersDoDraw = string.Concat(glyphRunImpl.GlyphIndices.Select(us => (char)us).ToArray()); - DrawStringInternal(foreground, charactersDoDraw); + DrawStringInternal(foreground, charactersDoDraw, glyphRun.GlyphTypeface); } public IDrawingContextLayerImpl CreateLayer(Size size) @@ -262,14 +258,14 @@ public Matrix Transform set => _transform = value * _postTransform; } - private static ConsoleColor? ExtractConsoleColorOrNullWithPlatformCheck(IPen pen, out LineStyle? lineStyle) + private static Color? ExtractColorOrNullWithPlatformCheck(IPen pen, out LineStyle? lineStyle) { lineStyle = null; if (pen is not { - Brush: FourBitColorBrush or LineBrush { Brush: FourBitColorBrush }, + Brush: ConsoleBrush or LineBrush or ImmutableSolidColorBrush, Thickness: 1, - DashStyle: null or { Dashes.Count: 0 }, + DashStyle: null or { Dashes: { Count: 0 } }, LineCap: PenLineCap.Flat, LineJoin: PenLineJoin.Miter }) @@ -278,12 +274,10 @@ public Matrix Transform return null; } - if (pen.Brush is not FourBitColorBrush consoleColorBrush) - { - var lineBrush = (LineBrush)pen.Brush; - consoleColorBrush = (FourBitColorBrush)lineBrush.Brush; + if (pen.Brush is LineBrush lineBrush) lineStyle = lineBrush.LineStyle; - } + + ConsoleBrush consoleColorBrush = ConsoleBrush.FromBrush(pen.Brush); switch (consoleColorBrush.Mode) { @@ -317,12 +311,12 @@ private void DrawLineInternal(IPen pen, Line line) return; } - var extractConsoleColorCheckPlatformSupported = - ExtractConsoleColorOrNullWithPlatformCheck(pen, out var lineStyle); - if (extractConsoleColorCheckPlatformSupported == null) + var extractColorCheckPlatformSupported = + ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle); + if (extractColorCheckPlatformSupported == null) return; - var consoleColor = (ConsoleColor)extractConsoleColorCheckPlatformSupported; + var consoleColor = (Color)extractColorCheckPlatformSupported; byte pattern = (byte)(line.Vertical ? 0b0010 : 0b0100); DrawPixelAndMoveHead(1); //beginning @@ -354,9 +348,10 @@ void DrawPixelAndMoveHead(int count) } } - private void DrawStringInternal(IBrush foreground, string str, Point origin = new()) + private void DrawStringInternal(IBrush foreground, string str, IGlyphTypeface typeface, Point origin = new()) { - if (foreground is not FourBitColorBrush { Mode: PixelBackgroundMode.Colored } consoleColorBrush) + foreground = ConsoleBrush.FromBrush(foreground); + if (foreground is not ConsoleBrush { Mode: PixelBackgroundMode.Colored } consoleColorBrush) { ConsoloniaPlatform.RaiseNotSupported(4); return; @@ -371,7 +366,7 @@ void DrawPixelAndMoveHead(int count) foreach (char c in str) { Point characterPoint = whereToDraw.Transform(Matrix.CreateTranslation(currentXPosition++, 0)); - ConsoleColor foregroundColor = consoleColorBrush.Color; + Color foregroundColor = consoleColorBrush.Color; switch (c) { @@ -406,7 +401,7 @@ void DrawPixelAndMoveHead(int count) break; default: { - var consolePixel = new Pixel(c, foregroundColor); + var consolePixel = new Pixel(c, foregroundColor, typeface.Style, typeface.Weight); CurrentClip.ExecuteWithClipping(characterPoint, () => { _pixelBuffer.Set((PixelBufferCoordinate)characterPoint, diff --git a/src/Consolonia.Core/Drawing/FourBitColorBrush.cs b/src/Consolonia.Core/Drawing/FourBitColorBrush.cs deleted file mode 100644 index bd523b82..00000000 --- a/src/Consolonia.Core/Drawing/FourBitColorBrush.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.ComponentModel; -using System.Globalization; -using Avalonia; -using Avalonia.Media; -using Consolonia.Core.Drawing.PixelBufferImplementation; - -namespace Consolonia.Core.Drawing -{ - [TypeConverter(typeof(FourBitBrushConverter))] - public class FourBitColorBrush : AvaloniaObject, IImmutableBrush - { - public static readonly StyledProperty ColorProperty = - AvaloniaProperty.Register(nameof(Color)); - - public static readonly StyledProperty ModeProperty = - AvaloniaProperty.Register(nameof(Mode)); - - // ReSharper disable once UnusedMember.Global - public FourBitColorBrush(ConsoleColor consoleColor, PixelBackgroundMode mode) : this(consoleColor) - { - Mode = mode; - } - - public FourBitColorBrush(ConsoleColor consoleColor) - { - Color = consoleColor; - } - - public FourBitColorBrush() - { - } - - public PixelBackgroundMode Mode - { - get => GetValue(ModeProperty); - set => SetValue(ModeProperty, value); - } - - public ConsoleColor Color - { - get => GetValue(ColorProperty); - set => SetValue(ColorProperty, value); - } - - public double Opacity => 1; - public ITransform Transform => null; - public RelativePoint TransformOrigin => RelativePoint.TopLeft; - - // ReSharper disable once UnusedMember.Global used by Avalonia - // ReSharper disable once UnusedParameter.Global - public IBrush ProvideValue(IServiceProvider _) - { - return this; - } - } - - public class FourBitBrushConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof(string); - } - - public override object ConvertFrom( - ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value is string s) - return Enum.TryParse(s, out ConsoleColor result) - ? result - : null; - - return null; - } - } -} \ 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 99748959..25d289e0 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global @@ -8,7 +9,9 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation public readonly struct Pixel { public PixelForeground Foreground { get; } + public PixelBackground Background { get; } + public bool IsCaret { get; } public Pixel(bool isCaret) : this(PixelBackgroundMode.Transparent) @@ -16,23 +19,25 @@ public Pixel(bool isCaret) : this(PixelBackgroundMode.Transparent) IsCaret = isCaret; } - public Pixel(char character, ConsoleColor foregroundColor) : this(new SimpleSymbol(character), - foregroundColor) + public Pixel(char character, Color foregroundColor, FontStyle style = FontStyle.Normal, + FontWeight weight = FontWeight.Normal) : + this(new SimpleSymbol(character), foregroundColor, style, weight) { } - public Pixel(byte drawingBoxSymbol, ConsoleColor foregroundColor) : this( + public Pixel(byte drawingBoxSymbol, Color foregroundColor) : this( new DrawingBoxSymbol(drawingBoxSymbol), foregroundColor) { } - public Pixel(ISymbol symbol, ConsoleColor foregroundColor) : this( - new PixelForeground(symbol, foregroundColor), + 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), new PixelBackground(PixelBackgroundMode.Transparent)) { } - public Pixel(ConsoleColor backgroundColor) : this( + public Pixel(Color backgroundColor) : this( new PixelBackground(PixelBackgroundMode.Colored, backgroundColor)) { } @@ -63,7 +68,17 @@ public Pixel Blend(Pixel pixelAbove) case PixelBackgroundMode.Colored: return pixelAbove; case PixelBackgroundMode.Transparent: - newForeground = Foreground.Blend(pixelAbove.Foreground); + // when a textdecoration of underline happens a DrawLine() is called over the top of the a pixel with non-zero symbol. + // this detects this situation and eats the draw line, turning it into a textdecoration + if (pixelAbove.Foreground.Symbol is DrawingBoxSymbol && + Foreground.Symbol is SimpleSymbol simpleSymbol && + ((ISymbol)simpleSymbol).GetCharacter() != (char)0) + // this is a line being draw through text. add TextDecoration for underline. + newForeground = new PixelForeground(Foreground.Symbol, Foreground.Weight, Foreground.Style, + TextDecorations.Underline, Foreground.Color); + else + // do normal blend. + newForeground = Foreground.Blend(pixelAbove.Foreground); newBackground = Background; break; case PixelBackgroundMode.Shaded: diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs index fa38b802..ddefd3af 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelBackground.cs @@ -1,21 +1,22 @@ using System; +using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { public readonly struct PixelBackground { - public PixelBackground(PixelBackgroundMode mode, ConsoleColor color = ConsoleColor.Black) + public PixelBackground(PixelBackgroundMode mode, Color? color = null) { - Color = color; + Color = color ?? Colors.Black; Mode = mode; } - public ConsoleColor Color { get; } + public Color Color { get; } public PixelBackgroundMode Mode { get; } public PixelBackground Shade() { - ConsoleColor newColor = Color; + Color newColor = Color; PixelBackgroundMode newMode = Mode; switch (Mode) { @@ -27,7 +28,7 @@ public PixelBackground Shade() break; case PixelBackgroundMode.Shaded: newMode = PixelBackgroundMode.Colored; - newColor = ConsoleColor.Black; + newColor = Colors.Black; break; default: throw new ArgumentOutOfRangeException(); diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs index 1f80191a..36091826 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelForeground.cs @@ -1,34 +1,49 @@ using System; +using Avalonia.Media; namespace Consolonia.Core.Drawing.PixelBufferImplementation { public readonly struct PixelForeground { - public PixelForeground(ISymbol symbol, ConsoleColor color = ConsoleColor.White) + public PixelForeground(ISymbol symbol, FontWeight weight = FontWeight.Normal, + FontStyle style = FontStyle.Normal, TextDecorationCollection textDecorations = null, Color? color = null) { + ArgumentNullException.ThrowIfNull(symbol); Symbol = symbol; - Color = color; + Color = color ?? Colors.White; + Weight = weight; + Style = style; + TextDecorations = textDecorations; } public ISymbol Symbol { get; } //now working with 16 bit unicode only for simplicity //todo: reference here - public ConsoleColor Color { get; } + + public Color Color { get; } + + public FontWeight Weight { get; } + + public FontStyle Style { get; } + + public TextDecorationCollection TextDecorations { get; } public PixelForeground Shade() { - ConsoleColor newColor = Color.Shade(); - return new PixelForeground(Symbol, newColor); + Color newColor = Color.Shade(); + return new PixelForeground(Symbol, Weight, Style, TextDecorations, newColor); } public PixelForeground Blend(PixelForeground pixelAboveForeground) { //todo: check default(char) is there ISymbol symbolAbove = pixelAboveForeground.Symbol; + ArgumentNullException.ThrowIfNull(symbolAbove); if (symbolAbove.IsWhiteSpace()) return this; - ConsoleColor newColor = pixelAboveForeground.Color; ISymbol newSymbol = Symbol.Blend(ref symbolAbove); - return new PixelForeground(newSymbol, newColor); + + return new PixelForeground(newSymbol, pixelAboveForeground.Weight, pixelAboveForeground.Style, + pixelAboveForeground.TextDecorations, pixelAboveForeground.Color); } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelOperations.cs b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelOperations.cs index 6f3f6460..8cfda27d 100644 --- a/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelOperations.cs +++ b/src/Consolonia.Core/Drawing/PixelBufferImplementation/PixelOperations.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Media; // ReSharper disable UnusedMember.Global @@ -6,25 +7,77 @@ namespace Consolonia.Core.Drawing.PixelBufferImplementation { internal static class PixelOperations { - public static ConsoleColor Shade(this ConsoleColor color) + private const int ColorAdjustment = 32; + + /// + /// Make color darker + /// + /// + /// + public static Color Shade(this Color color) + { + return color.Shade(Colors.Black); + } + + /// + /// Make color brighter + /// + /// + /// + public static Color Brighten(this Color color) + { + return color.Brighten(Colors.Black); + } + + /// + /// Make color less contrast with background color + /// + /// + /// + /// + public static Color Shade(this Color foreground, Color background) + { + int foregroundBrightness = GetPerceivedBrightness(foreground); + int backgroundBrightness = GetPerceivedBrightness(background); + if (foregroundBrightness > backgroundBrightness || + foregroundBrightness == backgroundBrightness && foregroundBrightness > 0) + // if color is lighter than background shading should make it more dark (less contrast) + return Color.FromRgb((byte)Math.Max(0, foreground.R - ColorAdjustment), + (byte)Math.Max(0, foreground.G - ColorAdjustment), + (byte)Math.Max(0, foreground.B - ColorAdjustment)); + // if color is darker than background shading should make it more bright (less contrast) + return Color.FromRgb((byte)Math.Min(byte.MaxValue, foreground.R + ColorAdjustment), + (byte)Math.Min(byte.MaxValue, foreground.G + ColorAdjustment), + (byte)Math.Min(byte.MaxValue, foreground.B + ColorAdjustment)); + } + + /// + /// Make color more contrast with background color + /// + /// + /// + /// + public static Color Brighten(this Color foreground, Color background) { - // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (color) - { - case ConsoleColor.Black: return color; - case ConsoleColor.White: return ConsoleColor.Gray; - default: - string name = Enum.GetName(color) ?? throw new NotImplementedException(); - const string dark = "Dark"; - return !name.Contains(dark, StringComparison.Ordinal) - ? Enum.Parse(dark + name) - : ConsoleColor.Black; - } + int foregroundBrightness = GetPerceivedBrightness(foreground); + int backgroundBrightness = GetPerceivedBrightness(background); + if (foregroundBrightness > backgroundBrightness || foregroundBrightness == backgroundBrightness && + foregroundBrightness < byte.MaxValue) + // if color is lighter than background brighten should make it more bright (more contrast) + return Color.FromRgb((byte)Math.Min(byte.MaxValue, foreground.R + ColorAdjustment), + (byte)Math.Min(byte.MaxValue, foreground.G + ColorAdjustment), + (byte)Math.Min(byte.MaxValue, foreground.B + ColorAdjustment)); + // if color is darker than background brighten should make it more dark (more contrast) + return Color.FromRgb((byte)Math.Max(0, foreground.R - ColorAdjustment), + (byte)Math.Max(0, foreground.G - ColorAdjustment), + (byte)Math.Max(0, foreground.B - ColorAdjustment)); } - public static (int integerFraction, int remainder) Divide(int a, int b) + private static int GetPerceivedBrightness(Color color) { - return (a / b, a % b); + return (int)(0.299 * color.R + + 0.587 * color.G + + 0.114 * color.B); } } } \ No newline at end of file diff --git a/src/Consolonia.Core/Drawing/RenderTarget.cs b/src/Consolonia.Core/Drawing/RenderTarget.cs index d4cacc4e..631f5b88 100644 --- a/src/Consolonia.Core/Drawing/RenderTarget.cs +++ b/src/Consolonia.Core/Drawing/RenderTarget.cs @@ -5,6 +5,7 @@ using System.Text; using Avalonia; using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Platform; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; @@ -19,7 +20,8 @@ internal class RenderTarget : IDrawingContextLayerImpl private PixelBuffer _bufferBuffer; - private (ConsoleColor background, ConsoleColor foreground, char character)?[,] _cache; + private (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection + textDecorations, char character)?[,] _cache; internal RenderTarget(ConsoleWindow consoleWindow) { @@ -94,7 +96,9 @@ private void InitializeBuffer(Size size) private void InitializeCache(ushort width, ushort height) { - _cache = new (ConsoleColor background, ConsoleColor foreground, char character)?[width, height]; + _cache = + new (Color background, Color foreground, FontWeight weight, FontStyle style, TextDecorationCollection + textDecorations, char character)?[width, height]; } private void RenderToDevice() @@ -125,45 +129,22 @@ private void RenderToDevice() throw new InvalidOperationException( "All pixels in the buffer must have exact console color before rendering"); - if (x == pixelBuffer.Width - 1 && y == pixelBuffer.Height - 1) - break; - - char character; - try - { - character = pixel.Foreground.Symbol.GetCharacter(); - } - catch (NullReferenceException) - { - //todo: need to break current drawing - if (pixel.Foreground.Symbol is null) // not using 'when' as it swallows the exceptions - { - // buffer re-initialized after resizing - character = '░'; - pixel = new Pixel(new PixelForeground(), new PixelBackground(PixelBackgroundMode.Colored)); - } - else - { - throw; - } - } - - if (char.IsControl(character) /*|| character is '保' or '哥'*/) - character = ' '; // some terminals do not print \0 - - ConsoleColor backgroundColor = pixel.Background.Color; - ConsoleColor foregroundColor = pixel.Foreground.Color; + 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 PixelBackground(PixelBackgroundMode.Colored)); + (Color, Color, FontWeight Weight, FontStyle Style, TextDecorationCollection TextDecorations, char) + pixelSpread = (pixel.Background.Color, pixel.Foreground.Color, pixel.Foreground.Weight, + pixel.Foreground.Style, pixel.Foreground.TextDecorations, + pixel.Foreground.Symbol.GetCharacter()); //todo: indexOutOfRange during resize - if (_cache[x, y] == (backgroundColor, foregroundColor, character)) + if (_cache[x, y] == pixelSpread) continue; - _cache[x, y] = (backgroundColor, foregroundColor, character); + _cache[x, y] = pixelSpread; - flushingBuffer.WriteCharacter(new PixelBufferCoordinate(x, y), - backgroundColor, - foregroundColor, - character); + flushingBuffer.WritePixel(new PixelBufferCoordinate(x, y), pixel); } flushingBuffer.Flush(); @@ -184,8 +165,11 @@ private struct FlushingBuffer //todo: move class out private readonly IConsole _console; private readonly StringBuilder _stringBuilder; - private ConsoleColor _lastBackgroundColor; - private ConsoleColor _lastForegroundColor; + private Color _lastBackgroundColor; + private Color _lastForegroundColor; + private FontStyle _lastStyle = FontStyle.Normal; + private FontWeight _lastWeight = FontWeight.Normal; + private TextDecorationCollection _lastTextDecorations = new(); private PixelBufferCoordinate _currentBufferPoint; private PixelBufferCoordinate _lastBufferPointStart; @@ -196,24 +180,31 @@ public FlushingBuffer(IConsole console) _stringBuilder = new StringBuilder(); } - public void WriteCharacter( + public void WritePixel( PixelBufferCoordinate bufferPoint, - ConsoleColor backgroundColor, - ConsoleColor foregroundColor, - char character) + Pixel pixel) { if (!bufferPoint.Equals(_currentBufferPoint) /*todo: performance*/ || - _lastForegroundColor != foregroundColor || - _lastBackgroundColor != backgroundColor) + _lastForegroundColor != pixel.Foreground.Color || + _lastBackgroundColor != pixel.Background.Color || + _lastWeight != pixel.Foreground.Weight || + _lastStyle != pixel.Foreground.Style || + _lastTextDecorations != pixel.Foreground.TextDecorations) Flush(); if (_stringBuilder.Length == 0) { - _lastBackgroundColor = backgroundColor; - _lastForegroundColor = foregroundColor; + _lastBackgroundColor = pixel.Background.Color; + _lastForegroundColor = pixel.Foreground.Color; + _lastStyle = pixel.Foreground.Style; + _lastWeight = pixel.Foreground.Weight; + _lastTextDecorations = pixel.Foreground.TextDecorations; _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); _currentBufferPoint = _currentBufferPoint.WithXpp(); } @@ -223,8 +214,8 @@ public void Flush() if (_stringBuilder.Length == 0) return; - _console.Print(_lastBufferPointStart, _lastBackgroundColor, _lastForegroundColor, - _stringBuilder.ToString()); + _console.Print(_lastBufferPointStart, _lastBackgroundColor, _lastForegroundColor, _lastStyle, + _lastWeight, _lastTextDecorations, _stringBuilder.ToString()); _stringBuilder.Clear(); } } diff --git a/src/Consolonia.Core/Infrastructure/IConsole.cs b/src/Consolonia.Core/Infrastructure/IConsole.cs index 48a477e5..a3a89346 100644 --- a/src/Consolonia.Core/Infrastructure/IConsole.cs +++ b/src/Consolonia.Core/Infrastructure/IConsole.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; // ReSharper disable UnusedMember.Global @@ -19,8 +20,8 @@ public interface IConsole : IDisposable void SetCaretPosition(PixelBufferCoordinate bufferPoint); PixelBufferCoordinate GetCaretPosition(); - void Print(PixelBufferCoordinate bufferPoint, ConsoleColor backgroundColor, ConsoleColor foregroundColor, - string str); + void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, + FontWeight weight, TextDecorationCollection textDecorations, string str); event Action Resized; diff --git a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs index 02f59ca8..de9891fa 100644 --- a/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs +++ b/src/Consolonia.Core/Infrastructure/InputLessDefaultNetConsole.cs @@ -1,11 +1,13 @@ -using System; +using System; using System.Linq; using System.Text; using System.Threading.Tasks; using Avalonia; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Media; using Consolonia.Core.Drawing.PixelBufferImplementation; +using Crayon; using NullLib.ConsoleEx; namespace Consolonia.Core.Infrastructure @@ -13,9 +15,7 @@ namespace Consolonia.Core.Infrastructure public class InputLessDefaultNetConsole : IConsole { private bool _caretVisible; - private ConsoleColor _headBackground; private PixelBufferCoordinate _headBufferPoint; - private ConsoleColor _headForeground; protected InputLessDefaultNetConsole() { @@ -66,17 +66,12 @@ public PixelBufferCoordinate GetCaretPosition() return _headBufferPoint; } - public void Print(PixelBufferCoordinate bufferPoint, ConsoleColor backgroundColor, ConsoleColor foregroundColor, - string str) + public void Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, + FontWeight weight, TextDecorationCollection textDecorations, string str) { PauseTask?.Wait(); SetCaretPosition(bufferPoint); - if (_headBackground != backgroundColor) - _headBackground = Console.BackgroundColor = backgroundColor; - if (_headForeground != foregroundColor) - _headForeground = Console.ForegroundColor = foregroundColor; - if (!str.IsNormalized(NormalizationForm.FormKC)) throw new NotSupportedException("Is not supposed to be rendered"); @@ -85,7 +80,20 @@ public void Print(PixelBufferCoordinate bufferPoint, ConsoleColor backgroundColo char.IsLetterOrDigit(c) /*todo: https://github.com/SlimeNull/NullLib.ConsoleEx/issues/2*/)) throw new NotSupportedException("Is not supposed to be rendered"); - Console.Write(str); + if (textDecorations != null && textDecorations.Any(td => td.Location == TextDecorationLocation.Underline)) + str = Output.Underline(str); + + if (weight == FontWeight.Normal) + foreground = foreground.Shade(background); + else if (weight == FontWeight.Thin || weight == FontWeight.ExtraLight || weight == FontWeight.Light) + foreground = foreground.Shade(background).Shade(background); + else if (weight == FontWeight.Medium || weight == FontWeight.SemiBold || weight == FontWeight.Bold || + weight == FontWeight.ExtraBold || weight == FontWeight.Black || weight == FontWeight.ExtraBlack) + foreground = foreground.Brighten(background); + Console.Write(Output.Rgb(foreground.R, foreground.G, foreground.B) + .Background.Rgb(background.R, background.G, background.B) + .Text(str)); + if (_headBufferPoint.X < Size.Width - str.Length) _headBufferPoint = diff --git a/src/Consolonia.Core/Text/FontManagerImpl.cs b/src/Consolonia.Core/Text/FontManagerImpl.cs index c3c53874..fbf44abd 100644 --- a/src/Consolonia.Core/Text/FontManagerImpl.cs +++ b/src/Consolonia.Core/Text/FontManagerImpl.cs @@ -32,7 +32,11 @@ public bool TryCreateGlyphTypeface(string familyName, FontStyle style, FontWeigh out IGlyphTypeface glyphTypeface) { //todo: check font is ours the only - glyphTypeface = new GlyphTypeface(); + glyphTypeface = new GlyphTypeface + { + Weight = weight, + Style = style + }; return true; } diff --git a/src/Consolonia.Core/Text/GlyphRunImpl.cs b/src/Consolonia.Core/Text/GlyphRunImpl.cs index 7c141569..7fbcbb50 100644 --- a/src/Consolonia.Core/Text/GlyphRunImpl.cs +++ b/src/Consolonia.Core/Text/GlyphRunImpl.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Avalonia; @@ -28,7 +27,7 @@ public void Dispose() public IReadOnlyList GetIntersections(float lowerLimit, float upperLimit) { - throw new NotImplementedException(); + return new List(); } public IGlyphTypeface GlyphTypeface { get; } diff --git a/src/Consolonia.Core/Text/GlyphTypeface.cs b/src/Consolonia.Core/Text/GlyphTypeface.cs index c8d28083..ad4a7c2d 100644 --- a/src/Consolonia.Core/Text/GlyphTypeface.cs +++ b/src/Consolonia.Core/Text/GlyphTypeface.cs @@ -61,8 +61,8 @@ public bool TryGetTable(uint tag, out byte[] table) } public string FamilyName { get; } = FontManagerImpl.GetTheOnlyFontFamilyName(); - public FontWeight Weight => FontWeight.Normal; - public FontStyle Style => FontStyle.Normal; + public FontWeight Weight { get; set; } = FontWeight.Normal; + public FontStyle Style { get; set; } = FontStyle.Normal; public FontStretch Stretch => FontStretch.Normal; public int GlyphCount => char.MaxValue; @@ -73,8 +73,9 @@ public bool TryGetTable(uint tag, out byte[] table) Ascent = -1, //var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; //todo: need to consult Avalonia guys Descent = 0, LineGap = 0, - UnderlinePosition = 0, - UnderlineThickness = 0, + UnderlinePosition = + -1, // this turns on TextDecorations="Underline", -1 is so it draws over the top of the text. + UnderlineThickness = 1, // this is the thickness of the underline, aka 1 char thick. StrikethroughPosition = 0, StrikethroughThickness = 0, IsFixedPitch = true diff --git a/src/Consolonia.Core/Text/TextShaper.cs b/src/Consolonia.Core/Text/TextShaper.cs index 84a26c9d..5331aeab 100644 --- a/src/Consolonia.Core/Text/TextShaper.cs +++ b/src/Consolonia.Core/Text/TextShaper.cs @@ -14,7 +14,7 @@ public ShapedBuffer ShapeText(ReadOnlyMemory text, TextShaperOptions optio var glyphInfos = Convert(text.Span.ToString()); var shapedBuffer = new ShapedBuffer(text, glyphInfos.Length, - new GlyphTypeface(), 1, 0 /*todo: must be 1 for right to left?*/); + options.Typeface, 1, 0 /*todo: must be 1 for right to left?*/); for (int i = 0; i < shapedBuffer.Length; i++) shapedBuffer[i] = glyphInfos[i]; return shapedBuffer; diff --git a/src/Consolonia.Gallery/Consolonia.Gallery.csproj b/src/Consolonia.Gallery/Consolonia.Gallery.csproj index 2b6a024d..a964102e 100644 --- a/src/Consolonia.Gallery/Consolonia.Gallery.csproj +++ b/src/Consolonia.Gallery/Consolonia.Gallery.csproj @@ -17,4 +17,17 @@ + + + MSBuild:Compile + + + + + + Code + GalleryGradientBrush.axaml + + + diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryAnimatedLines.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryAnimatedLines.axaml index 208dac67..6d15341c 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryAnimatedLines.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryAnimatedLines.axaml @@ -4,9 +4,8 @@ x:Class="Consolonia.Gallery.Gallery.GalleryViews.GalleryAnimatedLines"> - + @@ -46,10 +45,7 @@ VerticalAlignment="Center" HorizontalAlignment="Right"> - - - - + @@ -89,11 +85,7 @@ VerticalAlignment="Bottom" HorizontalAlignment="Right"> - - - - - + - - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryGradientBrush.xaml.cs b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryGradientBrush.xaml.cs new file mode 100644 index 00000000..977b9a25 --- /dev/null +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryGradientBrush.xaml.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Consolonia.Core.Drawing; + +namespace Consolonia.Gallery.Gallery.GalleryViews +{ + [GalleryOrder(1000)] + public partial class GalleryGradientBrush : UserControl + { + public GalleryGradientBrush() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void Linear_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + this.FindControl("MyGrid").Background = new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative), + EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative), + GradientStops = new GradientStops + { + new GradientStop { Color = Colors.White, Offset = 0 }, + new GradientStop { Color = Colors.Red, Offset = 1 } + } + }; + } + + private void Radial_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + this.FindControl("MyGrid").Background = new RadialGradientBrush + { + Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative), + GradientOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative), + Radius = 0.5, + GradientStops = new GradientStops + { + new GradientStop { Color = Colors.Black, Offset = 0 }, + new GradientStop { Color = Colors.Red, Offset = 1 } + } + }; + } + private void Conic_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e) + { + + this.FindControl("MyGrid").Background = new ConicGradientBrush + { + Center = new RelativePoint(0.5, 0.5, RelativeUnit.Relative), + Angle = 0, + GradientStops = new GradientStops + { + new GradientStop { Color = Colors.Red, Offset = 0 }, + new GradientStop { Color = Colors.Yellow, Offset = 0.25 }, + new GradientStop { Color = Colors.Green, Offset = 0.5 }, + new GradientStop { Color = Colors.Cyan, Offset = 0.75 }, + new GradientStop { Color = Colors.Blue, Offset = 1 } + } + }; + + } + } + +} \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryListBox.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryListBox.axaml index a4edd6b9..44b14827 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryListBox.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryListBox.axaml @@ -6,11 +6,11 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBox.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBox.axaml index 2cbbb006..e67ec4a9 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBox.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryTextBox.axaml @@ -57,8 +57,8 @@ + SelectionBrush="Magenta" + SelectionForegroundBrush="Yellow" /> - Welcome to Avalonia! + + Welcome to Consolonia! + \ No newline at end of file diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/SomeDialogWindow.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/SomeDialogWindow.axaml index 9b5aa0e6..06c80b17 100644 --- a/src/Consolonia.Gallery/Gallery/GalleryViews/SomeDialogWindow.axaml +++ b/src/Consolonia.Gallery/Gallery/GalleryViews/SomeDialogWindow.axaml @@ -10,7 +10,7 @@ . + Foreground="DarkRed">. /; ;\ __ \\____// /{_\_/ `'\____ diff --git a/src/Consolonia.Gallery/View/ControlsListView.axaml b/src/Consolonia.Gallery/View/ControlsListView.axaml index 94cc117d..0c8a46d8 100644 --- a/src/Consolonia.Gallery/View/ControlsListView.axaml +++ b/src/Consolonia.Gallery/View/ControlsListView.axaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:gallery="clr-namespace:Consolonia.Gallery.Gallery" x:Class="Consolonia.Gallery.View.ControlsListView" - Title="ControlsListView"> + Title="ControlsListView" Background="LightGray"> diff --git a/src/Consolonia.PlatformSupport/WindowsConsole.cs b/src/Consolonia.PlatformSupport/WindowsConsole.cs index 34a2eef9..d88d7d57 100644 --- a/src/Consolonia.PlatformSupport/WindowsConsole.cs +++ b/src/Consolonia.PlatformSupport/WindowsConsole.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Diagnostics; using System.Linq; @@ -163,9 +163,7 @@ private bool HandleMouseInput(WindowsConsole.MouseEventRecord mouseEvent) repeat = 0; break; case WindowsConsole.EventFlags.MouseWheeled: - double velocity = 1 / 12D; - if (incomeMouseState == -7864320) - velocity *= -1; + double velocity = incomeMouseState < 0 ? -1 : 1; wheelDelta = new Vector(0, velocity); eventType = RawPointerEventType.Wheel; break; @@ -174,6 +172,10 @@ private bool HandleMouseInput(WindowsConsole.MouseEventRecord mouseEvent) case WindowsConsole.EventFlags.MouseMoved: eventType = RawPointerEventType.Move; break; + case WindowsConsole.EventFlags.MouseMoved | WindowsConsole.EventFlags.DoubleClick: + RaiseMouseEvent(RawPointerEventType.LeftButtonDown, point, null, inputModifiers); + RaiseMouseEvent(RawPointerEventType.Move, point, null, inputModifiers); + return false; default: throw new InvalidOperationException(mouseEvent.EventFlags.ToString()); } @@ -257,7 +259,7 @@ private static extern bool WriteConsoleInput( // Resharper restore MemberCanBePrivate.Local // Resharper restore FieldCanBeMadeReadOnly.Global // Resharper restore FieldCanBeMadeReadOnly.Local -// ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming #endregion } diff --git a/src/Consolonia.Themes.TurboVision/Templates/Controls/CalendarItem.axaml b/src/Consolonia.Themes.TurboVision/Templates/Controls/CalendarItem.axaml index 5925a239..c0cb0240 100644 --- a/src/Consolonia.Themes.TurboVision/Templates/Controls/CalendarItem.axaml +++ b/src/Consolonia.Themes.TurboVision/Templates/Controls/CalendarItem.axaml @@ -21,7 +21,7 @@ --> diff --git a/src/Consolonia.Themes.TurboVision/Templates/TurboVisionDefaultColors.axaml b/src/Consolonia.Themes.TurboVision/Templates/TurboVisionDefaultColors.axaml index 60d965c7..adec0823 100644 --- a/src/Consolonia.Themes.TurboVision/Templates/TurboVisionDefaultColors.axaml +++ b/src/Consolonia.Themes.TurboVision/Templates/TurboVisionDefaultColors.axaml @@ -4,30 +4,30 @@ xmlns:drawing="clr-namespace:Consolonia.Core.Drawing;assembly=Consolonia.Core"> 1 - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Consolonia.Themes.TurboVision/Themes/TurboVisionBlack/TurboVisionBlackColors.axaml b/src/Consolonia.Themes.TurboVision/Themes/TurboVisionBlack/TurboVisionBlackColors.axaml index 549c2102..a3483c34 100644 --- a/src/Consolonia.Themes.TurboVision/Themes/TurboVisionBlack/TurboVisionBlackColors.axaml +++ b/src/Consolonia.Themes.TurboVision/Themes/TurboVisionBlack/TurboVisionBlackColors.axaml @@ -2,28 +2,28 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:drawing="clr-namespace:Consolonia.Core.Drawing;assembly=Consolonia.Core"> - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Consolonia.Themes.TurboVision/Themes/TurboVisionDark/TurboVisionDarkColors.axaml b/src/Consolonia.Themes.TurboVision/Themes/TurboVisionDark/TurboVisionDarkColors.axaml index ebf27518..5e85f1da 100644 --- a/src/Consolonia.Themes.TurboVision/Themes/TurboVisionDark/TurboVisionDarkColors.axaml +++ b/src/Consolonia.Themes.TurboVision/Themes/TurboVisionDark/TurboVisionDarkColors.axaml @@ -2,28 +2,28 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:drawing="clr-namespace:Consolonia.Core.Drawing;assembly=Consolonia.Core"> - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Example/Views/DataGridTestWindow.axaml b/src/Example/Views/DataGridTestWindow.axaml index 6fafbea7..bcc538d1 100644 --- a/src/Example/Views/DataGridTestWindow.axaml +++ b/src/Example/Views/DataGridTestWindow.axaml @@ -28,11 +28,8 @@ Margin="4,0,0,0" /> - - - - @@ -48,7 +45,7 @@ diff --git a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs index 3d99584c..d1beb95e 100644 --- a/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs +++ b/src/Tests/Consolonia.GalleryTests/TextBlockTests.cs @@ -15,7 +15,7 @@ protected override Task PerformSingleTest() "Text trimming with word...", "│Left aligned text ", " Center aligned text ", - "│ Right aligned text │", + "Right aligned text│", // multiline "│Lorem ipsum dolor sit amet, consectetur adipiscing elit.│", diff --git a/src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj b/src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj index aa551cab..2f3ae527 100644 --- a/src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj +++ b/src/Tests/Consolonia.TestsCore/Consolonia.TestsCore.csproj @@ -1,9 +1,15 @@ - + + + false + + + + diff --git a/src/Tests/Consolonia.TestsCore/PixelTests.cs b/src/Tests/Consolonia.TestsCore/PixelTests.cs new file mode 100644 index 00000000..5ffc2681 --- /dev/null +++ b/src/Tests/Consolonia.TestsCore/PixelTests.cs @@ -0,0 +1,134 @@ +using Avalonia.Media; +using Consolonia.Core.Drawing.PixelBufferImplementation; +using NUnit.Framework; + +namespace Consolonia.TestsCore +{ + [TestFixture] + public class PixelTests + { + [Test] + public void TestShade() + { + Color foreground = Colors.Gray; + Color newColor = foreground.Shade(); + Assert.That(newColor.R < foreground.R); + Assert.That(newColor.G < foreground.G); + Assert.That(newColor.B < foreground.B); + } + + [Test] + public void TestBrighten() + { + Color foreground = Colors.Gray; + Color newColor = foreground.Brighten(); + Assert.That(newColor.R > foreground.R); + Assert.That(newColor.G > foreground.G); + Assert.That(newColor.B > foreground.B); + } + + [Test] + public void TestRelativeShade() + { + Color foreground = Colors.LightGray; + Color background = Colors.DarkGray; + Color newColor = foreground.Shade(background); + Assert.That(newColor.R < foreground.R); + Assert.That(newColor.G < foreground.G); + Assert.That(newColor.B < foreground.B); + + Color foreground2 = Colors.DarkGray; + Color background2 = Colors.LightGray; + newColor = foreground2.Shade(background2); + Assert.That(newColor.R > foreground2.R); + Assert.That(newColor.G > foreground2.G); + Assert.That(newColor.B > foreground2.B); + } + + [Test] + public void TestRelativeBrighten() + { + Color foreground = Colors.LightGray; + Color background = Colors.DarkGray; + Color newColor = foreground.Brighten(background); + Assert.That(newColor.R > foreground.R); + Assert.That(newColor.G > foreground.G); + Assert.That(newColor.B > foreground.B); + + Color foreground2 = Colors.DarkGray; + Color background2 = Colors.LightGray; + newColor = foreground2.Brighten(background2); + Assert.That(newColor.R < foreground2.R); + Assert.That(newColor.G < foreground2.G); + Assert.That(newColor.B < foreground2.B); + } + + [Test] + public void TestShadeBoundaries() + { + Color foreground = Colors.White; + Color background = Colors.Black; + Color newColor = foreground; + for (int i = 0; i < byte.MaxValue; i++) + { + newColor = newColor.Shade(background); + Assert.That(newColor.R < foreground.R); + Assert.That(newColor.G < foreground.G); + Assert.That(newColor.B < foreground.B); + if (newColor == Colors.Black) + break; + } + + Assert.That(newColor == Colors.Black); + + Color foreground2 = Colors.Black; + Color background2 = Colors.White; + newColor = foreground2; + for (int i = 0; i < byte.MaxValue; i++) + { + newColor = newColor.Shade(background2); + Assert.That(newColor.R > foreground2.R); + Assert.That(newColor.G > foreground2.G); + Assert.That(newColor.B > foreground2.B); + if (newColor == Colors.White) + break; + } + + Assert.That(newColor == Colors.White); + } + + [Test] + public void TestBrightenBoundaries() + { + Color foreground = Colors.Black; + Color background = Colors.Black; + Color newColor = foreground; + for (int i = 0; i < byte.MaxValue; i++) + { + newColor = newColor.Brighten(background); + Assert.That(newColor.R > foreground.R); + Assert.That(newColor.G > foreground.G); + Assert.That(newColor.B > foreground.B); + if (newColor == Colors.White) + break; + } + + Assert.That(newColor == Colors.White); + + Color foreground2 = Colors.White; + Color background2 = Colors.White; + newColor = foreground2; + for (int i = 0; i < byte.MaxValue; i++) + { + newColor = newColor.Brighten(background2); + Assert.That(newColor.R < foreground2.R); + Assert.That(newColor.G < foreground2.G); + Assert.That(newColor.B < foreground2.B); + if (newColor == Colors.Black) + break; + } + + Assert.That(newColor == Colors.Black); + } + } +} \ No newline at end of file diff --git a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs index 71ac5ca0..964e67a6 100644 --- a/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs +++ b/src/Tests/Consolonia.TestsCore/UnitTestConsole.cs @@ -8,6 +8,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Media; using Avalonia.Threading; using Consolonia.Core.Drawing.PixelBufferImplementation; using Consolonia.Core.Infrastructure; @@ -52,17 +53,18 @@ PixelBufferCoordinate IConsole.GetCaretPosition() return _fakeCaretPosition; } - void IConsole.Print(PixelBufferCoordinate bufferPoint, ConsoleColor backgroundColor, - ConsoleColor foregroundColor, - string str) + void IConsole.Print(PixelBufferCoordinate bufferPoint, Color background, Color foreground, FontStyle style, + FontWeight weight, TextDecorationCollection textDecorations, string str) { (ushort x, ushort y) = bufferPoint; for (int i = 0; i < str.Length; i++) 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]), foregroundColor), - new PixelBackground(PixelBackgroundMode.Colored, backgroundColor))); + new Pixel( + new PixelForeground(new SimpleSymbol(str[i]), color: foreground, style: style, weight: weight, + textDecorations: textDecorations), + new PixelBackground(PixelBackgroundMode.Colored, background))); } public void PauseIO(Task task)