Browse Source

Merge remote-tracking branch 'origin/master' into features/managed-notifications

Dan Walmsley 6 years ago
parent
commit
30fa0e8774
63 changed files with 742 additions and 99 deletions
  1. 8 0
      nukebuild/Build.cs
  2. 1 1
      nukebuild/Numerge
  3. 1 0
      samples/BindingDemo/App.xaml.cs
  4. 1 0
      samples/ControlCatalog.Desktop/Program.cs
  5. 1 0
      samples/ControlCatalog.NetCore/Program.cs
  6. 1 0
      samples/RenderDemo/App.xaml.cs
  7. 3 0
      samples/RenderDemo/MainWindow.xaml
  8. 119 0
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  9. 1 0
      samples/VirtualizationDemo/Program.cs
  10. 2 0
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  11. 2 2
      src/Avalonia.Controls/ListBox.cs
  12. 3 5
      src/Avalonia.Controls/TreeView.cs
  13. 13 1
      src/Avalonia.Controls/Window.cs
  14. 6 1
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  15. 8 0
      src/Avalonia.ReactiveUI/Attributes.cs
  16. 17 1
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  17. 1 1
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  18. 1 1
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  19. 6 2
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  20. 1 0
      src/Avalonia.Styling/StyledElement.cs
  21. 7 0
      src/Avalonia.Visuals/Media/DrawingContext.cs
  22. 1 30
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  23. 1 9
      src/Avalonia.Visuals/Media/LineGeometry.cs
  24. 10 18
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  25. 7 0
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  26. 22 0
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  27. 39 0
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  28. 9 0
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  29. 2 1
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  30. 42 0
      src/Skia/Avalonia.Skia/CustomRenderTarget.cs
  31. 6 1
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  32. 25 0
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  33. 1 1
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  34. 1 1
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  35. 1 1
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  36. 1 1
      src/Skia/Avalonia.Skia/GlRenderTarget.cs
  37. 26 0
      src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs
  38. 29 0
      src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs
  39. 19 0
      src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs
  40. 10 0
      src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
  41. 1 1
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  42. 29 0
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  43. 32 6
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  44. 25 0
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  45. 3 2
      src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs
  46. 19 0
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  47. 8 2
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  48. 1 1
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  49. 1 1
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  50. 1 1
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  51. 1 1
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  52. 4 4
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  53. 3 0
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  54. 27 0
      src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs
  55. 1 1
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  56. 27 0
      src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs
  57. 26 0
      src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs
  58. 1 1
      src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
  59. 45 0
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  60. 1 0
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  61. 1 0
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  62. 15 0
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  63. 15 0
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

+ 8 - 0
nukebuild/Build.cs

