From c46ca88b974577d38f701c3be56a9a33077954f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 18 Nov 2016 17:20:29 +0100 Subject: [PATCH] Make IGeometryImpl immutable. As it will be shared with the UI thread and the render thread. --- src/Avalonia.Visuals/Media/Geometry.cs | 11 ++- .../Platform/IGeometryImpl.cs | 11 ++- .../Media/StreamGeometryImpl.cs | 20 ++--- .../Avalonia.Skia.Android/RenderTarget.cs | 13 ++- .../Avalonia.Skia.Android/SkiaRenderView.cs | 2 +- src/Skia/Avalonia.Skia/StreamGeometryImpl.cs | 49 ++++------ .../Avalonia.Direct2D1.csproj | 3 +- .../Avalonia.Direct2D1/Media/GeometryImpl.cs | 90 ++++++------------- .../Media/StreamGeometryImpl.cs | 34 +++---- .../Media/TransformedGeometryImpl.cs | 24 +++++ .../VisualTree/MockRenderInterface.cs | 5 ++ 11 files changed, 119 insertions(+), 143 deletions(-) create mode 100644 src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs index bbed5a9e64..591c7d0468 100644 --- a/src/Avalonia.Visuals/Media/Geometry.cs +++ b/src/Avalonia.Visuals/Media/Geometry.cs @@ -22,10 +22,7 @@ namespace Avalonia.Media /// static Geometry() { - TransformProperty.Changed.Subscribe(x => - { - ((Geometry)x.Sender).PlatformImpl.Transform = ((Transform)x.NewValue).Value; - }); + TransformProperty.Changed.AddClassHandler(x => x.TransformChanged); } /// @@ -87,5 +84,11 @@ namespace Avalonia.Media { return PlatformImpl.StrokeContains(pen, point); } + + private void TransformChanged(AvaloniaPropertyChangedEventArgs e) + { + var transform = (Transform)e.NewValue; + PlatformImpl = PlatformImpl.WithTransform(transform.Value); + } } } diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 34a7bb1394..9dc00ac14f 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -16,9 +16,9 @@ namespace Avalonia.Platform Rect Bounds { get; } /// - /// Gets or sets a transform to apply to the geometry. + /// Gets the transform to applied to the geometry. /// - Matrix Transform { get; set; } + Matrix Transform { get; } /// /// Gets the geometry's bounding rectangle with the specified stroke thickness. @@ -41,5 +41,12 @@ namespace Avalonia.Platform /// The point. /// true if the geometry contains the point; otherwise, false. bool StrokeContains(Pen pen, Point point); + + /// + /// Makes a clone of the geometry with the specified transform. + /// + /// The transform. + /// The cloned geometry. + IGeometryImpl WithTransform(Matrix transform); } } diff --git a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs index b7f8b698c9..18d0060bff 100644 --- a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs @@ -35,19 +35,11 @@ namespace Avalonia.Cairo.Media private readonly StreamGeometryContextImpl _impl; private Matrix _transform = Matrix.Identity; + public Matrix Transform { get { return _transform; } - set - { - if (value != Transform) - { - if (!value.IsIdentity) - { - _transform = value; - } - } - } + private set { _transform = value; } } public FillRule FillRule { get; set; } @@ -77,5 +69,13 @@ namespace Avalonia.Cairo.Media { return _impl.StrokeContains(pen, point); } + + /// + public IGeometryImpl WithTransform(Matrix transform) + { + var result = (StreamGeometryImpl)Clone(); + result.Transform = transform; + return result; + } } } diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 59ad3b9fbb..18045e1a5a 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -11,11 +11,9 @@ namespace Avalonia.Skia { public SKSurface Surface { get; protected set; } - public virtual DrawingContext CreateDrawingContext() + public virtual IDrawingContextImpl CreateDrawingContext() { - return - new DrawingContext( - new DrawingContextImpl(Surface.Canvas)); + return new DrawingContextImpl(Surface.Canvas); } public void Dispose() @@ -68,8 +66,9 @@ namespace Avalonia.Skia h = surfaceView.Height; } - public override DrawingContext CreateDrawingContext() + public override IDrawingContextImpl CreateDrawingContext() { + base.CreateDrawingContext(); FixSize(); var canvas = Surface.Canvas; @@ -78,9 +77,7 @@ namespace Avalonia.Skia canvas.Clear(SKColors.Red); canvas.ResetMatrix(); - return - new DrawingContext( - new WindowDrawingContextImpl(this)); + return new WindowDrawingContextImpl(this); } public void Present() diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs index 62d8e7bd18..f176e9fb06 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs @@ -30,7 +30,7 @@ namespace Avalonia.Skia.Android if (_renderTarget == null) return; using (var ctx = _renderTarget.CreateDrawingContext()) - OnRender(ctx); + OnRender(new DrawingContext(ctx)); } protected abstract void OnRender(DrawingContext ctx); diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 420d11b965..aebf404c66 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -13,11 +13,10 @@ namespace Avalonia.Skia class StreamGeometryImpl : IStreamGeometryImpl { SKPath _path; - SKPath _transformedPath; private Matrix _transform = Matrix.Identity; - public SKPath EffectivePath => (_transformedPath ?? _path); + public SKPath EffectivePath => _path; public Rect GetRenderBounds(double strokeThickness) { @@ -30,32 +29,6 @@ namespace Avalonia.Skia public Matrix Transform { get { return _transform; } - set - { - if (_transform == value) - return; - - _transform = value; - ApplyTransform(); - } - } - - void ApplyTransform() - { - if (_path == null) - return; - - if (_transformedPath != null) - { - _transformedPath.Dispose(); - _transformedPath = null; - } - - if (!Transform.IsIdentity) - { - _transformedPath = new SKPath(_path); - _transformedPath.Transform(Transform.ToSKMatrix()); - } } public IStreamGeometryImpl Clone() @@ -63,7 +36,6 @@ namespace Avalonia.Skia return new StreamGeometryImpl { _path = _path?.Clone(), - _transformedPath = _transformedPath?.Clone(), _transform = Transform, Bounds = Bounds }; @@ -91,6 +63,24 @@ namespace Avalonia.Skia return GetRenderBounds(0).Contains(point); } + public IGeometryImpl WithTransform(Matrix transform) + { + var result = (StreamGeometryImpl)Clone(); + + if (result.Transform != Matrix.Identity) + { + result._path.Transform(result.Transform.Invert().ToSKMatrix()); + } + + if (transform != Matrix.Identity) + { + result._path.Transform(transform.ToSKMatrix()); + } + + result._transform = transform; + return result; + } + class StreamContext : IStreamGeometryContextImpl { private readonly StreamGeometryImpl _geometryImpl; @@ -107,7 +97,6 @@ namespace Avalonia.Skia { SKRect rc; _path.GetBounds(out rc); - _geometryImpl.ApplyTransform(); _geometryImpl.Bounds = rc.ToAvaloniaRect(); } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index eeae2bff1c..f99efa1678 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -65,15 +65,16 @@ + + - diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index dda2f2af89..6245289cc9 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using Avalonia.Platform; using SharpDX.Direct2D1; @@ -11,88 +12,49 @@ namespace Avalonia.Direct2D1.Media /// public abstract class GeometryImpl : IGeometryImpl { - private TransformedGeometry _transformed; - - /// - /// Gets the geometry's bounding rectangle. - /// - public abstract Rect Bounds - { - get; - } - - /// - /// Gets the geomentry without any transforms applied. - /// - public abstract Geometry DefiningGeometry + public GeometryImpl(Geometry geometry) { - get; + Geometry = geometry; } - /// - /// Gets the Direct2D . - /// - public Geometry Geometry => _transformed ?? DefiningGeometry; + /// + public Rect Bounds => Geometry.GetBounds().ToAvalonia(); - /// - /// Gets or sets the transform for the geometry. - /// - public Matrix Transform - { - get - { - return _transformed != null ? - _transformed.Transform.ToAvalonia() : - Matrix.Identity; - } + /// + public Geometry Geometry { get; } - set - { - if (value != Transform) - { - if (_transformed != null) - { - _transformed.Dispose(); - _transformed = null; - } + /// + public virtual Matrix Transform => Matrix.Identity; - if (!value.IsIdentity) - { - Factory factory = AvaloniaLocator.Current.GetService(); - _transformed = new TransformedGeometry( - factory, - DefiningGeometry, - value.ToDirect2D()); - } - } - } - } - - /// - /// Gets the geometry's bounding rectangle with the specified stroke thickness. - /// - /// The stroke thickness. - /// The bounding rectangle. + /// public Rect GetRenderBounds(double strokeThickness) { - if (_transformed != null) - { - return _transformed.GetWidenedBounds((float)strokeThickness).ToAvalonia(); - } - else - { - return DefiningGeometry.GetWidenedBounds((float)strokeThickness).ToAvalonia(); - } + return Geometry.GetWidenedBounds((float)strokeThickness).ToAvalonia(); } + /// public bool FillContains(Point point) { return Geometry.FillContainsPoint(point.ToSharpDX()); } + /// public bool StrokeContains(Avalonia.Media.Pen pen, Point point) { return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)pen.Thickness); } + + /// + public IGeometryImpl WithTransform(Matrix transform) + { + var factory = AvaloniaLocator.Current.GetService(); + return new TransformedGeometryImpl( + new TransformedGeometry( + factory, + GetSourceGeometry(), + transform.ToDirect2D())); + } + + protected virtual Geometry GetSourceGeometry() => Geometry; } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs index 213c4b5c03..a32426843f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs @@ -3,7 +3,6 @@ using Avalonia.Platform; using SharpDX.Direct2D1; -using D2DGeometry = SharpDX.Direct2D1.Geometry; namespace Avalonia.Direct2D1.Media { @@ -12,15 +11,12 @@ namespace Avalonia.Direct2D1.Media /// public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl { - private readonly PathGeometry _path; - /// /// Initializes a new instance of the class. /// public StreamGeometryImpl() + : base(CreateGeometry()) { - Factory factory = AvaloniaLocator.Current.GetService(); - _path = new PathGeometry(factory); } /// @@ -28,39 +24,31 @@ namespace Avalonia.Direct2D1.Media /// /// An existing Direct2D . protected StreamGeometryImpl(PathGeometry geometry) + : base(geometry) { - _path = geometry; } /// - public override Rect Bounds => _path.GetWidenedBounds(0).ToAvalonia(); - - /// - public override D2DGeometry DefiningGeometry => _path; - - /// - /// Clones the geometry. - /// - /// A cloned geometry. public IStreamGeometryImpl Clone() { Factory factory = AvaloniaLocator.Current.GetService(); var result = new PathGeometry(factory); var sink = result.Open(); - _path.Stream(sink); + ((PathGeometry)Geometry).Stream(sink); sink.Close(); return new StreamGeometryImpl(result); } - /// - /// Opens the geometry to start defining it. - /// - /// - /// An which can be used to define the geometry. - /// + /// public IStreamGeometryContextImpl Open() { - return new StreamGeometryContextImpl(_path.Open()); + return new StreamGeometryContextImpl(((PathGeometry)Geometry).Open()); + } + + private static Geometry CreateGeometry() + { + Factory factory = AvaloniaLocator.Current.GetService(); + return new PathGeometry(factory); } } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs new file mode 100644 index 0000000000..4043e180dc --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs @@ -0,0 +1,24 @@ +// 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 +{ + public class TransformedGeometryImpl : GeometryImpl + { + /// + /// Initializes a new instance of the class. + /// + /// An existing Direct2D . + public TransformedGeometryImpl(TransformedGeometry geometry) + : base(geometry) + { + } + + /// + public override Matrix Transform => ((TransformedGeometry)Geometry).Transform.ToAvalonia(); + + protected override Geometry GetSourceGeometry() => ((TransformedGeometry)Geometry).SourceGeometry; + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 5f033f4c53..713d55a14a 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -78,6 +78,11 @@ namespace Avalonia.Visuals.UnitTests.VisualTree return _impl; } + public IGeometryImpl WithTransform(Matrix transform) + { + return this; + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List();