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> /// </summary>
static Geometry() static Geometry()
{ {
TransformProperty.Changed.Subscribe(x => TransformProperty.Changed.AddClassHandler<Geometry>(x => x.TransformChanged);
{
((Geometry)x.Sender).PlatformImpl.Transform = ((Transform)x.NewValue).Value;
});
} }
/// <summary> /// <summary>
@ -87,5 +84,11 @@ namespace Avalonia.Media
{ {
return PlatformImpl.StrokeContains(pen, point); 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; } Rect Bounds { get; }
/// <summary> /// <summary>
/// Gets or sets a transform to apply to the geometry. /// Gets the transform to applied to the geometry.
/// </summary> /// </summary>
Matrix Transform { get; set; } Matrix Transform { get; }
/// <summary> /// <summary>
/// Gets the geometry's bounding rectangle with the specified stroke thickness. /// Gets the geometry's bounding rectangle with the specified stroke thickness.
@ -41,5 +41,12 @@ namespace Avalonia.Platform
/// <param name="point">The point.</param> /// <param name="point">The point.</param>
/// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
bool StrokeContains(Pen pen, Point point); 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 readonly StreamGeometryContextImpl _impl;
private Matrix _transform = Matrix.Identity; private Matrix _transform = Matrix.Identity;
public Matrix Transform public Matrix Transform
{ {
get { return _transform; } get { return _transform; }
set private set { _transform = value; }
{
if (value != Transform)
{
if (!value.IsIdentity)
{
_transform = value;
}
}
}
} }
public FillRule FillRule { get; set; } public FillRule FillRule { get; set; }
@ -77,5 +69,13 @@ namespace Avalonia.Cairo.Media
{ {
return _impl.StrokeContains(pen, point); 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 SKSurface Surface { get; protected set; }
public virtual DrawingContext CreateDrawingContext() public virtual IDrawingContextImpl CreateDrawingContext()
{ {
return return new DrawingContextImpl(Surface.Canvas);
new DrawingContext(
new DrawingContextImpl(Surface.Canvas));
} }
public void Dispose() public void Dispose()
@ -68,8 +66,9 @@ namespace Avalonia.Skia
h = surfaceView.Height; h = surfaceView.Height;
} }
public override DrawingContext CreateDrawingContext() public override IDrawingContextImpl CreateDrawingContext()
{ {
base.CreateDrawingContext();
FixSize(); FixSize();
var canvas = Surface.Canvas; var canvas = Surface.Canvas;
@ -78,9 +77,7 @@ namespace Avalonia.Skia
canvas.Clear(SKColors.Red); canvas.Clear(SKColors.Red);
canvas.ResetMatrix(); canvas.ResetMatrix();
return return new WindowDrawingContextImpl(this);
new DrawingContext(
new WindowDrawingContextImpl(this));
} }
public void Present() public void Present()

2
src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs

@ -30,7 +30,7 @@ namespace Avalonia.Skia.Android
if (_renderTarget == null) if (_renderTarget == null)
return; return;
using (var ctx = _renderTarget.CreateDrawingContext()) using (var ctx = _renderTarget.CreateDrawingContext())
OnRender(ctx); OnRender(new DrawingContext(ctx));
} }
protected abstract void OnRender(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 class StreamGeometryImpl : IStreamGeometryImpl
{ {
SKPath _path; SKPath _path;
SKPath _transformedPath;
private Matrix _transform = Matrix.Identity; private Matrix _transform = Matrix.Identity;
public SKPath EffectivePath => (_transformedPath ?? _path); public SKPath EffectivePath => _path;
public Rect GetRenderBounds(double strokeThickness) public Rect GetRenderBounds(double strokeThickness)
{ {
@ -30,32 +29,6 @@ namespace Avalonia.Skia
public Matrix Transform public Matrix Transform
{ {
get { return _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() public IStreamGeometryImpl Clone()
@ -63,7 +36,6 @@ namespace Avalonia.Skia
return new StreamGeometryImpl return new StreamGeometryImpl
{ {
_path = _path?.Clone(), _path = _path?.Clone(),
_transformedPath = _transformedPath?.Clone(),
_transform = Transform, _transform = Transform,
Bounds = Bounds Bounds = Bounds
}; };
@ -91,6 +63,24 @@ namespace Avalonia.Skia
return GetRenderBounds(0).Contains(point); 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 class StreamContext : IStreamGeometryContextImpl
{ {
private readonly StreamGeometryImpl _geometryImpl; private readonly StreamGeometryImpl _geometryImpl;
@ -107,7 +97,6 @@ namespace Avalonia.Skia
{ {
SKRect rc; SKRect rc;
_path.GetBounds(out rc); _path.GetBounds(out rc);
_geometryImpl.ApplyTransform();
_geometryImpl.Bounds = rc.ToAvaloniaRect(); _geometryImpl.Bounds = rc.ToAvaloniaRect();
} }

3
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -65,15 +65,16 @@
<Compile Include="Media\BrushImpl.cs" /> <Compile Include="Media\BrushImpl.cs" />
<Compile Include="Media\BrushWrapper.cs" /> <Compile Include="Media\BrushWrapper.cs" />
<Compile Include="Media\DrawingContextImpl.cs" /> <Compile Include="Media\DrawingContextImpl.cs" />
<Compile Include="Media\GeometryImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" /> <Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
<Compile Include="Media\Imaging\BitmapImpl.cs" /> <Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" /> <Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" /> <Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\AvaloniaTextRenderer.cs" /> <Compile Include="Media\AvaloniaTextRenderer.cs" />
<Compile Include="Media\TransformedGeometryImpl.cs" />
<Compile Include="Media\TileBrushImpl.cs" /> <Compile Include="Media\TileBrushImpl.cs" />
<Compile Include="Media\SolidColorBrushImpl.cs" /> <Compile Include="Media\SolidColorBrushImpl.cs" />
<Compile Include="Media\StreamGeometryContextImpl.cs" /> <Compile Include="Media\StreamGeometryContextImpl.cs" />
<Compile Include="Media\GeometryImpl.cs" />
<Compile Include="Media\StreamGeometryImpl.cs" /> <Compile Include="Media\StreamGeometryImpl.cs" />
<Compile Include="Media\FormattedTextImpl.cs" /> <Compile Include="Media\FormattedTextImpl.cs" />
<Compile Include="PrimitiveExtensions.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. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Platform; using Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
@ -11,88 +12,49 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
public abstract class GeometryImpl : IGeometryImpl public abstract class GeometryImpl : IGeometryImpl
{ {
private TransformedGeometry _transformed; public GeometryImpl(Geometry geometry)
/// <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
{ {
get; Geometry = geometry;
} }
/// <summary> /// <inheritdoc/>
/// Gets the Direct2D <see cref="Geometry"/>. public Rect Bounds => Geometry.GetBounds().ToAvalonia();
/// </summary>
public Geometry Geometry => _transformed ?? DefiningGeometry;
/// <summary> /// <inheritdoc/>
/// Gets or sets the transform for the geometry. public Geometry Geometry { get; }
/// </summary>
public Matrix Transform
{
get
{
return _transformed != null ?
_transformed.Transform.ToAvalonia() :
Matrix.Identity;
}
set /// <inheritdoc/>
{ public virtual Matrix Transform => Matrix.Identity;
if (value != Transform)
{
if (_transformed != null)
{
_transformed.Dispose();
_transformed = null;
}
if (!value.IsIdentity) /// <inheritdoc/>
{
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>
public Rect GetRenderBounds(double strokeThickness) public Rect GetRenderBounds(double strokeThickness)
{ {
if (_transformed != null) return Geometry.GetWidenedBounds((float)strokeThickness).ToAvalonia();
{
return _transformed.GetWidenedBounds((float)strokeThickness).ToAvalonia();
}
else
{
return DefiningGeometry.GetWidenedBounds((float)strokeThickness).ToAvalonia();
}
} }
/// <inheritdoc/>
public bool FillContains(Point point) public bool FillContains(Point point)
{ {
return Geometry.FillContainsPoint(point.ToSharpDX()); return Geometry.FillContainsPoint(point.ToSharpDX());
} }
/// <inheritdoc/>
public bool StrokeContains(Avalonia.Media.Pen pen, Point point) public bool StrokeContains(Avalonia.Media.Pen pen, Point point)
{ {
return Geometry.StrokeContainsPoint(point.ToSharpDX(), (float)pen.Thickness); 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 Avalonia.Platform;
using SharpDX.Direct2D1; using SharpDX.Direct2D1;
using D2DGeometry = SharpDX.Direct2D1.Geometry;
namespace Avalonia.Direct2D1.Media namespace Avalonia.Direct2D1.Media
{ {
@ -12,15 +11,12 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
{ {
private readonly PathGeometry _path;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class. /// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary> /// </summary>
public StreamGeometryImpl() public StreamGeometryImpl()
: base(CreateGeometry())
{ {
Factory factory = AvaloniaLocator.Current.GetService<Factory>();
_path = new PathGeometry(factory);
} }
/// <summary> /// <summary>
@ -28,39 +24,31 @@ namespace Avalonia.Direct2D1.Media
/// </summary> /// </summary>
/// <param name="geometry">An existing Direct2D <see cref="PathGeometry"/>.</param> /// <param name="geometry">An existing Direct2D <see cref="PathGeometry"/>.</param>
protected StreamGeometryImpl(PathGeometry geometry) protected StreamGeometryImpl(PathGeometry geometry)
: base(geometry)
{ {
_path = geometry;
} }
/// <inheritdoc/> /// <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() public IStreamGeometryImpl Clone()
{ {
Factory factory = AvaloniaLocator.Current.GetService<Factory>(); Factory factory = AvaloniaLocator.Current.GetService<Factory>();
var result = new PathGeometry(factory); var result = new PathGeometry(factory);
var sink = result.Open(); var sink = result.Open();
_path.Stream(sink); ((PathGeometry)Geometry).Stream(sink);
sink.Close(); sink.Close();
return new StreamGeometryImpl(result); return new StreamGeometryImpl(result);
} }
/// <summary> /// <inheritdoc/>
/// Opens the geometry to start defining it.
/// </summary>
/// <returns>
/// An <see cref="Avalonia.Platform.IStreamGeometryContextImpl"/> which can be used to define the geometry.
/// </returns>
public IStreamGeometryContextImpl Open() 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; return _impl;
} }
public IGeometryImpl WithTransform(Matrix transform)
{
return this;
}
class MockStreamGeometryContext : IStreamGeometryContextImpl class MockStreamGeometryContext : IStreamGeometryContextImpl
{ {
private List<Point> points = new List<Point>(); private List<Point> points = new List<Point>();

Loading…
Cancel
Save