@@ -122,6 +122,14 @@ partial class Build : NukeBuild
         
         foreach(var fw in frameworks)
         {
+            if (fw.StartsWith("net4")
+                && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) 
+                && Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
+            {
+                Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969");
+                continue;
+            }
+
             Information("Running for " + fw);
             DotNetTest(c =>
             {

+ 1 - 1
nukebuild/Numerge

@@ -1 +1 @@
-Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
+Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5

+ 1 - 0
samples/BindingDemo/App.xaml.cs

@@ -3,6 +3,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Logging.Serilog;
 using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
 using Serilog;
 
 namespace BindingDemo

+ 1 - 0
samples/ControlCatalog.Desktop/Program.cs

@@ -4,6 +4,7 @@ using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Logging.Serilog;
 using Avalonia.Platform;
+using Avalonia.ReactiveUI;
 using Serilog;
 
 namespace ControlCatalog

+ 1 - 0
samples/ControlCatalog.NetCore/Program.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Threading;
 using Avalonia;
 using Avalonia.Skia;
+using Avalonia.ReactiveUI;
 
 namespace ControlCatalog.NetCore
 {

+ 1 - 0
samples/RenderDemo/App.xaml.cs

@@ -4,6 +4,7 @@
 using Avalonia;
 using Avalonia.Logging.Serilog;
 using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
 
 namespace RenderDemo
 {

+ 3 - 0
samples/RenderDemo/MainWindow.xaml

@@ -33,6 +33,9 @@
       <TabItem Header="Drawing">
         <pages:DrawingPage/>
       </TabItem>
+      <TabItem Header="SkCanvas">
+        <pages:CustomSkiaPage/>
+      </TabItem>
     </TabControl>
   </DockPanel>
 </Window>

+ 119 - 0
samples/RenderDemo/Pages/CustomSkiaPage.cs

@@ -0,0 +1,119 @@
+using System;
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+using Avalonia.Threading;
+using SkiaSharp;
+
+namespace RenderDemo.Pages
+{
+    public class CustomSkiaPage : Control
+    {
+        public CustomSkiaPage()
+        {
+            ClipToBounds = true;
+        }
+        
+        class CustomDrawOp : ICustomDrawOperation
+        {
+            private readonly FormattedText _noSkia;
+
+            public CustomDrawOp(Rect bounds, FormattedText noSkia)
+            {
+                _noSkia = noSkia;
+                Bounds = bounds;
+            }
+            
+            public void Dispose()
+            {
+                // No-op
+            }
+
+            public Rect Bounds { get; }
+            public bool HitTest(Point p) => false;
+            public bool Equals(ICustomDrawOperation other) => false;
+            static Stopwatch St = Stopwatch.StartNew();
+            public void Render(IDrawingContextImpl context)
+            {
+                var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
+                if (canvas == null)
+                    context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
+                else
+                {
+                    canvas.Save();
+                    // create the first shader
+                    var colors = new SKColor[] {
+                        new SKColor(0, 255, 255),
+                        new SKColor(255, 0, 255),
+                        new SKColor(255, 255, 0),
+                        new SKColor(0, 255, 255)
+                    };
+
+                    var sx = Animate(100, 2, 10);
+                    var sy = Animate(1000, 5, 15);
+                    var lightPosition = new SKPoint(
+                        (float)(Bounds.Width / 2 + Math.Cos(St.Elapsed.TotalSeconds) * Bounds.Width / 4),
+                        (float)(Bounds.Height / 2 + Math.Sin(St.Elapsed.TotalSeconds) * Bounds.Height / 4));
+                    using (var sweep =
+                        SKShader.CreateSweepGradient(new SKPoint((int)Bounds.Width / 2, (int)Bounds.Height / 2), colors,
+                            null)) 
+                    using(var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0))
+                    using(var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop))
+                    using(var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15)))
+                    using (var paint = new SKPaint
+                    {
+                        Shader = shader,
+                        ImageFilter = blur
+                    })
+                        canvas.DrawPaint(paint);
+                    
+                    using (var pseudoLight = SKShader.CreateRadialGradient(
+                        lightPosition,
+                        (float) (Bounds.Width/3),
+                        new [] { 
+                            new SKColor(255, 200, 200, 100), 
+                            SKColors.Transparent,
+                            new SKColor(40,40,40, 220), 
+                            new SKColor(20,20,20, (byte)Animate(100, 200,220)) },
+                        new float[] { 0.3f, 0.3f, 0.8f, 1 },
+                        SKShaderTileMode.Clamp))
+                    using (var paint = new SKPaint
+                    {
+                        Shader = pseudoLight
+                    })
+                        canvas.DrawPaint(paint);
+                    canvas.Restore();
+                }
+            }    
+            static int Animate(int d, int from, int to)
+            {
+                var ms = (int)(St.ElapsedMilliseconds / d);
+                var diff = to - from;
+                var range = diff * 2;
+                var v = ms % range;
+                if (v > diff)
+                    v = range - v;
+                var rv = v + from;
+                if (rv < from || rv > to)
+                    throw new Exception("WTF");
+                return rv;
+            }
+        }
+
+
+        
+        public override void Render(DrawingContext context)
+        {
+            var noSkia = new FormattedText()
+            {
+                Text = "Current rendering API is not Skia"
+            };
+            context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
+            Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
+        }
+    }
+}

+ 1 - 0
samples/VirtualizationDemo/Program.cs

@@ -5,6 +5,7 @@ using System;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Logging.Serilog;
+using Avalonia.ReactiveUI;
 using Serilog;
 
 namespace VirtualizationDemo

+ 2 - 0
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@@ -19,6 +19,7 @@ namespace Avalonia.Utilities
         /// </summary>
         /// <typeparam name="TTarget">The type of the target.</typeparam>
         /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
+        /// <typeparam name="TSubscriber">The type of the subscriber.</typeparam>
         /// <param name="target">The event source.</param>
         /// <param name="eventName">The name of the event.</param>
         /// <param name="subscriber">The subscriber.</param>
@@ -40,6 +41,7 @@ namespace Avalonia.Utilities
         /// Unsubscribes from an event.
         /// </summary>
         /// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
+        /// <typeparam name="TSubscriber">The type of the subscriber.</typeparam>
         /// <param name="target">The event source.</param>
         /// <param name="eventName">The name of the event.</param>
         /// <param name="subscriber">The subscriber.</param>

+ 2 - 2
src/Avalonia.Controls/ListBox.cs

@@ -30,13 +30,13 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="SelectedItems"/> property.
         /// </summary>
-        public static readonly new AvaloniaProperty<IList> SelectedItemsProperty =
+        public static readonly new DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
             SelectingItemsControl.SelectedItemsProperty;
 
         /// <summary>
         /// Defines the <see cref="SelectionMode"/> property.
         /// </summary>
-        public static readonly new AvaloniaProperty<SelectionMode> SelectionModeProperty = 
+        public static readonly new StyledProperty<SelectionMode> SelectionModeProperty = 
             SelectingItemsControl.SelectionModeProperty;
 
         /// <summary>

+ 3 - 5
src/Avalonia.Controls/TreeView.cs

@@ -40,17 +40,15 @@ namespace Avalonia.Controls
         /// Defines the <see cref="SelectedItems"/> property.
         /// </summary>
         public static readonly DirectProperty<TreeView, IList> SelectedItemsProperty =
-            AvaloniaProperty.RegisterDirect<TreeView, IList>(
-                nameof(SelectedItems),
+            ListBox.SelectedItemsProperty.AddOwner<TreeView>(
                 o => o.SelectedItems,
                 (o, v) => o.SelectedItems = v);
 
         /// <summary>
         /// Defines the <see cref="SelectionMode"/> property.
         /// </summary>
-        protected static readonly StyledProperty<SelectionMode> SelectionModeProperty =
-            AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>(
-                nameof(SelectionMode));
+        public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
+            ListBox.SelectionModeProperty.AddOwner<TreeView>();
 
         private static readonly IList Empty = new object[0];
         private object _selectedItem;

+ 13 - 1
src/Avalonia.Controls/Window.cs

@@ -291,7 +291,8 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="dialogResult">The dialog result.</param>
         /// <remarks>
-        /// When the window is shown with the <see cref="ShowDialog{TResult}"/> method, the
+        /// When the window is shown with the <see cref="ShowDialog{TResult}(IWindowImpl)"/>
+        /// or <see cref="ShowDialog{TResult}(Window)"/> method, the
         /// resulting task will produce the <see cref="_dialogResult"/> value when the window
         /// is closed.
         /// </remarks>
@@ -370,8 +371,16 @@ namespace Avalonia.Controls
         /// <summary>
         /// Shows the window.
         /// </summary>
+        /// <exception cref="InvalidOperationException">
+        /// The window has already been closed.
+        /// </exception>
         public override void Show()
         {
+            if (PlatformImpl == null)
+            {
+                throw new InvalidOperationException("Cannot re-show a closed window.");
+            }
+
             if (IsVisible)
             {
                 return;
@@ -396,6 +405,9 @@ namespace Avalonia.Controls
         /// Shows the window as a dialog.
         /// </summary>
         /// <param name="owner">The dialog's owner window.</param>
+        /// <exception cref="InvalidOperationException">
+        /// The window has already been closed.
+        /// </exception>
         /// <returns>
         /// A task that can be used to track the lifetime of the dialog.
         /// </returns>

+ 6 - 1
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@@ -6,10 +6,15 @@ using Avalonia.Threading;
 using ReactiveUI;
 using Splat;
 
-namespace Avalonia
+namespace Avalonia.ReactiveUI
 {
     public static class AppBuilderExtensions
     {
+        /// <summary>
+        /// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia 
+        /// scheduler and Avalonia activation for view fetcher. Always remember to
+        /// call this method if you are using ReactiveUI in your application.
+        /// </summary>
         public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
             where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
         {

+ 8 - 0
src/Avalonia.ReactiveUI/Attributes.cs

@@ -0,0 +1,8 @@
+// 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.Reflection;
+using System.Runtime.CompilerServices;
+using Avalonia.Metadata;
+
+[assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")]

+ 17 - 1
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@@ -9,15 +9,24 @@ using Avalonia.VisualTree;
 using Avalonia.Controls;
 using ReactiveUI;
 
-namespace Avalonia
+namespace Avalonia.ReactiveUI
 {
+    /// <summary>
+    /// Determines when Avalonia IVisuals get activated.
+    /// </summary>
     public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher
     {
+        /// <summary>
+        /// Returns affinity for view.
+        /// </summary>
         public int GetAffinityForView(Type view)
         {
             return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
         }
 
+        /// <summary>
+        /// Returns activation observable for activatable Avalonia view.
+        /// </summary>
         public IObservable<bool> GetActivationForView(IActivatable view)
         {
             if (!(view is IVisual visual)) return Observable.Return(false);
@@ -25,6 +34,9 @@ namespace Avalonia
             return GetActivationForVisual(visual);
         }
 
+        /// <summary>
+        /// Listens to Opened and Closed events for Avalonia windows.
+        /// </summary>
         private IObservable<bool> GetActivationForWindowBase(WindowBase window) 
         {
             var windowLoaded = Observable
@@ -42,6 +54,10 @@ namespace Avalonia
                 .DistinctUntilChanged();
         }
 
+        /// <summary>
+        /// Listens to AttachedToVisualTree and DetachedFromVisualTree 
+        /// events for Avalonia IVisuals.
+        /// </summary>
         private IObservable<bool> GetActivationForVisual(IVisual visual) 
         {
             var visualLoaded = Observable

+ 1 - 1
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@@ -6,7 +6,7 @@ using Avalonia.VisualTree;
 using Avalonia.Controls;
 using ReactiveUI;
 
-namespace Avalonia
+namespace Avalonia.ReactiveUI
 {
     /// <summary>
     /// A ReactiveUI UserControl that implements <see cref="IViewFor{TViewModel}"/> 

+ 1 - 1
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@@ -6,7 +6,7 @@ using Avalonia.VisualTree;
 using Avalonia.Controls;
 using ReactiveUI;
 
-namespace Avalonia 
+namespace Avalonia.ReactiveUI
 {
     /// <summary>
     /// A ReactiveUI Window that implements <see cref="IViewFor{TViewModel}"/>

+ 6 - 2
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@@ -1,13 +1,17 @@
+// 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.Reactive.Disposables;
 using System.Reactive.Linq;
 using Avalonia.Animation;
 using Avalonia.Controls;
 using Avalonia.Styling;
+using Avalonia;
 using ReactiveUI;
 using Splat;
 
-namespace Avalonia
+namespace Avalonia.ReactiveUI
 {
     /// <summary>
     /// This control hosts the View associated with ReactiveUI RoutingState,
@@ -157,7 +161,7 @@ namespace Avalonia
                 return;
             }
     
-            var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
+            var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
             var view = viewLocator.ResolveView(viewModel);
             if (view == null) throw new Exception($"Couldn't find view for '{viewModel}'. Is it registered?");
     

+ 1 - 0
src/Avalonia.Styling/StyledElement.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Collections.Specialized;
+using System.ComponentModel;
 using System.Linq;
 using System.Reactive.Linq;
 using System.Reactive.Subjects;

+ 7 - 0
src/Avalonia.Visuals/Media/DrawingContext.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Media.Imaging;
 using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
 using Avalonia.Threading;
 using Avalonia.Visuals.Media.Imaging;
 
@@ -131,6 +132,12 @@ namespace Avalonia.Media
             }
         }
 
+        /// <summary>
+        /// Draws a custom drawing operation
+        /// </summary>
+        /// <param name="custom">custom operation</param>
+        public void Custom(ICustomDrawOperation custom) => PlatformImpl.Custom(custom);
+
         /// <summary>
         /// Draws text.
         /// </summary>

+ 1 - 30
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<IPlatformRenderInterface>();
-            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);
         }
     }
 }

+ 1 - 9
src/Avalonia.Visuals/Media/LineGeometry.cs

@@ -73,16 +73,8 @@ namespace Avalonia.Media
         protected override IGeometryImpl CreateDefiningGeometry()
         {
             var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            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);
         }
     }
 }

+ 10 - 18
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@@ -16,12 +16,6 @@ namespace Avalonia.Media
         public static readonly StyledProperty<Rect> RectProperty =
             AvaloniaProperty.Register<RectangleGeometry, Rect>(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;
         }
 
+        /// <summary>
+        /// Gets or sets the bounds of the rectangle.
+        /// </summary>
+        public Rect Rect
+        {
+            get => GetValue(RectProperty);
+            set => SetValue(RectProperty, value);
+        }
+
         /// <inheritdoc/>
         public override Geometry Clone() => new RectangleGeometry(Rect);
 
         protected override IGeometryImpl CreateDefiningGeometry()
         {
             var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            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);
         }
     }
 }

+ 7 - 0
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Media;
+using Avalonia.Rendering.SceneGraph;
 using Avalonia.Utilities;
 using Avalonia.Visuals.Media.Imaging;
 
@@ -139,5 +140,11 @@ namespace Avalonia.Platform
         /// Pops the latest pushed geometry clip.
         /// </summary>
         void PopGeometryClip();
+
+        /// <summary>
+        /// Adds a custom draw operation
+        /// </summary>
+        /// <param name="custom">Custom draw operation</param>
+        void Custom(ICustomDrawOperation custom);
     }
 }

