Browse Source
There were a number of problems with `Geometry` and its subclasses and platform implementations. Fix these, for more details see the PR that this commit is a part of.pull/1349/head
24 changed files with 543 additions and 259 deletions
@ -0,0 +1,24 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Platform |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a geometry with a transform applied.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// An <see cref="ITransformedGeometryImpl"/> transforms a geometry without transforming its
|
|||
/// stroke thickness.
|
|||
/// </remarks>
|
|||
public interface ITransformedGeometryImpl : IGeometryImpl |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the source geometry that the <see cref="Transform"/> is applied to.
|
|||
/// </summary>
|
|||
IGeometryImpl SourceGeometry { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the applied transform.
|
|||
/// </summary>
|
|||
Matrix Transform { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
abstract class GeometryImpl : IGeometryImpl |
|||
{ |
|||
public abstract Rect Bounds { get; } |
|||
public abstract SKPath EffectivePath { get; } |
|||
public abstract void Dispose(); |
|||
public abstract bool FillContains(Point point); |
|||
public abstract Rect GetRenderBounds(Pen pen); |
|||
public abstract IGeometryImpl Intersect(IGeometryImpl geometry); |
|||
public abstract bool StrokeContains(Pen pen, Point point); |
|||
public abstract ITransformedGeometryImpl WithTransform(Matrix transform); |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Skia |
|||
{ |
|||
class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl |
|||
{ |
|||
public TransformedGeometryImpl(GeometryImpl source, Matrix transform) |
|||
{ |
|||
SourceGeometry = source; |
|||
Transform = transform; |
|||
EffectivePath = source.EffectivePath.Clone(); |
|||
EffectivePath.Transform(transform.ToSKMatrix()); |
|||
} |
|||
|
|||
public override SKPath EffectivePath { get; } |
|||
|
|||
public IGeometryImpl SourceGeometry { get; } |
|||
|
|||
public Matrix Transform { get; } |
|||
|
|||
public override Rect Bounds => SourceGeometry.Bounds.TransformToAABB(Transform); |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
} |
|||
|
|||
public override bool FillContains(Point point) |
|||
{ |
|||
// TODO: Not supported by SkiaSharp yet, so use expanded Rect
|
|||
return GetRenderBounds(0).Contains(point); |
|||
} |
|||
|
|||
public override Rect GetRenderBounds(Pen pen) |
|||
{ |
|||
return GetRenderBounds(pen.Thickness); |
|||
} |
|||
|
|||
public override IGeometryImpl Intersect(IGeometryImpl geometry) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
public override bool StrokeContains(Pen pen, Point point) |
|||
{ |
|||
// TODO: Not supported by SkiaSharp yet, so use expanded Rect
|
|||
return GetRenderBounds(0).Contains(point); |
|||
} |
|||
|
|||
public override ITransformedGeometryImpl WithTransform(Matrix transform) |
|||
{ |
|||
return new TransformedGeometryImpl(this, transform); |
|||
} |
|||
|
|||
public Rect GetRenderBounds(double strokeThickness) |
|||
{ |
|||
// TODO: Calculate properly.
|
|||
return Bounds.Inflate(strokeThickness); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Visuals.UnitTests.Media |
|||
{ |
|||
public class GeometryTests |
|||
{ |
|||
[Fact] |
|||
public void Changing_AffectsGeometry_Property_Causes_PlatformImpl_To_Be_Updated() |
|||
{ |
|||
var target = new TestGeometry(); |
|||
var platformImpl = target.PlatformImpl; |
|||
|
|||
target.Foo = true; |
|||
|
|||
Assert.NotSame(platformImpl, target.PlatformImpl); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_AffectsGeometry_Property_Causes_Changed_To_Be_Raised() |
|||
{ |
|||
var target = new TestGeometry(); |
|||
var raised = false; |
|||
|
|||
target.Changed += (s, e) => raised = true; |
|||
target.Foo = true; |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Old_PlatformImpl_Is_Disposed_When_Updated() |
|||
{ |
|||
var target = new TestGeometry(); |
|||
var platformImpl = target.PlatformImpl; |
|||
|
|||
target.Foo = true; |
|||
|
|||
Mock.Get(platformImpl).Verify(x => x.Dispose()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Setting_Transform_Causes_Changed_To_Be_Raised() |
|||
{ |
|||
var target = new TestGeometry(); |
|||
var raised = false; |
|||
|
|||
target.Changed += (s, e) => raised = true; |
|||
target.Transform = new RotateTransform(45); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Changing_Transform_Causes_Changed_To_Be_Raised() |
|||
{ |
|||
var transform = new RotateTransform(45); |
|||
var target = new TestGeometry { Transform = transform }; |
|||
var raised = false; |
|||
|
|||
target.Changed += (s, e) => raised = true; |
|||
transform.Angle = 90; |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Removing_Transform_Causes_Changed_To_Be_Raised() |
|||
{ |
|||
var transform = new RotateTransform(45); |
|||
var target = new TestGeometry { Transform = transform }; |
|||
var raised = false; |
|||
|
|||
target.Changed += (s, e) => raised = true; |
|||
target.Transform = null; |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Transform_Produces_Transformed_PlatformImpl() |
|||
{ |
|||
var target = new TestGeometry(); |
|||
var rotate = new RotateTransform(45); |
|||
|
|||
Assert.False(target.PlatformImpl is ITransformedGeometryImpl); |
|||
target.Transform = rotate; |
|||
Assert.True(target.PlatformImpl is ITransformedGeometryImpl); |
|||
rotate.Angle = 0; |
|||
Assert.False(target.PlatformImpl is ITransformedGeometryImpl); |
|||
} |
|||
|
|||
private class TestGeometry : Geometry |
|||
{ |
|||
public static readonly AvaloniaProperty<bool> FooProperty = |
|||
AvaloniaProperty.Register<TestGeometry, bool>(nameof(Foo)); |
|||
|
|||
static TestGeometry() |
|||
{ |
|||
AffectsGeometry(FooProperty); |
|||
} |
|||
|
|||
public bool Foo |
|||
{ |
|||
get => GetValue(FooProperty); |
|||
set => SetValue(FooProperty, value); |
|||
} |
|||
|
|||
public override Geometry Clone() |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
|
|||
protected override IGeometryImpl CreateDefiningGeometry() |
|||
{ |
|||
return Mock.Of<IGeometryImpl>( |
|||
x => x.WithTransform(It.IsAny<Matrix>()) == |
|||
Mock.Of<ITransformedGeometryImpl>(y => |
|||
y.SourceGeometry == x)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue