diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 2ddb5887e5..874449c9b6 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -23,14 +23,17 @@
+
+
+
diff --git a/samples/ControlCatalog/Pages/EllipsePage.xaml b/samples/ControlCatalog/Pages/EllipsePage.xaml
new file mode 100644
index 0000000000..f66cca9cdb
--- /dev/null
+++ b/samples/ControlCatalog/Pages/EllipsePage.xaml
@@ -0,0 +1,23 @@
+
+
+ Ellipse
+ A control which represents an ellipse with a fill and a stroke.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/EllipsePage.xaml.cs b/samples/ControlCatalog/Pages/EllipsePage.xaml.cs
new file mode 100644
index 0000000000..e450e961ed
--- /dev/null
+++ b/samples/ControlCatalog/Pages/EllipsePage.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class EllipsePage : UserControl
+ {
+ public EllipsePage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/LinePage.xaml b/samples/ControlCatalog/Pages/LinePage.xaml
new file mode 100644
index 0000000000..62ab917578
--- /dev/null
+++ b/samples/ControlCatalog/Pages/LinePage.xaml
@@ -0,0 +1,21 @@
+
+
+ Line
+ A control which represents a line.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/LinePage.xaml.cs b/samples/ControlCatalog/Pages/LinePage.xaml.cs
new file mode 100644
index 0000000000..43b8049d45
--- /dev/null
+++ b/samples/ControlCatalog/Pages/LinePage.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class LinePage : UserControl
+ {
+ public LinePage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/RectanglePage.xaml b/samples/ControlCatalog/Pages/RectanglePage.xaml
new file mode 100644
index 0000000000..49701812c4
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RectanglePage.xaml
@@ -0,0 +1,21 @@
+
+
+ Rectangle
+ A control which represents a rectangle with a fill and a stroke.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/RectanglePage.xaml.cs b/samples/ControlCatalog/Pages/RectanglePage.xaml.cs
new file mode 100644
index 0000000000..0ee8a1a40f
--- /dev/null
+++ b/samples/ControlCatalog/Pages/RectanglePage.xaml.cs
@@ -0,0 +1,18 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+ public class RectanglePage : UserControl
+ {
+ public RectanglePage()
+ {
+ this.InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs
index ca84d4cc7b..c2df9db635 100644
--- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs
+++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs
@@ -1,7 +1,6 @@
// 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 Avalonia.Platform;
namespace Avalonia.Media
@@ -57,36 +56,8 @@ namespace Avalonia.Media
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService();
- var geometry = factory.CreateStreamGeometry();
- using (var ctx = geometry.Open())
- {
- var rect = Rect;
- double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
- var center = rect.Center;
- var radius = new Vector(rect.Width / 2, rect.Height / 2);
-
- var x0 = center.X - radius.X;
- var x1 = center.X - (radius.X * controlPointRatio);
- var x2 = center.X;
- var x3 = center.X + (radius.X * controlPointRatio);
- var x4 = center.X + radius.X;
-
- var y0 = center.Y - radius.Y;
- var y1 = center.Y - (radius.Y * controlPointRatio);
- var y2 = center.Y;
- var y3 = center.Y + (radius.Y * controlPointRatio);
- var y4 = center.Y + radius.Y;
-
- ctx.BeginFigure(new Point(x2, y0), true);
- ctx.CubicBezierTo(new Point(x3, y0), new Point(x4, y1), new Point(x4, y2));
- ctx.CubicBezierTo(new Point(x4, y3), new Point(x3, y4), new Point(x2, y4));
- ctx.CubicBezierTo(new Point(x1, y4), new Point(x0, y3), new Point(x0, y2));
- ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
- ctx.EndFigure(true);
- }
-
- return geometry;
+ return factory.CreateEllipseGeometry(Rect);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs
index f7ba4ccb0e..90577dabd8 100644
--- a/src/Avalonia.Visuals/Media/LineGeometry.cs
+++ b/src/Avalonia.Visuals/Media/LineGeometry.cs
@@ -73,16 +73,8 @@ namespace Avalonia.Media
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService();
- var geometry = factory.CreateStreamGeometry();
- using (var context = geometry.Open())
- {
- context.BeginFigure(StartPoint, false);
- context.LineTo(EndPoint);
- context.EndFigure(false);
- }
-
- return geometry;
+ return factory.CreateLineGeometry(StartPoint, EndPoint);
}
}
}
diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs
index 3ccfd80f93..9250500644 100644
--- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs
+++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs
@@ -16,12 +16,6 @@ namespace Avalonia.Media
public static readonly StyledProperty RectProperty =
AvaloniaProperty.Register(nameof(Rect));
- public Rect Rect
- {
- get => GetValue(RectProperty);
- set => SetValue(RectProperty, value);
- }
-
static RectangleGeometry()
{
AffectsGeometry(RectProperty);
@@ -43,25 +37,23 @@ namespace Avalonia.Media
Rect = rect;
}
+ ///
+ /// Gets or sets the bounds of the rectangle.
+ ///
+ public Rect Rect
+ {
+ get => GetValue(RectProperty);
+ set => SetValue(RectProperty, value);
+ }
+
///
public override Geometry Clone() => new RectangleGeometry(Rect);
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService();
- var geometry = factory.CreateStreamGeometry();
-
- using (var context = geometry.Open())
- {
- var rect = Rect;
- context.BeginFigure(rect.TopLeft, true);
- context.LineTo(rect.TopRight);
- context.LineTo(rect.BottomRight);
- context.LineTo(rect.BottomLeft);
- context.EndFigure(true);
- }
- return geometry;
+ return factory.CreateRectangleGeometry(Rect);
}
}
}
diff --git a/src/Avalonia.Visuals/Platform/IEllipseGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IEllipseGeometryImpl.cs
new file mode 100644
index 0000000000..982cf286a3
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IEllipseGeometryImpl.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Avalonia.Platform
+{
+ ///
+ /// Defines the platform-specific interface for a .
+ ///
+ public interface IEllipseGeometryImpl : IGeometryImpl
+ {
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/ILineGeometryImpl.cs b/src/Avalonia.Visuals/Platform/ILineGeometryImpl.cs
new file mode 100644
index 0000000000..a8aa46fb13
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/ILineGeometryImpl.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Avalonia.Platform
+{
+ ///
+ /// Defines the platform-specific interface for a .
+ ///
+ public interface ILineGeometryImpl : IGeometryImpl
+ {
+ }
+}
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index 3a1f79e32a..45ce8f5874 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -36,6 +36,28 @@ namespace Avalonia.Platform
Size constraint,
IReadOnlyList spans);
+ ///
+ /// Creates an ellipse geometry implementation.
+ ///
+ /// The bounds of the ellipse.
+ /// An .
+ IEllipseGeometryImpl CreateEllipseGeometry(Rect rect);
+
+ ///
+ /// Creates a line geometry implementation.
+ ///
+ /// The start of the line.
+ /// The end of the line.
+ /// An .
+ ILineGeometryImpl CreateLineGeometry(Point p1, Point p2);
+
+ ///
+ /// Creates a rectangle geometry implementation.
+ ///
+ /// The bounds of the rectangle.
+ /// An .
+ IRectangleGeometryImpl CreateRectangleGeometry(Rect rect);
+
///
/// Creates a stream geometry implementation.
///
diff --git a/src/Avalonia.Visuals/Platform/IRectangleGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IRectangleGeometryImpl.cs
new file mode 100644
index 0000000000..7ada2c874d
--- /dev/null
+++ b/src/Avalonia.Visuals/Platform/IRectangleGeometryImpl.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Avalonia.Platform
+{
+ ///
+ /// Defines the platform-specific interface for a .
+ ///
+ public interface IRectangleGeometryImpl : IGeometryImpl
+ {
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs b/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
new file mode 100644
index 0000000000..92a0a5ff80
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
@@ -0,0 +1,26 @@
+// 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.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ ///
+ /// A Skia implementation of a .
+ ///
+ internal class EllipseGeometryImpl : GeometryImpl, IEllipseGeometryImpl
+ {
+ public override Rect Bounds { get; }
+ public override SKPath EffectivePath { get; }
+
+ public EllipseGeometryImpl(Rect rect)
+ {
+ var path = new SKPath();
+ path.AddOval(rect.ToSKRect());
+
+ EffectivePath = path;
+ Bounds = rect;
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/LineGeometryImpl.cs b/src/Skia/Avalonia.Skia/LineGeometryImpl.cs
new file mode 100644
index 0000000000..a78c76d44a
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/LineGeometryImpl.cs
@@ -0,0 +1,30 @@
+// 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 Avalonia.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ ///
+ /// A Skia implementation of a .
+ ///
+ internal class LineGeometryImpl : GeometryImpl, ILineGeometryImpl
+ {
+ public override Rect Bounds { get; }
+ public override SKPath EffectivePath { get; }
+
+ public LineGeometryImpl(Point p1, Point p2)
+ {
+ var path = new SKPath();
+ path.MoveTo(p1.ToSKPoint());
+ path.LineTo(p2.ToSKPoint());
+
+ EffectivePath = path;
+ Bounds = new Rect(
+ new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)),
+ new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)));
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index c6e68b1c8b..f1e89bc464 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -50,6 +50,12 @@ namespace Avalonia.Skia
return new FormattedTextImpl(text, typeface, textAlignment, wrapping, constraint, spans);
}
+ public IEllipseGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
+
+ public ILineGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
+
+ public IRectangleGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
+
///
public IStreamGeometryImpl CreateStreamGeometry()
{
diff --git a/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs b/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
new file mode 100644
index 0000000000..235cd37b56
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
@@ -0,0 +1,26 @@
+// 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.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ ///
+ /// A Skia implementation of a .
+ ///
+ internal class RectangleGeometryImpl : GeometryImpl, IRectangleGeometryImpl
+ {
+ public override Rect Bounds { get; }
+ public override SKPath EffectivePath { get; }
+
+ public RectangleGeometryImpl(Rect rect)
+ {
+ var path = new SKPath();
+ path.AddRect(rect.ToSKRect());
+
+ EffectivePath = path;
+ Bounds = rect;
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 8412a65e23..cb37a93bb3 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -182,10 +182,10 @@ namespace Avalonia.Direct2D1
return new WriteableWicBitmapImpl(size, dpi, format);
}
- public IStreamGeometryImpl CreateStreamGeometry()
- {
- return new StreamGeometryImpl();
- }
+ public IEllipseGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
+ public ILineGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
+ public IRectangleGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
+ public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IBitmapImpl LoadBitmap(string fileName)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs
new file mode 100644
index 0000000000..c6bf475ed1
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs
@@ -0,0 +1,28 @@
+// 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.Platform;
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ ///
+ /// A Direct2D implementation of a .
+ ///
+ internal class EllipseGeometryImpl : GeometryImpl, IEllipseGeometryImpl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public EllipseGeometryImpl(Rect rect)
+ : base(CreateGeometry(rect))
+ {
+ }
+
+ private static Geometry CreateGeometry(Rect rect)
+ {
+ var ellipse = new Ellipse(rect.Center.ToSharpDX(), (float)rect.Width / 2, (float)rect.Height / 2);
+ return new EllipseGeometry(Direct2D1Platform.Direct2D1Factory, ellipse);
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs
new file mode 100644
index 0000000000..b2d11247b9
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs
@@ -0,0 +1,28 @@
+// 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.Platform;
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ ///
+ /// A Direct2D implementation of a .
+ ///
+ internal class LineGeometryImpl : StreamGeometryImpl, ILineGeometryImpl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LineGeometryImpl(Point p1, Point p2)
+ {
+ using (var sink = ((PathGeometry)Geometry).Open())
+ {
+ sink.BeginFigure(p1.ToSharpDX(), FigureBegin.Hollow);
+ sink.AddLine(p2.ToSharpDX());
+ sink.EndFigure(FigureEnd.Open);
+ sink.Close();
+ }
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs
new file mode 100644
index 0000000000..55b2b66b3f
--- /dev/null
+++ b/src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs
@@ -0,0 +1,27 @@
+// 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.Platform;
+using SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+ ///
+ /// A Direct2D implementation of a .
+ ///
+ internal class RectangleGeometryImpl : GeometryImpl, IRectangleGeometryImpl
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RectangleGeometryImpl(Rect rect)
+ : base(CreateGeometry(rect))
+ {
+ }
+
+ private static Geometry CreateGeometry(Rect rect)
+ {
+ return new RectangleGeometry(Direct2D1Platform.Direct2D1Factory, rect.ToDirect2D());
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
index 400dd59ea9..5b04c5d7ff 100644
--- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
+++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
@@ -50,7 +50,7 @@ namespace Avalonia.Win32.Interop.Wpf
{
_resource = texture.QueryInterface();
- Target = new RenderTarget(AvaloniaLocator.Current.GetService(), surface,
+ Target = new RenderTarget(Direct2D1Platform.Direct2D1Factory, surface,
new RenderTargetProperties
{
DpiX = (float) dpi.X,
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 0e2abb314d..1aeaf6bf8b 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -22,6 +22,21 @@ namespace Avalonia.UnitTests
return Mock.Of();
}
+ public IEllipseGeometryImpl CreateEllipseGeometry(Rect rect)
+ {
+ throw new NotImplementedException();
+ }
+
+ public ILineGeometryImpl CreateLineGeometry(Point p1, Point p2)
+ {
+ throw new NotImplementedException();
+ }
+
+ public IRectangleGeometryImpl CreateRectangleGeometry(Rect rect)
+ {
+ throw new NotImplementedException();
+ }
+
public IRenderTarget CreateRenderTarget(IEnumerable