+ 22 - 0
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@@ -36,6 +36,28 @@ namespace Avalonia.Platform
             Size constraint,
             IReadOnlyList<FormattedTextStyleSpan> spans);
 
+        /// <summary>
+        /// Creates an ellipse geometry implementation.
+        /// </summary>
+        /// <param name="rect">The bounds of the ellipse.</param>
+        /// <returns>An ellipse geometry..</returns>
+        IGeometryImpl CreateEllipseGeometry(Rect rect);
+
+        /// <summary>
+        /// Creates a line geometry implementation.
+        /// </summary>
+        /// <param name="p1">The start of the line.</param>
+        /// <param name="p2">The end of the line.</param>
+        /// <returns>A line geometry.</returns>
+        IGeometryImpl CreateLineGeometry(Point p1, Point p2);
+
+        /// <summary>
+        /// Creates a rectangle geometry implementation.
+        /// </summary>
+        /// <param name="rect">The bounds of the rectangle.</param>
+        /// <returns>A rectangle.</returns>
+        IGeometryImpl CreateRectangleGeometry(Rect rect);
+
         /// <summary>
         /// Creates a stream geometry implementation.
         /// </summary>

+ 39 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs

@@ -0,0 +1,39 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+    internal sealed class CustomDrawOperation : DrawOperation
+    {
+        public Matrix Transform { get; }
+        public ICustomDrawOperation Custom { get; }
+        public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform) 
+            : base(custom.Bounds, transform, null)
+        {
+            Transform = transform;
+            Custom = custom;
+        }
+
+        public override bool HitTest(Point p)
+        {
+            return Custom.HitTest(p * Transform);
+        }
+
+        public override void Render(IDrawingContextImpl context)
+        {
+            context.Transform = Transform;
+            Custom.Render(context);
+        }
+
+        public override void Dispose() => Custom.Dispose();
+
+        public bool Equals(Matrix transform, ICustomDrawOperation custom) =>
+            Transform == transform && Custom?.Equals(custom) == true;
+    }
+
+    public interface ICustomDrawOperation : IDrawOperation, IEquatable<ICustomDrawOperation>
+    {
+        
+    }
+}

