Browse Source

Make IGeometryImpl immutable.

As it will be shared with the UI thread and the render thread.
scenegraph-after-breakage
Steven Kirk 10 years ago
parent
commit
c46ca88b97
  1. 11
      src/Avalonia.Visuals/Media/Geometry.cs
  2. 11
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  3. 20
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  4. 13
      src/Skia/Avalonia.Skia.Android/RenderTarget.cs
  5. 2
      src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs
  6. 49
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  7. 3
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  8. 90
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  9. 34
      src/Windows/Avalonia.Direct2D1/Media/StreamGeometryImpl.cs
  10. 24
      src/Windows/Avalonia.Direct2D1/Media/TransformedGeometryImpl.cs
  11. 5
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

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

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

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

13
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()

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

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

3
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" />

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

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

Loading…
Cancel
Save