Skip to content

Commit

Permalink
Add new brushes and Consolonia.Editor project (#238)
Browse files Browse the repository at this point in the history
* add consolonia.editor project
Add BrightBrush/ShadeBrush/InvertBrush
update drawingcontext to support brushes
Update drawingcontext to correctly implement Fills.
  • Loading branch information
tomlm authored Jan 1, 2025
1 parent 87500ad commit b5b4a3f
Show file tree
Hide file tree
Showing 84 changed files with 3,639 additions and 78 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/editorconfig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ jobs:

- name: resharper-cleanup
run: |
jb cleanupcode Consolonia.sln --exclude="**Consolonia.GuiCS/**.*;**Consolonia.Templates/**.*"
jb cleanupcode Consolonia.sln --exclude="**Consolonia.GuiCS/**.*;**Consolonia.Templates/**.*;**Consolonia.Editor/**.*"
working-directory: ./src

- name: Inspect code
uses: muno92/[email protected]
with:
solutionPath: ./src/Consolonia.sln
exclude: "**Consolonia.GuiCS/**.*;**Consolonia.Gallery/**.*;**Example/**.*;**Consolonia.Templates/**.*"
exclude: "**Consolonia.GuiCS/**.*;**Consolonia.Gallery/**.*;**Example/**.*;**Consolonia.Templates/**.*;**Consolonia.Editor/**.*"
# version: '2024.2.6'
minimumSeverity: 'warning'

Expand Down
17 changes: 17 additions & 0 deletions src/Consolonia.Core/Controls/BrightenBrush.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Animation;
using Avalonia.Media;

// ReSharper disable CheckNamespace
namespace Consolonia.Controls
{
/// <summary>
/// This brush brightens the area it paints.
/// </summary>
public class BrightenBrush : Animatable, IImmutableBrush
{
public double Opacity => 1;
public ITransform Transform => null;
public RelativePoint TransformOrigin => RelativePoint.TopLeft;
}
}
17 changes: 17 additions & 0 deletions src/Consolonia.Core/Controls/InvertBrush.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Animation;
using Avalonia.Media;

// ReSharper disable CheckNamespace
namespace Consolonia.Controls
{
/// <summary>
/// This brush inverts foreground/background colors for each pixel of the area it paints.
/// </summary>
public class InvertBrush : Animatable, IImmutableBrush
{
public double Opacity => 1;
public ITransform Transform => null;
public RelativePoint TransformOrigin => RelativePoint.TopLeft;
}
}
4 changes: 0 additions & 4 deletions src/Consolonia.Core/Controls/LineBrush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ public class LineBrush : Animatable, IImmutableBrush
public static readonly StyledProperty<LineStyle> LineStyleProperty =
AvaloniaProperty.Register<LineBrush, LineStyle>(CommonInternalHelper.GetStyledPropertyName());

static LineBrush()
{
}

public IBrush Brush
{
get => GetValue(BrushProperty);
Expand Down
17 changes: 17 additions & 0 deletions src/Consolonia.Core/Controls/ShadeBrush.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Animation;
using Avalonia.Media;

// ReSharper disable CheckNamespace
namespace Consolonia.Controls
{
/// <summary>
/// This brush shades the area it paints.
/// </summary>
public class ShadeBrush : Animatable, IImmutableBrush
{
public double Opacity => 1;
public ITransform Transform => null;
public RelativePoint TransformOrigin => RelativePoint.TopLeft;
}
}
7 changes: 6 additions & 1 deletion src/Consolonia.Core/Drawing/BrushExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using Avalonia.Media;
using Consolonia.Controls;
using Consolonia.Core.Infrastructure;

namespace Consolonia.Core.Drawing
Expand Down Expand Up @@ -84,9 +85,13 @@ public static Color FromPosition(this IBrush brush, int x, int y, int width, int
case ISolidColorBrush solidColorBrush:
return solidColorBrush.Color;

case ShadeBrush:
case BrightenBrush:
case InvertBrush:
return Colors.Transparent;
default:
ConsoloniaPlatform.RaiseNotSupported(751, brush); //todo: allow RaiseNotSupported to return a result
return new Color();
return Colors.Transparent;
}
}

Expand Down
159 changes: 91 additions & 68 deletions src/Consolonia.Core/Drawing/DrawingContextImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,80 +151,90 @@ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
break;
case StreamGeometryImpl streamGeometry:
{
pen = pen ?? new Pen(brush);
// if we have fills to do.
if (streamGeometry.Fills.Count > 0)
foreach (Rectangle fill in streamGeometry.Fills)
DrawRectangle(brush, pen, new RoundedRect(fill.Rect));

var extractColorCheckPlatformSupported =
ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle);
if (extractColorCheckPlatformSupported == null)
return;
// if we have strokes to draw
if (streamGeometry.Strokes.Count > 0)
{
if (pen == null || pen.Thickness == 0)
return;

var color = (Color)extractColorCheckPlatformSupported;
var extractColorCheckPlatformSupported =
ExtractColorOrNullWithPlatformCheck(pen, out var lineStyle);
if (extractColorCheckPlatformSupported == null)
return;

lineStyle ??= LineStyle.SingleLine;
var color = (Color)extractColorCheckPlatformSupported;

var strokePositions = InferStrokePositions(streamGeometry);
lineStyle ??= LineStyle.SingleLine;

bool hasTop = strokePositions.Contains(RectangleLinePosition.Top);
bool hasRight = strokePositions.Contains(RectangleLinePosition.Right);
bool hasBottom = strokePositions.Contains(RectangleLinePosition.Bottom);
bool hasLeft = strokePositions.Contains(RectangleLinePosition.Left);
var strokePositions = InferStrokePositions(streamGeometry);

if (lineStyle == LineStyle.Edge || lineStyle == LineStyle.EdgeWide)
{
for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++)
{
Line stroke = TransformLineInternal(streamGeometry.Strokes[iStroke]);
bool hasTop = strokePositions.Contains(RectangleLinePosition.Top);
bool hasRight = strokePositions.Contains(RectangleLinePosition.Right);
bool hasBottom = strokePositions.Contains(RectangleLinePosition.Bottom);
bool hasLeft = strokePositions.Contains(RectangleLinePosition.Left);

if (stroke.Bounds.Width > 0 || stroke.Bounds.Height > 0)
if (lineStyle == LineStyle.Edge || lineStyle == LineStyle.EdgeWide)
{
for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++)
{
if (stroke.Vertical)
DrawEdgeLine(stroke, strokePositions[iStroke], lineStyle.Value, color, hasTop,
hasBottom);
else
DrawEdgeLine(stroke, strokePositions[iStroke], lineStyle.Value, color, hasLeft,
hasRight);
Line stroke = TransformLineInternal(streamGeometry.Strokes[iStroke]);

if (stroke.Bounds.Width > 0 || stroke.Bounds.Height > 0)
{
if (stroke.Vertical)
DrawEdgeLine(stroke, strokePositions[iStroke], lineStyle.Value, color, hasTop,
hasBottom);
else
DrawEdgeLine(stroke, strokePositions[iStroke], lineStyle.Value, color, hasLeft,
hasRight);
}
}
}
}
else
{
Line strokeTop = null;
Line strokeLeft = null;
Line strokeRight = null;
Line strokeBottom = null;
for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++)
else
{
Line stroke = streamGeometry.Strokes[iStroke];
RectangleLinePosition strokePosition = strokePositions[iStroke];
if (strokePosition == RectangleLinePosition.Left)
strokeLeft = stroke;
else if (strokePosition == RectangleLinePosition.Right)
strokeRight = stroke;
else if (strokePosition == RectangleLinePosition.Top)
strokeTop = stroke;
else if (strokePosition == RectangleLinePosition.Bottom)
strokeBottom = stroke;
}
Line strokeTop = null;
Line strokeLeft = null;
Line strokeRight = null;
Line strokeBottom = null;
for (int iStroke = 0; iStroke < streamGeometry.Strokes.Count; iStroke++)
{
Line stroke = streamGeometry.Strokes[iStroke];
RectangleLinePosition strokePosition = strokePositions[iStroke];
if (strokePosition == RectangleLinePosition.Left)
strokeLeft = stroke;
else if (strokePosition == RectangleLinePosition.Right)
strokeRight = stroke;
else if (strokePosition == RectangleLinePosition.Top)
strokeTop = stroke;
else if (strokePosition == RectangleLinePosition.Bottom)
strokeBottom = stroke;
}

if (strokeLeft != null)
//if (strokeBottom != null)
// strokeLeft = new Line(strokeLeft.PStart, strokeBottom.PStart, strokeLeft.SourceGeometry, strokeLeft.Transform);
DrawBoxLineInternal(pen, strokeLeft, RectangleLinePosition.Left);

if (strokeTop != null)
//if (strokeRight != null)
// strokeTop = new Line(strokeTop.PStart, strokeRight.PStart, strokeTop.SourceGeometry, strokeTop.Transform);
DrawBoxLineInternal(pen, strokeTop, RectangleLinePosition.Top);

if (strokeRight != null)
//if (strokeBottom != null)
// strokeRight = new Line(strokeRight.PStart, strokeBottom.PEnd, strokeRight.SourceGeometry, strokeRight.Transform);
DrawBoxLineInternal(pen, strokeRight, RectangleLinePosition.Right);

if (strokeBottom != null)
//if (strokeLeft != null)
// strokeBottom = new Line(strokeLeft.PEnd, strokeBottom.PEnd, strokeBottom.SourceGeometry, strokeBottom.Transform);
DrawBoxLineInternal(pen, strokeBottom, RectangleLinePosition.Bottom);
if (strokeLeft != null)
//if (strokeBottom != null)
// strokeLeft = new Line(strokeLeft.PStart, strokeBottom.PStart, strokeLeft.SourceGeometry, strokeLeft.Transform);
DrawBoxLineInternal(pen, strokeLeft, RectangleLinePosition.Left);

if (strokeTop != null)
//if (strokeRight != null)
// strokeTop = new Line(strokeTop.PStart, strokeRight.PStart, strokeTop.SourceGeometry, strokeTop.Transform);
DrawBoxLineInternal(pen, strokeTop, RectangleLinePosition.Top);

if (strokeRight != null)
//if (strokeBottom != null)
// strokeRight = new Line(strokeRight.PStart, strokeBottom.PEnd, strokeRight.SourceGeometry, strokeRight.Transform);
DrawBoxLineInternal(pen, strokeRight, RectangleLinePosition.Right);

if (strokeBottom != null)
//if (strokeLeft != null)
// strokeBottom = new Line(strokeLeft.PEnd, strokeBottom.PEnd, strokeBottom.SourceGeometry, strokeBottom.Transform);
DrawBoxLineInternal(pen, strokeBottom, RectangleLinePosition.Bottom);
}
}
}
break;
Expand Down Expand Up @@ -527,12 +537,25 @@ private void FillRectangleWithBrush(IBrush brush, IPen pen, Rect r)
{
int px = (int)(r2.TopLeft.X + x);
int py = (int)(r2.TopLeft.Y + y);

Color backgroundColor = brush.FromPosition(x, y, (int)width, (int)height);

CurrentClip.ExecuteWithClipping(new Point(px, py), () =>
{
_pixelBuffer.Set(new PixelBufferCoordinate((ushort)px, (ushort)py),
pixel => pixel.Blend(new Pixel(new PixelBackground(backgroundColor))));
pixel =>
{
switch (brush)
{
case ShadeBrush:
return pixel.Shade();
case BrightenBrush:
return pixel.Brighten();
case InvertBrush:
return pixel.Invert();
default:
return pixel.Blend(new Pixel(new PixelBackground(backgroundColor)));
}
});
});
}
}
Expand Down Expand Up @@ -652,11 +675,11 @@ private Line TransformLineInternal(Line line)
lineStyle = null;
if (pen is not
{
Brush: LineBrush or ISolidColorBrush,
Brush: LineBrush or ISolidColorBrush
// Thickness: 1,
DashStyle: null or { Dashes.Count: 0 },
LineCap: PenLineCap.Flat,
LineJoin: PenLineJoin.Miter
// DashStyle: null or { Dashes.Count: 0 },
//LineCap: PenLineCap.Flat,
//LineJoin: PenLineJoin.Miter
})
{
ConsoloniaPlatform.RaiseNotSupported(6);
Expand Down
3 changes: 2 additions & 1 deletion src/Consolonia.Core/Drawing/Line.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using Avalonia;
using Avalonia.Media;
using Avalonia.Platform;
Expand All @@ -24,7 +25,7 @@ public Line(Point pStart, Point pEnd, IGeometryImpl sourceGeometry = default, Ma
PStart = pStart;
pEnd = new Point(pEnd.X, pEnd.Y);
if (!(pStart.X.IsNearlyEqual(pEnd.X) || pStart.Y.IsNearlyEqual(pEnd.Y)))
throw new ArgumentException("Consolonia can only draw horizontal or vertical lines");
Debug.WriteLine("Consolonia can only draw horizontal or vertical lines");
Vertical = pStart.X.IsNearlyEqual(pEnd.X);
Length = Vertical ? (int)Math.Abs(pStart.Y - pEnd.Y) : (int)Math.Abs(pStart.X - pEnd.X);
SourceGeometry = sourceGeometry!;
Expand Down
21 changes: 21 additions & 0 deletions src/Consolonia.Core/Drawing/PixelBufferImplementation/Pixel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,27 @@ public bool Equals(Pixel other)
IsCaret.Equals(other.IsCaret);
}

public Pixel Shade()
{
return new Pixel(Foreground.Shade(), Background.Shade(), IsCaret);
}

public Pixel Brighten()
{
return new Pixel(Foreground.Brighten(), Background.Brighten(), IsCaret);
}

public Pixel Invert()
{
return new Pixel(new PixelForeground(Foreground.Symbol,
Background.Color, // background color becomes the new foreground color
Foreground.Weight,
Foreground.Style,
Foreground.TextDecoration),
new PixelBackground(Foreground.Color),
IsCaret);
}

/// <summary>
/// Blend the pixelAbove with this pixel.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ public PixelBackground() : this(Colors.Transparent)
}