+ 9 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -165,6 +165,15 @@ namespace Avalonia.Rendering.SceneGraph
                 ++_drawOperationindex;
             }
         }
+        
+        public void Custom(ICustomDrawOperation custom)
+        {
+            var next = NextDrawAs<CustomDrawOperation>();
+            if (next == null || !next.Item.Equals(Transform, custom))
+                Add(new CustomDrawOperation(custom, Transform));
+            else
+                ++_drawOperationindex;
+        }
 
         /// <inheritdoc/>
         public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)

+ 2 - 1
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@@ -162,7 +162,8 @@ namespace Avalonia.Markup.Xaml
             var readerSettings = new XamlXmlReaderSettings()
             {
                 BaseUri = uri,
-                LocalAssembly = localAssembly
+                LocalAssembly = localAssembly,
+                ProvideLineInfo = true,
             };
 
             var context = IsDesignMode ? AvaloniaXamlSchemaContext.DesignInstance : AvaloniaXamlSchemaContext.Instance;

+ 42 - 0
src/Skia/Avalonia.Skia/CustomRenderTarget.cs

@@ -0,0 +1,42 @@
+// 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 Avalonia.Rendering;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// Adapts <see cref="ICustomSkiaRenderTarget"/> to be used within Skia rendering pipeline.
+    /// </summary>
+    internal class CustomRenderTarget : IRenderTarget
+    {
+        private readonly ICustomSkiaRenderTarget _renderTarget;
+
+        public CustomRenderTarget(ICustomSkiaRenderTarget renderTarget)
+        {
+            _renderTarget = renderTarget;
+        }
+
+        public void Dispose()
+        {
+            _renderTarget.Dispose();
+        }
+
+        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
+        {
+            ICustomSkiaRenderSession session = _renderTarget.BeginRendering();
+
+            var nfo = new DrawingContextImpl.CreateInfo
+            {
+                GrContext = session.GrContext,
+                Canvas = session.Canvas,
+                Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
+                VisualBrushRenderer = visualBrushRenderer,
+                DisableTextLcdRendering = true
+            };
+
+            return new DrawingContextImpl(nfo, session);
+        }
+    }
+}

