diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml
index 1d3001f1b1..9e9a600161 100644
--- a/samples/RenderTest/MainWindow.xaml
+++ b/samples/RenderTest/MainWindow.xaml
@@ -27,6 +27,7 @@
+
\ No newline at end of file
diff --git a/samples/RenderTest/Pages/DrawingPage.xaml b/samples/RenderTest/Pages/DrawingPage.xaml
new file mode 100644
index 0000000000..a1a75b1962
--- /dev/null
+++ b/samples/RenderTest/Pages/DrawingPage.xaml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/RenderTest/Pages/DrawingPage.xaml.cs b/samples/RenderTest/Pages/DrawingPage.xaml.cs
new file mode 100644
index 0000000000..3bf9bd545d
--- /dev/null
+++ b/samples/RenderTest/Pages/DrawingPage.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace RenderTest.Pages
+{
+ public class DrawingPage : UserControl
+ {
+ public DrawingPage()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/RenderTest/RenderTest.csproj b/samples/RenderTest/RenderTest.csproj
index ea5c0bcc58..b7e64f4dae 100644
--- a/samples/RenderTest/RenderTest.csproj
+++ b/samples/RenderTest/RenderTest.csproj
@@ -51,6 +51,9 @@
App.xaml
+
+ DrawingPage.xaml
+
ClippingPage.xaml
@@ -178,6 +181,11 @@
Designer
+
+
+ Designer
+
+
diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs
new file mode 100644
index 0000000000..50a884d3bd
--- /dev/null
+++ b/src/Avalonia.Controls/DrawingPresenter.cs
@@ -0,0 +1,58 @@
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls
+{
+ public class DrawingPresenter : Control
+ {
+ public static readonly StyledProperty DrawingProperty =
+ AvaloniaProperty.Register(nameof(Drawing));
+
+ [Content]
+ public Drawing Drawing
+ {
+ get => GetValue(DrawingProperty);
+ set => SetValue(DrawingProperty, value);
+ }
+
+ public static readonly StyledProperty StretchProperty =
+ AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
+
+ public Stretch Stretch
+ {
+ get => GetValue(StretchProperty);
+ set => SetValue(StretchProperty, value);
+ }
+
+ static DrawingPresenter()
+ {
+ AffectsMeasure(DrawingProperty);
+ AffectsRender(DrawingProperty);
+ }
+
+ private Matrix _transform = Matrix.Identity;
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ if (Drawing == null) return new Size();
+
+ var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch);
+
+ _transform = transform;
+
+ return size;
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ if (Drawing != null)
+ {
+ using (context.PushPreTransform(_transform))
+ {
+ Drawing.Draw(context);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs
index 427749263a..c03f4dc563 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -155,11 +155,21 @@ namespace Avalonia.Controls.Shapes
{
// This should probably use GetRenderBounds(strokeThickness) but then the calculations
// will multiply the stroke thickness as well, which isn't correct.
- Rect shapeBounds = DefiningGeometry.Bounds;
+ var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
+
+ if (_transform != transform)
+ {
+ _transform = transform;
+ _renderedGeometry = null;
+ }
+
+ return size;
+ }
+
+ internal static (Size, Matrix) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch)
+ {
Size shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom);
Matrix translate = Matrix.Identity;
- double width = Width;
- double height = Height;
double desiredX = availableSize.Width;
double desiredY = availableSize.Height;
double sx = 0.0;
@@ -226,15 +236,9 @@ namespace Avalonia.Controls.Shapes
break;
}
- var t = translate * Matrix.CreateScale(sx, sy);
-
- if (_transform != t)
- {
- _transform = t;
- _renderedGeometry = null;
- }
-
- return new Size(shapeSize.Width * sx, shapeSize.Height * sy);
+ var transform = translate * Matrix.CreateScale(sx, sy);
+ var size = new Size(shapeSize.Width * sx, shapeSize.Height * sy);
+ return (size, transform);
}
private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)
diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs
new file mode 100644
index 0000000000..a60c591edc
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/Drawing.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.Media
+{
+ public abstract class Drawing : AvaloniaObject
+ {
+ public abstract void Draw(DrawingContext context);
+
+ public abstract Rect GetBounds();
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/DrawingGroup.cs b/src/Avalonia.Visuals/Media/DrawingGroup.cs
new file mode 100644
index 0000000000..623a4bf640
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/DrawingGroup.cs
@@ -0,0 +1,58 @@
+using Avalonia.Collections;
+using Avalonia.Metadata;
+
+namespace Avalonia.Media
+{
+ public class DrawingGroup : Drawing
+ {
+ [Content]
+ public AvaloniaList Children { get; } = new AvaloniaList();
+
+ public static readonly StyledProperty OpacityProperty =
+ AvaloniaProperty.Register(nameof(Opacity), 1);
+
+ public double Opacity
+ {
+ get => GetValue(OpacityProperty);
+ set => SetValue(OpacityProperty, value);
+ }
+
+ public static readonly StyledProperty TransformProperty =
+ AvaloniaProperty.Register(nameof(Transform));
+
+ public Transform Transform
+ {
+ get => GetValue(TransformProperty);
+ set => SetValue(TransformProperty, value);
+ }
+
+ public override void Draw(DrawingContext context)
+ {
+ using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity))
+ using (context.PushOpacity(Opacity))
+ {
+ foreach (var drawing in Children)
+ {
+ drawing.Draw(context);
+ }
+ }
+ }
+
+ public override Rect GetBounds()
+ {
+ var rect = new Rect();
+
+ foreach (var drawing in Children)
+ {
+ rect = rect.Union(drawing.GetBounds());
+ }
+
+ if (Transform != null)
+ {
+ rect = rect.TransformToAABB(Transform.Value);
+ }
+
+ return rect;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs
new file mode 100644
index 0000000000..e67e853a84
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/GeometryDrawing.cs
@@ -0,0 +1,43 @@
+namespace Avalonia.Media
+{
+ public class GeometryDrawing : Drawing
+ {
+ public static readonly StyledProperty GeometryProperty =
+ AvaloniaProperty.Register(nameof(Geometry));
+
+ public Geometry Geometry
+ {
+ get => GetValue(GeometryProperty);
+ set => SetValue(GeometryProperty, value);
+ }
+
+ public static readonly StyledProperty BrushProperty =
+ AvaloniaProperty.Register(nameof(Brush), Brushes.Transparent);
+
+ public IBrush Brush
+ {
+ get => GetValue(BrushProperty);
+ set => SetValue(BrushProperty, value);
+ }
+
+ public static readonly StyledProperty PenProperty =
+ AvaloniaProperty.Register(nameof(Pen));
+
+ public Pen Pen
+ {
+ get => GetValue(PenProperty);
+ set => SetValue(PenProperty, value);
+ }
+
+ public override void Draw(DrawingContext context)
+ {
+ context.DrawGeometry(Brush, Pen, Geometry);
+ }
+
+ public override Rect GetBounds()
+ {
+ // adding the Pen's stroke thickness here could yield wrong results due to transforms
+ return Geometry?.GetRenderBounds(0) ?? new Rect();
+ }
+ }
+}
\ No newline at end of file