소스 검색

Make IGeometryImpl immutable.

As it will be shared with the UI thread and the render thread.
Steven Kirk 9 년 전
부모
커밋
c46ca88b97

+ 7 - 4
src/Avalonia.Visuals/Media/Geometry.cs

@@ -22,10 +22,7 @@ namespace Avalonia.Media
         /// </summary>
         static Geometry()
         {
-            TransformProperty.Changed.Subscribe(x =>
-            {
-                ((Geometry)x.Sender).PlatformImpl.Transform = ((Transform)x.NewValue).Value;
-            });
+            TransformProperty.Changed.AddClassHandler<Geometry>(x => x.TransformChanged);
         }
 
         /// <summary>
@@ -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);
+        }
     }
 }

+ 9 - 2
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@@ -16,9 +16,9 @@ namespace Avalonia.Platform
         Rect Bounds { get; }
 
         /// <summary>
-        /// Gets or sets a transform to apply to the geometry.
+        /// Gets the transform to applied to the geometry.
         /// </summary>
-        Matrix Transform { get; set; }
+        Matrix Transform { get; }
 
         /// <summary>
         /// Gets the geometry's bounding rectangle with the specified stroke thickness.
@@ -41,5 +41,12 @@ namespace Avalonia.Platform
         /// <param name="point">The point.</param>
         /// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
         bool StrokeContains(Pen pen, Point point);
+
+        /// <summary>
+        /// Makes a clone of the geometry with the specified transform.
+        /// </summary>
+        /// <param name="transform">The transform.</param>
+        /// <returns>The cloned geometry.</returns>
+        IGeometryImpl WithTransform(Matrix transform);
     }
 }

+ 10 - 10
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);
         }
+
+        /// <inheritdoc/>
+        public IGeometryImpl WithTransform(Matrix transform)
+        {
+            var result = (StreamGeometryImpl)Clone();
+            result.Transform = transform;
+            return result;
+        }
     }
 }

+ 5 - 8
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()

+ 1 - 1
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);

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

+ 2 - 1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@@ -65,15 +65,16 @@
     <Compile Include="Media\BrushImpl.cs" />
     <Compile Include="Media\BrushWrapper.cs" />
     <Compile Include="Media\DrawingContextImpl.cs" />
+    <Compile Include="Media\GeometryImpl.cs" />
     <Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
     <Compile Include="Media\Imaging\BitmapImpl.cs" />
     <Compile Include="Media\RadialGradientBrushImpl.cs" />
     <Compile Include="Media\LinearGradientBrushImpl.cs" />
     <Compile Include="Media\AvaloniaTextRenderer.cs" />
+    <Compile Include="Media\TransformedGeometryImpl.cs" />
     <Compile Include="Media\TileBrushImpl.cs" />
     <Compile Include="Media\SolidColorBrushImpl.cs" />
     <Compile Include="Media\StreamGeometryContextImpl.cs" />
-    <Compile Include="Media\GeometryImpl.cs" />
     <Compile Include="Media\StreamGeometryImpl.cs" />
     <Compile Include="Media\FormattedTextImpl.cs" />
     <Compile Include="PrimitiveExtensions.cs" />