+ 6 - 1
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@@ -9,6 +9,7 @@ using System.Threading;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering;
+using Avalonia.Rendering.SceneGraph;
 using Avalonia.Rendering.Utilities;
 using Avalonia.Utilities;
 using Avalonia.Visuals.Media.Imaging;
@@ -19,7 +20,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia based drawing context.
     /// </summary>
-    public class DrawingContextImpl : IDrawingContextImpl
+    internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl
     {
         private IDisposable[] _disposables;
         private readonly Vector _dpi;
@@ -99,6 +100,8 @@ namespace Avalonia.Skia
         /// </summary>
         public SKCanvas Canvas { get; }
 
+        SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas;
+
         /// <inheritdoc />
         public void Clear(Color color)
         {
@@ -296,6 +299,8 @@ namespace Avalonia.Skia
             Canvas.Restore();
         }
 
+        public void Custom(ICustomDrawOperation custom) => custom.Render(this);
+
         /// <inheritdoc />
         public void PushOpacityMask(IBrush mask, Rect bounds)
         {

+ 25 - 0
src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs

@@ -0,0 +1,25 @@
+// 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 SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// A Skia implementation of a <see cref="Avalonia.Media.EllipseGeometry"/>.
+    /// </summary>
+    internal class EllipseGeometryImpl : GeometryImpl
+    {
+        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;
+        }
+    }
+}

+ 1 - 1
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia formatted text implementation.
     /// </summary>
