Browse Source

Merge branch 'master' into fixes/2420-slider-leak

pull/2438/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
72cd70b767
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      build/SharedVersion.props
  2. 8
      nukebuild/Build.cs
  3. 1
      samples/BindingDemo/App.xaml.cs
  4. 1
      samples/ControlCatalog.Desktop/Program.cs
  5. 1
      samples/ControlCatalog.NetCore/Program.cs
  6. 1
      samples/RenderDemo/App.xaml.cs
  7. 3
      samples/RenderDemo/MainWindow.xaml
  8. 119
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  9. 1
      samples/VirtualizationDemo/Program.cs
  10. 2
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  11. 4
      src/Avalonia.Controls/ListBox.cs
  12. 8
      src/Avalonia.Controls/TreeView.cs
  13. 14
      src/Avalonia.Controls/Window.cs
  14. 7
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  15. 8
      src/Avalonia.ReactiveUI/Attributes.cs
  16. 18
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  17. 2
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  18. 2
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  19. 8
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  20. 1
      src/Avalonia.Styling/StyledElement.cs
  21. 7
      src/Avalonia.Visuals/Media/DrawingContext.cs
  22. 31
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  23. 10
      src/Avalonia.Visuals/Media/LineGeometry.cs
  24. 28
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  25. 7
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  26. 22
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  27. 39
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  28. 9
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  29. 3
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  30. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  31. 42
      src/Skia/Avalonia.Skia/CustomRenderTarget.cs
  32. 7
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  33. 25
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  34. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  35. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  36. 2
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  37. 2
      src/Skia/Avalonia.Skia/GlRenderTarget.cs
  38. 26
      src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs
  39. 29
      src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs
  40. 19
      src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs
  41. 10
      src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
  42. 2
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  43. 29
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  44. 38
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  45. 25
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  46. 5
      src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs
  47. 19
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  48. 10
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  49. 2
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  50. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  51. 2
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  52. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  53. 8
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  54. 3
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  55. 27
      src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs
  56. 2
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  57. 27
      src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs
  58. 26
      src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs
  59. 2
      src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
  60. 45
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  61. 1
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  62. 1
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  63. 15
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  64. 15
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

6
build/SharedVersion.props

@ -2,8 +2,8 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.7.1</Version>
<Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
<Version>0.8.1</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
@ -11,4 +11,4 @@
<NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
</Project>

8
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
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
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
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
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
samples/RenderDemo/MainWindow.xaml

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

119
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
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
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>

4
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>

8
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;

14
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>

7
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
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")]

18
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

2
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}"/>

2
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}"/>

8
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
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
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>

31
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);
}
}
}

10
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);
}
}
}

28
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
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
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
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
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)

3
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;

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975
Subproject commit 7452b23169e4948907fa10e2c115b672897d0e04

42
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);
}
}
}

7
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
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;
}
}
}

2
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,

2
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;

2
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;

2
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
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
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
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
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; }
}
}

2
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
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)));
}
}
}

38
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
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;
}
}
}

5
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
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; }
}
}

10
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);
}

2
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;

2
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;

2
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.

2
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;

8
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
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
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);
}
}
}

2
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
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
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());
}
}
}

2
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
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
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
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
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
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();

Loading…
Cancel
Save