+ 26 - 64
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
     /// </summary>
     public abstract class GeometryImpl : IGeometryImpl
     {
-        private TransformedGeometry _transformed;
-
-        /// <summary>
-        /// Gets the geometry's bounding rectangle.
-        /// </summary>
-        public abstract Rect Bounds
-        {
-            get;
-        }
-
-        /// <summary>
-        /// Gets the geomentry without any transforms applied.
-        /// </summary>
-        public abstract Geometry DefiningGeometry
+        public GeometryImpl(Geometry geometry)
         {
-            get;
+            Geometry = geometry;
         }
 
-        /// <summary>
-        /// Gets the Direct2D <see cref="Geometry"/>.
-        /// </summary>
-        public Geometry Geometry => _transformed ?? DefiningGeometry;
+        /// <inheritdoc/>
+        public Rect Bounds => Geometry.GetBounds().ToAvalonia();
 
-        /// <summary>
-        /// Gets or sets the transform for the geometry.
-        /// </summary>
-        public Matrix Transform
-        {
-            get
-            {
-                return _transformed != null ?
-                    _transformed.Transform.ToAvalonia() :
-                    Matrix.Identity;
-            }
+        /// <inheritdoc/>
+        public Geometry Geometry { get; }
 
-            set
-            {
-                if (value != Transform)
-                {
-                    if (_transformed != null)
-                    {
-                        _transformed.Dispose();
-                        _transformed = null;
-                    }
+        /// <inheritdoc/>
+        public virtual Matrix Transform => Matrix.Identity;
 
-                    if (!value.IsIdentity)
-                    {
-                        Factory factory = AvaloniaLocator.Current.GetService<Factory>();
-                        _transformed = new TransformedGeometry(
-                            factory,
-                            DefiningGeometry,
-                            value.ToDirect2D());
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the geometry's bounding rectangle with the specified stroke thickness.
-        /// </summary>
-        /// <param name="strokeThickness">The stroke thickness.</param>
-        /// <returns>The bounding rectangle.</returns>
+        /// <inheritdoc/>
         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();
         }
 
+        /// <inheritdoc/>
         public bool FillContains(Point point)
         {
             return Geometry.FillContainsPoint(point.ToSharpDX());
         }
 
+        /// <inheritdoc/>
         public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
         {
             return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)pen.Thickness);
         }
+
+        /// <inheritdoc/>
+        public IGeometryImpl WithTransform(Matrix transform)
+        {
+            var factory = AvaloniaLocator.Current.GetService<Factory>();
+            return new TransformedGeometryImpl(
+                new TransformedGeometry(
+                    factory,
+                    GetSourceGeometry(),
+                    transform.ToDirect2D()));
+        }
+
+        protected virtual Geometry GetSourceGeometry() => Geometry;
     }
 }

+ 11 - 23
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
     /// </summary>
     public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
     {
-        private readonly PathGeometry _path;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
         /// </summary>
         public StreamGeometryImpl()
+            : base(CreateGeometry())
         {
-            Factory factory = AvaloniaLocator.Current.GetService<Factory>();
-            _path = new PathGeometry(factory);
         }
 
         /// <summary>
@@ -28,39 +24,31 @@ namespace Avalonia.Direct2D1.Media
         /// </summary>
         /// <param name="geometry">An existing Direct2D <see cref="PathGeometry"/>.</param>
         protected StreamGeometryImpl(PathGeometry geometry)
+            : base(geometry)
         {
-            _path = geometry;
         }
 
         /// <inheritdoc/>
-        public override Rect Bounds => _path.GetWidenedBounds(0).ToAvalonia();
-
-        /// <inheritdoc/>
-        public override D2DGeometry DefiningGeometry => _path;
-
-        /// <summary>
-        /// Clones the geometry.
-        /// </summary>
-        /// <returns>A cloned geometry.</returns>
         public IStreamGeometryImpl Clone()
         {
             Factory factory = AvaloniaLocator.Current.GetService<Factory>();
             var result = new PathGeometry(factory);
             var sink = result.Open();
-            _path.Stream(sink);
+            ((PathGeometry)Geometry).Stream(sink);
             sink.Close();
             return new StreamGeometryImpl(result);
         }
 
-        /// <summary>
-        /// Opens the geometry to start defining it.
-        /// </summary>
-        /// <returns>
-        /// An <see cref="Avalonia.Platform.IStreamGeometryContextImpl"/> which can be used to define the geometry.
-        /// </returns>
+        /// <inheritdoc/>
         public IStreamGeometryContextImpl Open()
         {
-            return new StreamGeometryContextImpl(_path.Open());
+            return new StreamGeometryContextImpl(((PathGeometry)Geometry).Open());
+        }
+
+        private static Geometry CreateGeometry()
+        {
+            Factory factory = AvaloniaLocator.Current.GetService<Factory>();
+            return new PathGeometry(factory);
         }
     }
 }

+ 24 - 0
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
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
+        /// </summary>
+        /// <param name="geometry">An existing Direct2D <see cref="TransformedGeometry"/>.</param>
+        public TransformedGeometryImpl(TransformedGeometry geometry)
+            : base(geometry)
+        {
+        }
+
+        /// <inheritdoc/>
+        public override Matrix Transform => ((TransformedGeometry)Geometry).Transform.ToAvalonia();
+
+        protected override Geometry GetSourceGeometry() => ((TransformedGeometry)Geometry).SourceGeometry;
+    }
+}

+ 5 - 0
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<Point> points = new List<Point>();