-    public class FormattedTextImpl : IFormattedTextImpl
+    internal class FormattedTextImpl : IFormattedTextImpl
     {
         public FormattedTextImpl(
             string text,

+ 1 - 1
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia render target that renders to a framebuffer surface. No gpu acceleration available.
     /// </summary>
-    public class FramebufferRenderTarget : IRenderTarget
+    internal class FramebufferRenderTarget : IRenderTarget
     {
         private readonly IFramebufferPlatformSurface _platformSurface;
         private SKImageInfo _currentImageInfo;

+ 1 - 1
src/Skia/Avalonia.Skia/GeometryImpl.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// A Skia implementation of <see cref="IGeometryImpl"/>.
     /// </summary>
-    public abstract class GeometryImpl : IGeometryImpl
+    internal abstract class GeometryImpl : IGeometryImpl
     {
         private PathCache _pathCache;
         

+ 1 - 1
src/Skia/Avalonia.Skia/GlRenderTarget.cs

@@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
 
 namespace Avalonia.Skia
 {
-    public class GlRenderTarget : IRenderTarget
+    internal class GlRenderTarget : IRenderTarget
     {
         private readonly GRContext _grContext;
         private IGlPlatformSurfaceRenderTarget _surface;

+ 26 - 0
src/Skia/Avalonia.Skia/ICustomSkiaGpu.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 System.Collections.Generic;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// Custom Skia gpu instance.
+    /// </summary>
+    public interface ICustomSkiaGpu
+    {
+        /// <summary>
+        /// Skia GrContext used.
+        /// </summary>
+        GRContext GrContext { get; }
+
+        /// <summary>
+        /// Attempts to create custom render target from given surfaces.
+        /// </summary>
+        /// <param name="surfaces">Surfaces.</param>
+        /// <returns>Created render target or <see langword="null"/> if it fails.</returns>
+        ICustomSkiaRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
+    }
+}

+ 29 - 0
src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs

@@ -0,0 +1,29 @@
+// 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 SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// Custom render session for Skia render target.
+    /// </summary>
+    public interface ICustomSkiaRenderSession : IDisposable
+    {
+        /// <summary>
+        /// GrContext used by this session.
+        /// </summary>
+        GRContext GrContext { get; }
+
+        /// <summary>
+        /// Canvas that will be used to render.
+        /// </summary>
+        SKCanvas Canvas { get; }
+
+        /// <summary>
+        /// Scaling factor.
+        /// </summary>
+        double ScaleFactor { get; }
+    }
+}

+ 19 - 0
src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs

@@ -0,0 +1,19 @@
+// 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;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// Custom Skia render target.
+    /// </summary>
+    public interface ICustomSkiaRenderTarget : IDisposable
+    {
+        /// <summary>
+        /// Start rendering to this render target.
+        /// </summary>
+        /// <returns></returns>
+        ICustomSkiaRenderSession BeginRendering();
+    }
+}

+ 10 - 0
src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs

@@ -0,0 +1,10 @@
+using Avalonia.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    public interface ISkiaDrawingContextImpl : IDrawingContextImpl
+    {
+        SKCanvas SkCanvas { get; }
+    }
+}

+ 1 - 1
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Immutable Skia bitmap.
     /// </summary>
-    public class ImmutableBitmap : IDrawableBitmapImpl
+    internal class ImmutableBitmap : IDrawableBitmapImpl
     {
         private readonly SKImage _image;
 

+ 29 - 0
src/Skia/Avalonia.Skia/LineGeometryImpl.cs

@@ -0,0 +1,29 @@
+// 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 SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// A Skia implementation of a <see cref="Avalonia.Media.LineGeometry"/>.
+    /// </summary>
+    internal class LineGeometryImpl : GeometryImpl
+    {
+        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)));
+        }
+    }
+}

+ 32 - 6
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@@ -15,14 +15,25 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia platform render interface.
     /// </summary>
-    public class PlatformRenderInterface : IPlatformRenderInterface
+    internal class PlatformRenderInterface : IPlatformRenderInterface
     {
+        private readonly ICustomSkiaGpu _customSkiaGpu;
+
         private GRContext GrContext { get; }
 
         public IEnumerable<string> InstalledFontNames => SKFontManager.Default.FontFamilies;
 
-        public PlatformRenderInterface()
+        public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu)
         {
+            if (customSkiaGpu != null)
+            {
+                _customSkiaGpu = customSkiaGpu;
+
+                GrContext = _customSkiaGpu.GrContext;
+
+                return;
+            }
+
             var gl = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
             if (gl != null)
             {
@@ -32,12 +43,11 @@ namespace Avalonia.Skia
                     ? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
                     : GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
                 {
-                    
                     GrContext = GRContext.Create(GRBackend.OpenGL, iface);
                 }
             }
         }
-        
+
         /// <inheritdoc />
         public IFormattedTextImpl CreateFormattedText(
             string text,
@@ -50,6 +60,12 @@ namespace Avalonia.Skia
             return new FormattedTextImpl(text, typeface, textAlignment, wrapping, constraint, spans);
         }
 
