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">
-
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello world!
+ Something bold!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryColors.axaml.cs b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryColors.axaml.cs
new file mode 100644
index 00000000..361f2c80
--- /dev/null
+++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryColors.axaml.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Consolonia.Core.Drawing;
+
+namespace Consolonia.Gallery.Gallery.GalleryViews
+{
+ [GalleryOrder(1000)]
+ public partial class GalleryColors : UserControl
+ {
+ public GalleryColors()
+ {
+ InitializeComponent();
+ var rnd = Random.Shared;
+ this.DataContext = new RGBModel() { Color = Color.FromRgb((byte)rnd.Next(255), (byte)rnd.Next(255), (byte)rnd.Next(255)) };
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ private void Random_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Random rnd = new Random();
+ var model = (RGBModel)this.DataContext;
+ model.Color = Color.FromRgb((byte)rnd.Next(255), (byte)rnd.Next(255), (byte)rnd.Next(255));
+ }
+
+ private void Red_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Random rnd = new Random();
+ var model = (RGBModel)this.DataContext;
+ model.Color = Color.FromRgb((byte)rnd.Next(255), model.Color.G, model.Color.B);
+ }
+ private void Green_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Random rnd = new Random();
+ var model = (RGBModel)this.DataContext;
+ model.Color = Color.FromRgb(model.Color.R, (byte)rnd.Next(255), model.Color.B);
+ }
+ private void Blue_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ Random rnd = new Random();
+ var model = (RGBModel)this.DataContext;
+ model.Color = Color.FromRgb(model.Color.R, model.Color.G, (byte)rnd.Next(255));
+ }
+ }
+
+ public class RGBModel : INotifyPropertyChanged
+ {
+ public RGBModel()
+ {
+ }
+
+ private Color _color;
+ public Color Color
+ {
+ get => _color;
+ set
+ {
+ _color = value;
+ Brush = new ConsoleBrush(_color);
+ }
+ }
+
+ private ConsoleBrush _brush;
+ public ConsoleBrush Brush
+ {
+ get => _brush; set
+ {
+ _brush = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Brush)));
+ }
+ }
+
+ public ObservableCollection Swatches { get; } = new ObservableCollection();
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+
+ public class Swatch
+ {
+ public string Name { get; set; }
+
+ public Color Color { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryComboBox.axaml b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryComboBox.axaml
index f727ca5d..ec77bc47 100644
--- a/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryComboBox.axaml
+++ b/src/Consolonia.Gallery/Gallery/GalleryViews/GalleryComboBox.axaml
@@ -47,14 +47,14 @@
-
+
+ Foreground="DarkMagenta" />
-
+