public PixelBackground Shade()
{
return new PixelBackground(Color.Shade());
}

public PixelBackground Brighten()
{
return new PixelBackground(Color.Brighten());
}

[JsonConverter(typeof(ColorConverter))]
public Color Color { get; init; } = color;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,12 @@ public bool Equals(PixelForeground other)

public PixelForeground Shade()
{
Color newColor = Color.Shade();
return new PixelForeground(Symbol, newColor, Weight, Style, TextDecoration);
return new PixelForeground(Symbol, Color.Shade(), Weight, Style, TextDecoration);
}

public PixelForeground Brighten()
{
return new PixelForeground(Symbol, Color.Brighten(), Weight, Style, TextDecoration);
}

public PixelForeground Blend(PixelForeground pixelAboveForeground)
Expand Down
4 changes: 4 additions & 0 deletions src/Consolonia.Core/Text/Esc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ internal static class Esc
public const string BlinkingBarCursor = "\u001b[5 q";
public const string SteadyBarCursor = "\u001b[6 q";

// bracketed
public const string EnableBracketedPasteMode = "\u001b[?2004h";
public const string DisableBracktedPasteMode = "\u001b[?2004l";

// move cursor
public static string MoveCursorUp(int n)
{
Expand Down
Loading

0 comments on commit b5b4a3f

Please sign in to comment.