+        public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
+
+        public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
+
+        public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
+
         /// <inheritdoc />
         public IStreamGeometryImpl CreateStreamGeometry()
         {
@@ -98,13 +114,23 @@ namespace Avalonia.Skia
                 DisableTextLcdRendering = false,
                 GrContext = GrContext
             };
-            
+
             return new SurfaceRenderTarget(createInfo);
         }
 
         /// <inheritdoc />
-        public virtual IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
+        public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
         {
+            if (_customSkiaGpu != null)
+            {
+                ICustomSkiaRenderTarget customRenderTarget = _customSkiaGpu.TryCreateRenderTarget(surfaces);
+
+                if (customRenderTarget != null)
+                {
+                    return new CustomRenderTarget(customRenderTarget);
+                }
+            }
+
             foreach (var surface in surfaces)
             {
                 if (surface is IGlPlatformSurface glSurface && GrContext != null)

+ 25 - 0
src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs

@@ -0,0 +1,25 @@
+// 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 SkiaSharp;
+
+namespace Avalonia.Skia
+{
+    /// <summary>
+    /// A Skia implementation of a <see cref="Avalonia.Media.RectangleGeometry"/>.
+    /// </summary>
+    internal class RectangleGeometryImpl : GeometryImpl
+    {
+        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;
+        }
+    }
+}

+ 3 - 2
src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs

@@ -20,8 +20,9 @@ namespace Avalonia
         /// <returns>Configure builder.</returns>
         public static T UseSkia<T>(this T builder) where T : AppBuilderBase<T>, new()
         {
-            builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize(), "Skia");
-            return builder;
+            return builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize(
+                AvaloniaLocator.Current.GetService<SkiaOptions>() ?? new SkiaOptions()),
+                "Skia");
         }
     }
 }

+ 19 - 0
src/Skia/Avalonia.Skia/SkiaOptions.cs

@@ -0,0 +1,19 @@
+// 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.Skia;
+
+namespace Avalonia
+{
+    /// <summary>
+    /// Options for Skia rendering subsystem.
+    /// </summary>
+    public class SkiaOptions
+    {
+        /// <summary>
+        /// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
+        /// </summary>
+        public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; }
+    }
+}

+ 8 - 2
src/Skia/Avalonia.Skia/SkiaPlatform.cs

@@ -15,8 +15,14 @@ namespace Avalonia.Skia
         /// </summary>
         public static void Initialize()
         {
-            var renderInterface = new PlatformRenderInterface();
-            
+            Initialize(new SkiaOptions());
+        }
+
+        public static void Initialize(SkiaOptions options)
+        {
+            var customGpu = options.CustomGpuFactory?.Invoke();
+            var renderInterface = new PlatformRenderInterface(customGpu);
+
             AvaloniaLocator.CurrentMutable
                 .Bind<IPlatformRenderInterface>().ToConstant(renderInterface);
         }

+ 1 - 1
src/Skia/Avalonia.Skia/StreamGeometryImpl.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// A Skia implementation of a <see cref="IStreamGeometryImpl"/>.
     /// </summary>
-    public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
+    internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
     {
         private Rect _bounds;
         private readonly SKPath _effectivePath;

+ 1 - 1
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia render target that writes to a surface.
     /// </summary>
-    public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
+    internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
     {
         private readonly SKSurface _surface;
         private readonly SKCanvas _canvas;

+ 1 - 1
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// A Skia implementation of a <see cref="ITransformedGeometryImpl"/>.
     /// </summary>
-    public class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
+    internal class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
     {
         /// <summary>
         ///  Initializes a new instance of the <see cref="TransformedGeometryImpl"/> class.

+ 1 - 1
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@@ -13,7 +13,7 @@ namespace Avalonia.Skia
     /// <summary>
     /// Skia based writeable bitmap.
     /// </summary>
-    public class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
+    internal class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
     {
         private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
         private readonly SKBitmap _bitmap;

+ 4 - 4
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 IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
+        public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
+        public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
+        public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
 
         public IBitmapImpl LoadBitmap(string fileName)
         {

+ 3 - 0
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering;
+using Avalonia.Rendering.SceneGraph;
 using Avalonia.Utilities;
 using SharpDX;
 using SharpDX.Direct2D1;
@@ -508,5 +509,7 @@ namespace Avalonia.Direct2D1.Media
         {
             PopLayer();
         }
+        
+        public void Custom(ICustomDrawOperation custom) => custom.Render(this);
     }
 }

+ 27 - 0
src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.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 SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+    /// <summary>
+    /// A Direct2D implementation of a <see cref="Avalonia.Media.EllipseGeometry"/>.
+    /// </summary>
+    internal class EllipseGeometryImpl : GeometryImpl
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
+        /// </summary>
+        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);
+        }
+    }
+}

