diff --git a/Avalonia.sln b/Avalonia.sln index d9ef7a7137..9af5841a0b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25123.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -159,19 +159,25 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 - src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 + samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4 - src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 + samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 + src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 + src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{e1aa3dbf-9056-4530-9376-18119a7a3ffe}*SharedItemsImports = 4 EndGlobalSection diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index abdd890ecd..b732387cf5 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Specialized; using System.Linq; using Avalonia.VisualTree; +using Avalonia.Media; namespace Avalonia.Controls.Primitives { @@ -58,6 +59,8 @@ namespace Avalonia.Controls.Primitives if (info != null) { + child.RenderTransform = new MatrixTransform(info.Bounds.Transform); + child.TransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute); child.Arrange(info.Bounds.Bounds); } else diff --git a/src/Avalonia.Input/InputExtensions.cs b/src/Avalonia.Input/InputExtensions.cs index fcf82287ec..a2321f3493 100644 --- a/src/Avalonia.Input/InputExtensions.cs +++ b/src/Avalonia.Input/InputExtensions.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 Avalonia.VisualTree; using System; using System.Collections.Generic; using System.Linq; @@ -23,14 +24,13 @@ namespace Avalonia.Input public static IEnumerable GetInputElementsAt(this IInputElement element, Point p) { Contract.Requires(element != null); + var transformedBounds = BoundsTracker.GetTransformedBounds((Visual)element); + var geometry = transformedBounds.GetTransformedBoundsGeometry(); - if (element.Bounds.Contains(p) && - element.IsVisible && + if (element.IsVisible && element.IsHitTestVisible && element.IsEnabledCore) { - p -= element.Bounds.Position; - if (element.VisualChildren.Any()) { foreach (var child in ZSort(element.VisualChildren.OfType())) @@ -42,7 +42,10 @@ namespace Avalonia.Input } } - yield return element; + if (geometry.FillContains(p)) + { + yield return element; + } } } diff --git a/src/Avalonia.SceneGraph/Media/DrawingContext.cs b/src/Avalonia.SceneGraph/Media/DrawingContext.cs index c12434a442..90a9749e2a 100644 --- a/src/Avalonia.SceneGraph/Media/DrawingContext.cs +++ b/src/Avalonia.SceneGraph/Media/DrawingContext.cs @@ -44,6 +44,8 @@ namespace Avalonia.Media private Matrix _currentTransform = Matrix.Identity; + private Matrix _currentContainerTransform = Matrix.Identity; + /// /// Gets the current transform of the drawing context. /// @@ -57,7 +59,7 @@ namespace Avalonia.Media } } - private Matrix _currentContainerTransform = Matrix.Identity; + internal Matrix CurrentContainerTransform => _currentContainerTransform; /// /// Draws a bitmap image. diff --git a/src/Avalonia.SceneGraph/Media/Geometry.cs b/src/Avalonia.SceneGraph/Media/Geometry.cs index e6eaaff042..092aea0c61 100644 --- a/src/Avalonia.SceneGraph/Media/Geometry.cs +++ b/src/Avalonia.SceneGraph/Media/Geometry.cs @@ -66,5 +66,15 @@ namespace Avalonia.Media { return PlatformImpl.GetRenderBounds(strokeThickness); } + + /// + /// Indicates whether the geometry contains the specified point. + /// + /// The point. + /// true if the geometry contains the point; otherwise, false. + public bool FillContains(Point point) + { + return PlatformImpl.FillContains(point); + } } } diff --git a/src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs b/src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs index f3a3f655e5..e18a76d739 100644 --- a/src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs +++ b/src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs @@ -24,5 +24,12 @@ namespace Avalonia.Platform /// The stroke thickness. /// The bounding rectangle. Rect GetRenderBounds(double strokeThickness); + + /// + /// Indicates whether the geometry contains the specified point. + /// + /// The point. + /// true if the geometry contains the point; otherwise, false. + bool FillContains(Point point); } } diff --git a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs index 3157feb369..a8899c7dcd 100644 --- a/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs +++ b/src/Avalonia.SceneGraph/Rendering/RendererMixin.cs @@ -122,6 +122,12 @@ namespace Avalonia.Rendering using (context.PushTransformContainer()) { visual.Render(context); + var transformed = + new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform); + if (visual is Visual) + { + BoundsTracker.SetTransformedBounds((Visual)visual, transformed); + } var lst = GetSortedVisualList(visual.VisualChildren); diff --git a/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs b/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs index f39c0f0c87..4824cb141b 100644 --- a/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs +++ b/src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; +using Avalonia.Media; namespace Avalonia.VisualTree { @@ -16,6 +17,9 @@ namespace Avalonia.VisualTree /// public class BoundsTracker { + private static AttachedProperty TransformedBoundsProperty = + AvaloniaProperty.RegisterAttached("TransformedBounds"); + /// /// Starts tracking the specified visual. /// @@ -23,42 +27,19 @@ namespace Avalonia.VisualTree /// An observable that returns the tracked bounds. public IObservable Track(Visual visual) { - return Track(visual, (Visual)visual.GetVisualRoot()); + return visual.GetObservable(TransformedBoundsProperty); } - /// - /// Starts tracking the specified visual relative to another control. - /// - /// The visual. - /// The control that the tracking should be relative to. - /// An observable that returns the tracked bounds. - public IObservable Track(Visual visual, Visual relativeTo) + internal static void SetTransformedBounds(Visual visual, TransformedBounds bounds) { - var visuals = visual.GetSelfAndVisualAncestors() - .TakeWhile(x => x != relativeTo) - .Reverse(); - var boundsSubscriptions = new List>(); - - foreach (var v in visuals.Cast()) - { - boundsSubscriptions.Add(v.GetObservable(Visual.BoundsProperty)); - } - - var bounds = boundsSubscriptions.CombineLatest().Select(ExtractBounds); - - // TODO: Track transform and clip rectangle. - return bounds.Select(x => new TransformedBounds(x, new Rect(), Matrix.Identity)); + visual.SetValue(TransformedBoundsProperty, bounds); } /// - /// Sums a collection of rectangles. + /// Gets the transformed bounds of the visual. /// - /// The collection of rectangles. - /// The summed rectangle. - private static Rect ExtractBounds(IList rects) - { - var position = rects.Select(x => x.Position).Aggregate((a, b) => a + b); - return new Rect(position, rects.Last().Size); - } + /// The visual. + /// The transformed bounds. + public static TransformedBounds GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty); } } diff --git a/src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs b/src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs index c7e0f3abe0..da8ed01ccc 100644 --- a/src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs @@ -1,15 +1,17 @@ // 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.Media; + namespace Avalonia.VisualTree { /// - /// Holds information about the bounds of a control, together with a transform and a clip/ + /// Holds information about the bounds of a control, together with a transform and a clip. /// - public class TransformedBounds + public struct TransformedBounds { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the struct. /// /// The control's bounds. /// The control's clip rectangle. @@ -35,5 +37,21 @@ namespace Avalonia.VisualTree /// Gets the control's transform. /// public Matrix Transform { get; } + + public Geometry GetTransformedBoundsGeometry() + { + StreamGeometry geometry = new StreamGeometry(); + using (var context = geometry.Open()) + { + context.SetFillRule(FillRule.EvenOdd); + context.BeginFigure(Bounds.TopLeft * Transform, true); + context.LineTo(Bounds.TopRight * Transform); + context.LineTo(Bounds.BottomRight * Transform); + context.LineTo(Bounds.BottomLeft * Transform); + context.LineTo(Bounds.TopLeft * Transform); + context.EndFigure(true); + } + return geometry; + } } } diff --git a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs index 126db894be..88d86182f8 100644 --- a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs @@ -62,6 +62,11 @@ namespace Avalonia.Cairo.Media } } + internal bool FillContains(Point point) + { + return _context.InFill(point.X, point.Y); + } + public void LineTo(Point point) { if (this.Path == null) diff --git a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs index 6152e73489..c325ebab14 100644 --- a/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs +++ b/src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs @@ -67,5 +67,10 @@ namespace Avalonia.Cairo.Media { return _impl; } + + public bool FillContains(Point point) + { + return _impl.FillContains(point); + } } } diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 04b325ff81..071c45c477 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -75,6 +75,13 @@ namespace Avalonia.Skia return new StreamContext(this); } + public bool FillContains(Point point) + { + // TODO: Not supported by SkiaSharp yet, so use expanded Rect + // return EffectivePath.Contains(point.X, point.Y); + return GetRenderBounds(0).Contains(point); + } + class StreamContext : IStreamGeometryContextImpl { private readonly StreamGeometryImpl _geometryImpl; diff --git a/src/Skia/Avalonia.Skia/readme.md b/src/Skia/Avalonia.Skia/readme.md index eec5df79f1..3defe9c58c 100644 --- a/src/Skia/Avalonia.Skia/readme.md +++ b/src/Skia/Avalonia.Skia/readme.md @@ -4,6 +4,9 @@ BitmapImpl - constructor from Width/Height - Save +StreamGeometryImpl +- Hit testing in Geometry missing as SkiaSharp does not expose this + DrawingContextImpl - Alpha support missing as SkiaSharp does not expose this - Gradient Shader caching? diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index 4c34c031c4..b98c944c7d 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -84,5 +84,12 @@ namespace Avalonia.Direct2D1.Media return DefiningGeometry.GetWidenedBounds((float)strokeThickness).ToAvalonia(); } } + + + public bool FillContains(Point point) + { + return Geometry.FillContainsPoint(point.ToSharpDX()); + } + } } diff --git a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj index a7144c7246..37ff49650a 100644 --- a/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj +++ b/tests/Avalonia.Input.UnitTests/Avalonia.Input.UnitTests.csproj @@ -1,4 +1,4 @@ - + @@ -35,6 +35,10 @@ 4 + + ..\..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll + True + diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index 2f984ddce7..bbec9e239f 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -1,9 +1,17 @@ // 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.Controls; using Avalonia.Layout; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.UnitTests; +using Moq; using Xunit; +using System.Collections.Generic; +using System.IO; namespace Avalonia.Input.UnitTests { @@ -12,59 +20,73 @@ namespace Avalonia.Input.UnitTests [Fact] public void InputHitTest_Should_Find_Control_At_Point() { - var container = new Decorator + using (var application = UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) { - Width = 200, - Height = 200, - Child = new Border + var container = new Decorator { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; + Width = 200, + Height = 200, + Child = new Border + { + Width = 100, + Height = 100, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); - var result = container.InputHitTest(new Point(100, 100)); + var context = new DrawingContext(Mock.Of()); + context.Render(container); - Assert.Equal(container.Child, result); + var result = container.InputHitTest(new Point(100, 100)); + + Assert.Equal(container.Child, result); + } } [Fact] public void InputHitTest_Should_Not_Find_Control_Outside_Point() { - var container = new Decorator + using (UnitTestApplication.Start(new TestServices(renderInterface:new MockRenderInterface()))) { - Width = 200, - Height = 200, - Child = new Border + var container = new Decorator { - Width = 100, - Height = 100, - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center - } - }; + Width = 200, + Height = 200, + Child = new Border + { + Width = 100, + Height = 100, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); + var context = new DrawingContext(Mock.Of()); + context.Render(container); - var result = container.InputHitTest(new Point(10, 10)); + var result = container.InputHitTest(new Point(10, 10)); - Assert.Equal(container, result); + Assert.Equal(container, result); + } } [Fact] public void InputHitTest_Should_Find_Top_Control_At_Point() { - var container = new Panel + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) { - Width = 200, - Height = 200, - Children = new Controls.Controls + var container = new Panel + { + Width = 200, + Height = 200, + Children = new Controls.Controls { new Border { @@ -81,24 +103,30 @@ namespace Avalonia.Input.UnitTests VerticalAlignment = VerticalAlignment.Center } } - }; + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); + var context = new DrawingContext(Mock.Of()); + context.Render(container); - var result = container.InputHitTest(new Point(100, 100)); + var result = container.InputHitTest(new Point(100, 100)); - Assert.Equal(container.Children[1], result); + Assert.Equal(container.Children[1], result); + } } [Fact] public void InputHitTest_Should_Find_Top_Control_At_Point_With_ZOrder() { - var container = new Panel + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) { - Width = 200, - Height = 200, - Children = new Controls.Controls + var container = new Panel + { + Width = 200, + Height = 200, + Children = new Controls.Controls { new Border { @@ -116,14 +144,210 @@ namespace Avalonia.Input.UnitTests VerticalAlignment = VerticalAlignment.Center } } - }; + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + context.Render(container); + + var result = container.InputHitTest(new Point(100, 100)); + + Assert.Equal(container.Children[0], result); + } + } + + [Fact] + public void InputHitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() + { + using (UnitTestApplication.Start(new TestServices(renderInterface: new MockRenderInterface()))) + { + Border target; + var container = new Panel + { + Width = 200, + Height = 200, + Children = new Controls.Controls + { + new Border + { + Width = 100, + Height = 100, + ZIndex = 1, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + Child = target = new Border + { + Width = 50, + Height = 50, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top, + RenderTransform = new TranslateTransform(110, 110), + } + }, + } + }; + + container.Measure(Size.Infinity); + container.Arrange(new Rect(container.DesiredSize)); + + var context = new DrawingContext(Mock.Of()); + context.Render(container); + + var result = container.InputHitTest(new Point(120, 120)); + + Assert.Equal(target, result); + } + } + + + class MockRenderInterface : IPlatformRenderInterface + { + public IFormattedTextImpl CreateFormattedText(string text, string fontFamilyName, double fontSize, FontStyle fontStyle, TextAlignment textAlignment, FontWeight fontWeight, TextWrapping wrapping) + { + throw new NotImplementedException(); + } + + public IRenderTarget CreateRenderer(IPlatformHandle handle) + { + throw new NotImplementedException(); + } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) + { + throw new NotImplementedException(); + } + + public IStreamGeometryImpl CreateStreamGeometry() + { + return new MockStreamGeometry(); + } + + public IBitmapImpl LoadBitmap(Stream stream) + { + throw new NotImplementedException(); + } + + public IBitmapImpl LoadBitmap(string fileName) + { + throw new NotImplementedException(); + } + + class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl + { + private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); + public Rect Bounds + { + get + { + throw new NotImplementedException(); + } + } + + public Matrix Transform + { + get + { + throw new NotImplementedException(); + } - container.Measure(Size.Infinity); - container.Arrange(new Rect(container.DesiredSize)); + set + { + throw new NotImplementedException(); + } + } - var result = container.InputHitTest(new Point(100, 100)); + public IStreamGeometryImpl Clone() + { + return this; + } - Assert.Equal(container.Children[0], result); + public bool FillContains(Point point) + { + return _impl.FillContains(point); + } + + public Rect GetRenderBounds(double strokeThickness) + { + throw new NotImplementedException(); + } + + public IStreamGeometryContextImpl Open() + { + return _impl; + } + + class MockStreamGeometryContext : IStreamGeometryContextImpl + { + private List points = new List(); + public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection) + { + throw new NotImplementedException(); + } + + public void BeginFigure(Point startPoint, bool isFilled) + { + points.Add(startPoint); + } + + public void CubicBezierTo(Point point1, Point point2, Point point3) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + } + + public void EndFigure(bool isClosed) + { + } + + public void LineTo(Point point) + { + points.Add(point); + } + + public void QuadraticBezierTo(Point control, Point endPoint) + { + throw new NotImplementedException(); + } + + public void SetFillRule(FillRule fillRule) + { + } + + public bool FillContains(Point point) + { + // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // to determine if the point is in the geometry (since it will always be convex in this situation) + for (int i = 0; i < points.Count; i++) + { + var a = points[i]; + var b = points[(i + 1) % points.Count]; + var c = points[(i + 2) % points.Count]; + + Vector v0 = c - a; + Vector v1 = b - a; + Vector v2 = point - a; + + var dot00 = v0 * v0; + var dot01 = v0 * v1; + var dot02 = v0 * v2; + var dot11 = v1 * v1; + var dot12 = v1 * v2; + + + var invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + var u = (dot11 * dot02 - dot01 * dot12) * invDenom; + var v = (dot00 * dot12 - dot01 * dot02) * invDenom; + if ((u >= 0) && (v >= 0) && (u + v < 1)) return true; + } + return false; + } + } + } } } } diff --git a/tests/Avalonia.Input.UnitTests/packages.config b/tests/Avalonia.Input.UnitTests/packages.config index dcbf45edf0..fb397c586c 100644 --- a/tests/Avalonia.Input.UnitTests/packages.config +++ b/tests/Avalonia.Input.UnitTests/packages.config @@ -1,5 +1,6 @@  + diff --git a/tests/Avalonia.SceneGraph.UnitTests/Avalonia.SceneGraph.UnitTests.csproj b/tests/Avalonia.SceneGraph.UnitTests/Avalonia.SceneGraph.UnitTests.csproj index 46eae92c4d..91fd14fc47 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/Avalonia.SceneGraph.UnitTests.csproj +++ b/tests/Avalonia.SceneGraph.UnitTests/Avalonia.SceneGraph.UnitTests.csproj @@ -1,4 +1,4 @@ - + @@ -127,6 +127,10 @@ {F1BAA01A-F176-4C6A-B39D-5B40BB1B148F} Avalonia.Styling + + {88060192-33D5-4932-B0F9-8BD2763E857D} + Avalonia.UnitTests + diff --git a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs index c4f3611f26..75eb9c3a50 100644 --- a/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs +++ b/tests/Avalonia.SceneGraph.UnitTests/VisualTree/BoundsTrackerTests.cs @@ -8,7 +8,11 @@ using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.VisualTree; +using Avalonia.Rendering; using Xunit; +using Avalonia.Media; +using Moq; +using Avalonia.UnitTests; namespace Avalonia.SceneGraph.UnitTests.VisualTree { @@ -17,36 +21,37 @@ namespace Avalonia.SceneGraph.UnitTests.VisualTree [Fact] public void Should_Track_Bounds() { - var target = new BoundsTracker(); - var control = default(Rectangle); - var tree = new Decorator + using (UnitTestApplication.Start(TestServices.StyledWindow)) { - Padding = new Thickness(10), - Child = new Decorator + var target = new BoundsTracker(); + var control = default(Rectangle); + var tree = new Decorator { - Padding = new Thickness(5), - Child = control = new Rectangle + Padding = new Thickness(10), + Child = new Decorator { - Width = 15, - Height = 15, - }, - } - }; + Padding = new Thickness(5), + Child = control = new Rectangle + { + Width = 15, + Height = 15, + }, + } + }; - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(0, 0, 100, 100)); + var context = new DrawingContext(Mock.Of()); - var track = target.Track(control, tree); - var results = new List(); - track.Subscribe(results.Add); + tree.Measure(Size.Infinity); + tree.Arrange(new Rect(0, 0, 100, 100)); + context.Render(tree); - Assert.Equal(new Rect(42, 42, 15, 15), results[0].Bounds); + var track = target.Track(control); + var results = new List(); + track.Subscribe(results.Add); - tree.Padding = new Thickness(15); - tree.Measure(Size.Infinity); - tree.Arrange(new Rect(0, 0, 100, 100)); - - Assert.Equal(new Rect(37, 37, 15, 15), results[1].Bounds); + Assert.Equal(new Rect(0, 0, 15, 15), results[0].Bounds); + Assert.Equal(Matrix.CreateTranslation(42, 42), results[0].Transform); + } } } }