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