+ 1 - 1
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@@ -9,7 +9,7 @@ using DWrite = SharpDX.DirectWrite;
 
 namespace Avalonia.Direct2D1.Media
 {
-    public class FormattedTextImpl : IFormattedTextImpl
+    internal class FormattedTextImpl : IFormattedTextImpl
     {
         public FormattedTextImpl(
             string text,

+ 27 - 0
src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.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 SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+    /// <summary>
+    /// A Direct2D implementation of a <see cref="Avalonia.Media.LineGeometry"/>.
+    /// </summary>
+    internal class LineGeometryImpl : StreamGeometryImpl
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
+        /// </summary>
+        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();
+            }
+        }
+    }
+}

+ 26 - 0
src/Windows/Avalonia.Direct2D1/Media/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 SharpDX.Direct2D1;
+
+namespace Avalonia.Direct2D1.Media
+{
+    /// <summary>
+    /// A Direct2D implementation of a <see cref="Avalonia.Media.RectangleGeometry"/>.
+    /// </summary>
+    internal class RectangleGeometryImpl : GeometryImpl
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
+        /// </summary>
+        public RectangleGeometryImpl(Rect rect)
+            : base(CreateGeometry(rect))
+        {
+        }
+
+        private static Geometry CreateGeometry(Rect rect)
+        {
+            return new RectangleGeometry(Direct2D1Platform.Direct2D1Factory, rect.ToDirect2D());
+        }
+    }
+}

+ 1 - 1
src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs

@@ -50,7 +50,7 @@ namespace Avalonia.Win32.Interop.Wpf
                 {
                     _resource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
                     
-                    Target = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>(), surface,
+                    Target = new RenderTarget(Direct2D1Platform.Direct2D1Factory, surface,
                         new RenderTargetProperties
                         {
                             DpiX = (float) dpi.X,

+ 45 - 0
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -292,6 +292,51 @@ namespace Avalonia.Controls.UnitTests
             }
         }
 
+        [Fact]
+        public void Calling_Show_On_Closed_Window_Should_Throw()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var windowImpl = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
+                var target = new Window(windowImpl);
+
+                target.Show();
+                target.Close();
+
+                var openedRaised = false;
+                target.Opened += (s, e) => openedRaised = true;
+
+                var ex = Assert.Throws<InvalidOperationException>(() => target.Show());
+                Assert.Equal("Cannot re-show a closed window.", ex.Message);
+                Assert.False(openedRaised);
+            }
+        }
+
+        [Fact]
+        public async Task Calling_ShowDialog_On_Closed_Window_Should_Throw()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var parent = new Mock<IWindowImpl>();
+                var windowImpl = new Mock<IWindowImpl>();
+                windowImpl.SetupProperty(x => x.Closed);
+                windowImpl.Setup(x => x.Scaling).Returns(1);
+
+                var target = new Window(windowImpl.Object);
+                var task = target.ShowDialog<bool>(parent.Object);
+
+                windowImpl.Object.Closed();
+                await task;
+
+                var openedRaised = false;
+                target.Opened += (s, e) => openedRaised = true;
+
+                var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent.Object));
+                Assert.Equal("Cannot re-show a closed window.", ex.Message);
+                Assert.False(openedRaised);
+            }
+        }
+
         [Fact]
         public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
         {

+ 1 - 0
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@@ -11,6 +11,7 @@ using DynamicData;
 using Xunit;
 using Splat;
 using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
 
 namespace Avalonia 
 {

+ 1 - 0
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@@ -14,6 +14,7 @@ using Avalonia.Markup.Xaml;
 using System.ComponentModel;
 using System.Threading.Tasks;
 using System.Reactive;
+using Avalonia.ReactiveUI;
 
 namespace Avalonia
 {

+ 15 - 0
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@@ -22,6 +22,21 @@ namespace Avalonia.UnitTests
             return Mock.Of<IFormattedTextImpl>();
         }
 
+        public IGeometryImpl CreateEllipseGeometry(Rect rect)
+        {
+            return Mock.Of<IGeometryImpl>();
+        }
+
+        public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
+        {
+            return Mock.Of<IGeometryImpl>();
+        }
+
+        public IGeometryImpl CreateRectangleGeometry(Rect rect)
+        {
+            return Mock.Of<IGeometryImpl>();
+        }
+
         public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
         {
             return Mock.Of<IRenderTarget>();

+ 15 - 0
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@@ -56,6 +56,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
             throw new NotImplementedException();
         }
 
+        public IGeometryImpl CreateEllipseGeometry(Rect rect)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IGeometryImpl CreateRectangleGeometry(Rect rect)
+        {
+            throw new NotImplementedException();
+        }
+
         class MockStreamGeometry : IStreamGeometryImpl
         {
             private MockStreamGeometryContext _impl = new MockStreamGeometryContext();