diff --git a/samples/RenderTest/MainWindow.xaml b/samples/RenderTest/MainWindow.xaml
index 1d3001f1b16..9e9a600161e 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 00000000000..81181e01fce
--- /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 00000000000..3bf9bd545d0
--- /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 ea5c0bcc587..b7e64f4daeb 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 00000000000..af3665fabc3
--- /dev/null
+++ b/src/Avalonia.Controls/DrawingPresenter.cs
@@ -0,0 +1,59 @@
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls
+{
+ public class DrawingPresenter : Control
+ {
+ static DrawingPresenter()
+ {
+ AffectsMeasure(DrawingProperty);
+ AffectsRender(DrawingProperty);
+ }
+
+ public static readonly StyledProperty DrawingProperty =
+ AvaloniaProperty.Register(nameof(Drawing));
+
+ public static readonly StyledProperty StretchProperty =
+ AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
+
+ [Content]
+ public Drawing Drawing
+ {
+ get => GetValue(DrawingProperty);
+ set => SetValue(DrawingProperty, value);
+ }
+
+ public Stretch Stretch
+ {
+ get => GetValue(StretchProperty);
+ set => SetValue(StretchProperty, value);
+ }
+
+ 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))
+ using (context.PushClip(Bounds))
+ {
+ 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 427749263a4..c03f4dc5630 100644
--- a/src/Avalonia.Controls/Shapes/Shape.cs
+++ b/src/Avalonia.Controls/Shapes/Shape.cs
@@ -155,11 +155,21 @@ protected override Size MeasureOverride(Size availableSize)
{
// 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 @@ protected override Size MeasureOverride(Size availableSize)
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/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs
index 8812000bdc4..10549b967de 100644
--- a/src/Avalonia.Visuals/Matrix.cs
+++ b/src/Avalonia.Visuals/Matrix.cs
@@ -3,6 +3,7 @@
using System;
using System.Globalization;
+using System.Linq;
namespace Avalonia
{
@@ -295,5 +296,33 @@ public Matrix Invert()
((_m21 * _m32) - (_m22 * _m31)) / d,
((_m12 * _m31) - (_m11 * _m32)) / d);
}
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The current culture.
+ /// The .
+ public static Matrix Parse(string s, CultureInfo culture)
+ {
+ var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim())
+ .ToArray();
+
+ if (parts.Length == 6)
+ {
+ return new Matrix(
+ double.Parse(parts[0], culture),
+ double.Parse(parts[1], culture),
+ double.Parse(parts[2], culture),
+ double.Parse(parts[3], culture),
+ double.Parse(parts[4], culture),
+ double.Parse(parts[5], culture));
+ }
+ else
+ {
+ throw new FormatException("Invalid Matrix.");
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Media/Drawing.cs b/src/Avalonia.Visuals/Media/Drawing.cs
new file mode 100644
index 00000000000..a60c591edcd
--- /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 00000000000..744ff2af031
--- /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
+ {
+ public static readonly StyledProperty OpacityProperty =
+ AvaloniaProperty.Register(nameof(Opacity), 1);
+
+ public static readonly StyledProperty TransformProperty =
+ AvaloniaProperty.Register(nameof(Transform));
+
+ public double Opacity
+ {
+ get => GetValue(OpacityProperty);
+ set => SetValue(OpacityProperty, value);
+ }
+
+ public Transform Transform
+ {
+ get => GetValue(TransformProperty);
+ set => SetValue(TransformProperty, value);
+ }
+
+ [Content]
+ public AvaloniaList Children { get; } = new AvaloniaList();
+
+ 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/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs
index 414fd9ab596..591b55cf58f 100644
--- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs
+++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs
@@ -11,16 +11,51 @@ namespace Avalonia.Media
///
public class EllipseGeometry : Geometry
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty RectProperty =
+ AvaloniaProperty.Register(nameof(Rect));
+
+ public Rect Rect
+ {
+ get => GetValue(RectProperty);
+ set => SetValue(RectProperty, value);
+ }
+
+ static EllipseGeometry()
+ {
+ RectProperty.Changed.AddClassHandler(x => x.RectChanged);
+ }
+
///
/// Initializes a new instance of the class.
///
- /// The rectangle that the ellipse should fill.
- public EllipseGeometry(Rect rect)
+ public EllipseGeometry()
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService();
- IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+ PlatformImpl = factory.CreateStreamGeometry();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The rectangle that the ellipse should fill.
+ public EllipseGeometry(Rect rect) : this()
+ {
+ Rect = rect;
+ }
+
+ ///
+ public override Geometry Clone()
+ {
+ return new EllipseGeometry(Rect);
+ }
- using (IStreamGeometryContextImpl ctx = impl.Open())
+ private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var rect = (Rect)e.NewValue;
+ using (var ctx = ((IStreamGeometryImpl)PlatformImpl).Open())
{
double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
var center = rect.Center;
@@ -45,14 +80,6 @@ public EllipseGeometry(Rect rect)
ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
ctx.EndFigure(true);
}
-
- PlatformImpl = impl;
- }
-
- ///
- public override Geometry Clone()
- {
- return new EllipseGeometry(Bounds);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/GeometryDrawing.cs b/src/Avalonia.Visuals/Media/GeometryDrawing.cs
new file mode 100644
index 00000000000..e67e853a846
--- /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
diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs
index 2783d7fb262..323bfa5a7ea 100644
--- a/src/Avalonia.Visuals/Media/LineGeometry.cs
+++ b/src/Avalonia.Visuals/Media/LineGeometry.cs
@@ -10,35 +10,89 @@ namespace Avalonia.Media
///
public class LineGeometry : Geometry
{
- private Point _startPoint;
- private Point _endPoint;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StartPointProperty =
+ AvaloniaProperty.Register(nameof(StartPoint));
+
+ public Point StartPoint
+ {
+ get => GetValue(StartPointProperty);
+ set => SetValue(StartPointProperty, value);
+ }
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty EndPointProperty =
+ AvaloniaProperty.Register(nameof(EndPoint));
+ private bool _isDirty;
+
+ public Point EndPoint
+ {
+ get => GetValue(EndPointProperty);
+ set => SetValue(EndPointProperty, value);
+ }
+
+ static LineGeometry()
+ {
+ StartPointProperty.Changed.AddClassHandler(x => x.PointsChanged);
+ EndPointProperty.Changed.AddClassHandler(x => x.PointsChanged);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LineGeometry()
+ {
+ IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService();
+ PlatformImpl = factory.CreateStreamGeometry();
+ }
///
/// Initializes a new instance of the class.
///
/// The start point.
/// The end point.
- public LineGeometry(Point startPoint, Point endPoint)
+ public LineGeometry(Point startPoint, Point endPoint) : this()
{
- _startPoint = startPoint;
- _endPoint = endPoint;
- IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService();
- IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+ StartPoint = startPoint;
+ EndPoint = endPoint;
+ }
- using (IStreamGeometryContextImpl context = impl.Open())
+ public override IGeometryImpl PlatformImpl
+ {
+ get
{
- context.BeginFigure(_startPoint, false);
- context.LineTo(_endPoint);
- context.EndFigure(false);
+ PrepareIfNeeded();
+ return base.PlatformImpl;
}
+ protected set => base.PlatformImpl = value;
+ }
- PlatformImpl = impl;
+ public void PrepareIfNeeded()
+ {
+ if (_isDirty)
+ {
+ _isDirty = false;
+
+ using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
+ {
+ context.BeginFigure(StartPoint, false);
+ context.LineTo(EndPoint);
+ context.EndFigure(false);
+ }
+ }
}
///
public override Geometry Clone()
{
- return new LineGeometry(_startPoint, _endPoint);
+ PrepareIfNeeded();
+ return new LineGeometry(StartPoint, EndPoint);
}
+
+ private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true;
}
}
diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
index 9c5ffe7151f..fad55ed531d 100644
--- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs
+++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
@@ -141,6 +141,7 @@ public void Parse(string s)
bool isLargeArc = ReadBool(reader);
ReadSeparator(reader);
SweepDirection sweepDirection = ReadBool(reader) ? SweepDirection.Clockwise : SweepDirection.CounterClockwise;
+ ReadSeparator(reader);
point = ReadPoint(reader, point, relative);
_context.ArcTo(point, size, rotationAngle, isLargeArc, sweepDirection);
diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs
index 709ad7a9f58..7c47e7d04d4 100644
--- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs
+++ b/src/Avalonia.Visuals/Media/PolylineGeometry.cs
@@ -3,10 +3,9 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
using Avalonia.Platform;
+using Avalonia.Metadata;
+using Avalonia.Collections;
namespace Avalonia.Media
{
@@ -15,36 +14,118 @@ namespace Avalonia.Media
///
public class PolylineGeometry : Geometry
{
- private IList _points;
- private bool _isFilled;
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty PointsProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Points), g => g.Points, (g, f) => g.Points = f);
- public PolylineGeometry(IList points, bool isFilled)
+ ///
+ /// Defines the property.
+ ///
+ public static readonly AvaloniaProperty IsFilledProperty =
+ AvaloniaProperty.Register(nameof(IsFilled));
+
+ private Points _points;
+ private bool _isDirty;
+ private IDisposable _pointsObserver;
+
+ static PolylineGeometry()
+ {
+ PointsProperty.Changed.AddClassHandler((s, e) =>
+ s.OnPointsChanged(e.OldValue as Points, e.NewValue as Points));
+ IsFilledProperty.Changed.AddClassHandler((s, _) => s.NotifyChanged());
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PolylineGeometry()
{
- _points = points;
- _isFilled = isFilled;
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService();
- IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+ PlatformImpl = factory.CreateStreamGeometry();
+
+ Points = new Points();
+ }
- using (IStreamGeometryContextImpl context = impl.Open())
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PolylineGeometry(IEnumerable points, bool isFilled) : this()
+ {
+ Points.AddRange(points);
+ IsFilled = isFilled;
+ }
+
+ public void PrepareIfNeeded()
+ {
+ if (_isDirty)
{
- if (points.Count > 0)
+ _isDirty = false;
+
+ using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
{
- context.BeginFigure(points[0], isFilled);
- for (int i = 1; i < points.Count; i++)
+ var points = Points;
+ var isFilled = IsFilled;
+ if (points.Count > 0)
{
- context.LineTo(points[i]);
+ context.BeginFigure(points[0], isFilled);
+ for (int i = 1; i < points.Count; i++)
+ {
+ context.LineTo(points[i]);
+ }
+ context.EndFigure(isFilled);
}
- context.EndFigure(isFilled);
}
}
+ }
- PlatformImpl = impl;
+ ///
+ /// Gets or sets the figures.
+ ///
+ ///
+ /// The points.
+ ///
+ [Content]
+ public Points Points
+ {
+ get => _points;
+ set => SetAndRaise(PointsProperty, ref _points, value);
+ }
+
+ public bool IsFilled
+ {
+ get => GetValue(IsFilledProperty);
+ set => SetValue(IsFilledProperty, value);
+ }
+
+ public override IGeometryImpl PlatformImpl
+ {
+ get
+ {
+ PrepareIfNeeded();
+ return base.PlatformImpl;
+ }
+ protected set => base.PlatformImpl = value;
}
///
public override Geometry Clone()
{
- return new PolylineGeometry(new List(_points), _isFilled);
+ PrepareIfNeeded();
+ return new PolylineGeometry(Points, IsFilled);
+ }
+
+ private void OnPointsChanged(Points oldValue, Points newValue)
+ {
+ _pointsObserver?.Dispose();
+
+ _pointsObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
+ }
+
+ internal void NotifyChanged()
+ {
+ _isDirty = true;
}
}
}
diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs
index ef7deaa6f6d..1aa449d9e1a 100644
--- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs
+++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs
@@ -10,16 +10,51 @@ namespace Avalonia.Media
///
public class RectangleGeometry : Geometry
{
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty RectProperty =
+ AvaloniaProperty.Register(nameof(Rect));
+
+ public Rect Rect
+ {
+ get => GetValue(RectProperty);
+ set => SetValue(RectProperty, value);
+ }
+
+ static RectangleGeometry()
+ {
+ RectProperty.Changed.AddClassHandler(x => x.RectChanged);
+ }
+
///
/// Initializes a new instance of the class.
///
- /// The rectangle bounds.
- public RectangleGeometry(Rect rect)
+ public RectangleGeometry()
{
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService();
- IStreamGeometryImpl impl = factory.CreateStreamGeometry();
+ PlatformImpl = factory.CreateStreamGeometry();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The rectangle bounds.
+ public RectangleGeometry(Rect rect) : this()
+ {
+ Rect = rect;
+ }
+
+ ///
+ public override Geometry Clone()
+ {
+ return new RectangleGeometry(Rect);
+ }
- using (IStreamGeometryContextImpl context = impl.Open())
+ private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ var rect = (Rect)e.NewValue;
+ using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
{
context.BeginFigure(rect.TopLeft, true);
context.LineTo(rect.TopRight);
@@ -27,14 +62,6 @@ public RectangleGeometry(Rect rect)
context.LineTo(rect.BottomLeft);
context.EndFigure(true);
}
-
- PlatformImpl = impl;
- }
-
- ///
- public override Geometry Clone()
- {
- return new RectangleGeometry(Bounds);
}
}
}
diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs
index 7c7a3336fc4..5fbd0829671 100644
--- a/src/Avalonia.Visuals/Point.cs
+++ b/src/Avalonia.Visuals/Point.cs
@@ -183,7 +183,7 @@ public static Point Parse(string s, CultureInfo culture)
}
else
{
- throw new FormatException("Invalid Thickness.");
+ throw new FormatException("Invalid Point.");
}
}
diff --git a/src/Avalonia.Visuals/Points.cs b/src/Avalonia.Visuals/Points.cs
new file mode 100644
index 00000000000..867d3d4d246
--- /dev/null
+++ b/src/Avalonia.Visuals/Points.cs
@@ -0,0 +1,9 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia.Collections;
+
+namespace Avalonia
+{
+ public sealed class Points : AvaloniaList { }
+}
diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs
index 0132c5e8a30..d562429fc7e 100644
--- a/src/Avalonia.Visuals/Rect.cs
+++ b/src/Avalonia.Visuals/Rect.cs
@@ -481,5 +481,31 @@ public override string ToString()
_width,
_height);
}
+
+ ///
+ /// Parses a string.
+ ///
+ /// The string.
+ /// The current culture.
+ /// The parsed .
+ public static Rect Parse(string s, CultureInfo culture)
+ {
+ var parts = s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => x.Trim())
+ .ToList();
+
+ if (parts.Count == 4)
+ {
+ return new Rect(
+ double.Parse(parts[0], culture),
+ double.Parse(parts[1], culture),
+ double.Parse(parts[2], culture),
+ double.Parse(parts[3], culture));
+ }
+ else
+ {
+ throw new FormatException("Invalid Rect.");
+ }
+ }
}
}
diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs
index 3ce3797c49c..a11f080e945 100644
--- a/src/Avalonia.Visuals/RelativeRect.cs
+++ b/src/Avalonia.Visuals/RelativeRect.cs
@@ -203,7 +203,7 @@ public static RelativeRect Parse(string s, CultureInfo culture)
}
else
{
- throw new FormatException("Invalid Rect.");
+ throw new FormatException("Invalid RelativeRect.");
}
}
}
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index d3d6776efa3..c30fb3bdc3f 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -11,6 +11,7 @@
using System.IO;
using Avalonia.Media.Immutable;
using System.Threading;
+using System.Linq;
namespace Avalonia.Rendering
{
@@ -58,7 +59,6 @@ public DeferredRenderer(
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _scene = new Scene(root);
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_renderLoop = renderLoop;
@@ -86,7 +86,6 @@ public DeferredRenderer(
_root = root;
_renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _scene = new Scene(root);
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
}
@@ -122,7 +121,7 @@ public IEnumerable HitTest(Point p, Func filter)
UpdateScene();
}
- return _scene.HitTest(p, filter);
+ return _scene?.HitTest(p, filter) ?? Enumerable.Empty();
}
///
@@ -186,7 +185,7 @@ private void Render(Scene scene)
_dirtyRectsDisplay.Tick();
}
- if (scene.Size != Size.Empty)
+ if (scene != null && scene.Size != Size.Empty)
{
if (scene.Generation != _lastSceneId)
{
@@ -366,25 +365,32 @@ private void UpdateScene()
try
{
- var scene = _scene.Clone();
-
- if (_dirty == null)
- {
- _dirty = new DirtyVisuals();
- _sceneBuilder.UpdateAll(scene);
- }
- else if (_dirty.Count > 0)
+ if (_root.IsVisible)
{
- foreach (var visual in _dirty)
+ var scene = _scene?.Clone() ?? new Scene(_root);
+
+ if (_dirty == null)
{
- _sceneBuilder.Update(scene, visual);
+ _dirty = new DirtyVisuals();
+ _sceneBuilder.UpdateAll(scene);
+ }
+ else if (_dirty.Count > 0)
+ {
+ foreach (var visual in _dirty)
+ {
+ _sceneBuilder.Update(scene, visual);
+ }
}
- }
- Interlocked.Exchange(ref _scene, scene);
+ Interlocked.Exchange(ref _scene, scene);
- _dirty.Clear();
- (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
+ _dirty.Clear();
+ (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
+ }
+ else
+ {
+ Interlocked.Exchange(ref _scene, null);
+ }
}
finally
{
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index afdf488b313..10455eb1470 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -36,8 +36,14 @@ public bool Update(Scene scene, IVisual visual)
{
Contract.Requires(scene != null);
Contract.Requires(visual != null);
+
Dispatcher.UIThread.VerifyAccess();
+ if (!scene.Root.Visual.IsVisible)
+ {
+ throw new AvaloniaInternalException("Cannot update the scene for an invisible root visual.");
+ }
+
var node = (VisualNode)scene.FindNode(visual);
if (visual == scene.Root.Visual)
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index dbf985fd796..08ea6b68779 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -31,6 +31,8 @@
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
new file mode 100644
index 00000000000..c477ff5637d
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/MatrixTypeConverter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+ using System.ComponentModel;
+
+ public class MatrixTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return Matrix.Parse((string)value, culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
new file mode 100644
index 00000000000..c9c6462f896
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/Converters/RectTypeConverter.cs
@@ -0,0 +1,23 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Globalization;
+
+namespace Avalonia.Markup.Xaml.Converters
+{
+ using System.ComponentModel;
+
+ public class RectTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string);
+ }
+
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return Rect.Parse((string)value, culture);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
index 66e9a697e45..1cf5b6a58eb 100644
--- a/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaDefaultTypeConverters.cs
@@ -32,12 +32,14 @@ public class AvaloniaDefaultTypeConverters
{ typeof(AvaloniaList), typeof(AvaloniaListTypeConverter) },
{ typeof(IMemberSelector), typeof(MemberSelectorTypeConverter) },
{ typeof(Point), typeof(PointTypeConverter) },
+ { typeof(Matrix), typeof(MatrixTypeConverter) },
{ typeof(IList), typeof(PointsListTypeConverter) },
{ typeof(AvaloniaProperty), typeof(AvaloniaPropertyTypeConverter) },
{ typeof(RelativePoint), typeof(RelativePointTypeConverter) },
{ typeof(RelativeRect), typeof(RelativeRectTypeConverter) },
{ typeof(RowDefinitions), typeof(RowDefinitionsTypeConverter) },
{ typeof(Size), typeof(SizeTypeConverter) },
+ { typeof(Rect), typeof(RectTypeConverter) },
{ typeof(Selector), typeof(SelectorTypeConverter)},
{ typeof(SolidColorBrush), typeof(BrushTypeConverter) },
{ typeof(Thickness), typeof(ThicknessTypeConverter) },
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 0b46ba1c479..69b582b009a 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -119,7 +119,10 @@ public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRe
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{
- d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D();
+ if (d2dOpacityMask.PlatformBrush != null)
+ {
+ d2dOpacityMask.PlatformBrush.Transform = Matrix.CreateTranslation(opacityMaskRect.Position).ToDirect2D();
+ }
_renderTarget.FillGeometry(
geometry,
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
new file mode 100644
index 00000000000..4c1e361952a
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/MatrixTests.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Media
+{
+ public class MatrixTests
+ {
+ [Fact]
+ public void Parse_Parses()
+ {
+ var matrix = Matrix.Parse("1,2,3,-4,5 6", CultureInfo.CurrentCulture);
+ var expected = new Matrix(1, 2, 3, -4, 5, 6);
+ Assert.Equal(expected, matrix);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
index f0d41680c06..3b903b4436c 100644
--- a/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Media/PathMarkupParserTests.cs
@@ -56,6 +56,7 @@ public void Parses_Close()
}
[Theory]
+ [InlineData("F1 M24,14 A2,2,0,1,1,20,14 A2,2,0,1,1,24,14 z")] // issue #1107
[InlineData("M0 0L10 10z")]
[InlineData("M50 50 L100 100 L150 50")]
[InlineData("M50 50L100 100L150 50")]
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs
new file mode 100644
index 00000000000..12070bfed34
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/RectTests.cs
@@ -0,0 +1,16 @@
+using System.Globalization;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Media
+{
+ public class RectTests
+ {
+ [Fact]
+ public void Parse_Parses()
+ {
+ var rect = Rect.Parse("1,2 3,-4", CultureInfo.CurrentCulture);
+ var expected = new Rect(1, 2, 3, -4);
+ Assert.Equal(expected, rect);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
index 13f28018db8..e65487ac44f 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
@@ -273,32 +273,5 @@ public void GeometryClip_Should_Affect_Child_Layers()
((MockStreamGeometryImpl)borderLayer.GeometryClip).Transform);
}
}
-
- [Fact]
- public void Hiding_Root_Should_Not_Remove_Root_Layer()
- {
- using (TestApplication())
- {
- Border border;
- var tree = new TestRoot
- {
- Child = border = new Border()
- };
-
- var layout = AvaloniaLocator.Current.GetService();
- layout.ExecuteInitialLayoutPass(tree);
-
- var scene = new Scene(tree);
- var sceneBuilder = new SceneBuilder();
- sceneBuilder.UpdateAll(scene);
-
- tree.IsVisible = false;
-
- scene = scene.Clone();
- sceneBuilder.Update(scene, tree);
-
- Assert.Equal(1, scene.Layers.Count);
- }
- }
}
}