committed by
GitHub
35 changed files with 1124 additions and 457 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,18 @@ |
|||
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 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,59 @@ |
|||
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 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,194 @@ |
|||
// 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 Avalonia.Controls.Presenters; |
|||
using Avalonia.Layout; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests.Presenters |
|||
{ |
|||
public class ContentPresenterTests_Layout |
|||
{ |
|||
[Theory] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 0, 0, 100, 100)] |
|||
[InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 0, 0, 16, 100)] |
|||
[InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 84, 0, 16, 100)] |
|||
[InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 0, 16, 100)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 0, 0, 100, 16)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 0, 84, 100, 16)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 0, 42, 100, 16)] |
|||
public void Content_Alignment_Is_Applied_To_Child_Bounds( |
|||
HorizontalAlignment h, |
|||
VerticalAlignment v, |
|||
double expectedX, |
|||
double expectedY, |
|||
double expectedWidth, |
|||
double expectedHeight) |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
HorizontalContentAlignment = h, |
|||
VerticalContentAlignment = v, |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Stretch, 10, 10, 80, 80)] |
|||
[InlineData(HorizontalAlignment.Left, VerticalAlignment.Stretch, 10, 10, 16, 80)] |
|||
[InlineData(HorizontalAlignment.Right, VerticalAlignment.Stretch, 74, 10, 16, 80)] |
|||
[InlineData(HorizontalAlignment.Center, VerticalAlignment.Stretch, 42, 10, 16, 80)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Top, 10, 10, 80, 16)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Bottom, 10, 74, 80, 16)] |
|||
[InlineData(HorizontalAlignment.Stretch, VerticalAlignment.Center, 10, 42, 80, 16)] |
|||
public void Content_Alignment_And_Padding_Are_Applied_To_Child_Bounds( |
|||
HorizontalAlignment h, |
|||
VerticalAlignment v, |
|||
double expectedX, |
|||
double expectedY, |
|||
double expectedWidth, |
|||
double expectedHeight) |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
HorizontalContentAlignment = h, |
|||
VerticalContentAlignment = v, |
|||
Padding = new Thickness(10), |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Content_Can_Be_Stretched() |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(0, 0, 100, 100), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Content_Can_Be_Right_Aligned() |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
HorizontalAlignment = HorizontalAlignment.Right |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(84, 0, 16, 100), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Content_Can_Be_Bottom_Aligned() |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
VerticalAlignment = VerticalAlignment.Bottom, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(0, 84, 100, 16), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Content_Can_Be_TopLeft_Aligned() |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
HorizontalAlignment = HorizontalAlignment.Right, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Content_Can_Be_TopRight_Aligned() |
|||
{ |
|||
Border content; |
|||
var target = new ContentPresenter |
|||
{ |
|||
Content = content = new Border |
|||
{ |
|||
MinWidth = 16, |
|||
MinHeight = 16, |
|||
HorizontalAlignment = HorizontalAlignment.Right, |
|||
VerticalAlignment = VerticalAlignment.Top, |
|||
}, |
|||
}; |
|||
|
|||
target.UpdateChild(); |
|||
target.Measure(new Size(100, 100)); |
|||
target.Arrange(new Rect(0, 0, 100, 100)); |
|||
|
|||
Assert.Equal(new Rect(84, 0, 16, 16), content.Bounds); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Padding_Is_Applied_To_TopLeft_Aligned_Content() |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
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 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)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
using System; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.UnitTests; |
|||
using Moq; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Visuals.UnitTests.Media |
|||
{ |
|||
public class RectangleGeometryTests |
|||
{ |
|||
[Fact] |
|||
public void Rectangle_With_Transform_Can_Be_Changed() |
|||
{ |
|||
using (UnitTestApplication.Start(GetServices())) |
|||
{ |
|||
var target = new RectangleGeometry |
|||
{ |
|||
Rect = new Rect(0, 0, 100, 100), |
|||
Transform = new RotateTransform(45), |
|||
}; |
|||
|
|||
target.Rect = new Rect(50, 50, 150, 150); |
|||
} |
|||
} |
|||
|
|||
private TestServices GetServices() |
|||
{ |
|||
var context = Mock.Of<IStreamGeometryContextImpl>(); |
|||
var transformedGeometry = new Mock<ITransformedGeometryImpl>(); |
|||
var streamGeometry = Mock.Of<IStreamGeometryImpl>(x => |
|||
x.Open() == context && |
|||
x.WithTransform(It.IsAny<Matrix>()) == transformedGeometry.Object); |
|||
var renderInterface = Mock.Of<IPlatformRenderInterface>(x => |
|||
x.CreateStreamGeometry() == streamGeometry); |
|||
return new TestServices(renderInterface: renderInterface); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue