From 9eac3b6203e5115a6b69ff02f7ee6a98e4fbf7bb Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 May 2019 09:05:37 +0200 Subject: [PATCH] Fix TranslatePoint. - Make `VisualExtensions.TranslatePoint` respect render transforms. - Move `IVisual.TransformToVisual` out of the interface and into `VisualExtensions` as an extension method - Make `TranslatePoint` return a nullable value as `TransformToVisual` does, so it can return null when controls don't have a common ancestor --- src/Avalonia.Input/DragDropDevice.cs | 10 +- src/Avalonia.Input/DragEventArgs.cs | 5 +- .../Rendering/SceneGraph/Scene.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 62 ------------- src/Avalonia.Visuals/VisualExtensions.cs | 92 ++++++++++++++----- src/Avalonia.Visuals/VisualTree/IVisual.cs | 11 --- .../VisualTree/VisualExtensions.cs | 10 +- 7 files changed, 87 insertions(+), 105 deletions(-) diff --git a/src/Avalonia.Input/DragDropDevice.cs b/src/Avalonia.Input/DragDropDevice.cs index 0692b21c66..0b9f09b224 100644 --- a/src/Avalonia.Input/DragDropDevice.cs +++ b/src/Avalonia.Input/DragDropDevice.cs @@ -23,7 +23,13 @@ namespace Avalonia.Input { if (target == null) return DragDropEffects.None; - var args = new DragEventArgs(routedEvent, data, target, inputRoot.TranslatePoint(point, target), modifiers) + + var p = inputRoot.TranslatePoint(point, target); + + if (!p.HasValue) + return DragDropEffects.None; + + var args = new DragEventArgs(routedEvent, data, target, p.Value, modifiers) { RoutedEvent = routedEvent, DragEffects = operation @@ -108,4 +114,4 @@ namespace Avalonia.Input } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Input/DragEventArgs.cs b/src/Avalonia.Input/DragEventArgs.cs index 915ee4ee5c..dc0b76b225 100644 --- a/src/Avalonia.Input/DragEventArgs.cs +++ b/src/Avalonia.Input/DragEventArgs.cs @@ -26,8 +26,9 @@ namespace Avalonia.Input if (_target != null) { - point = _target.TranslatePoint(_targetLocation, relativeTo); + point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point; } + return point; } @@ -41,4 +42,4 @@ namespace Avalonia.Input } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index ad4c475d89..1afc096c98 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph if (node.GeometryClip != null) { var controlPoint = Root.Visual.TranslatePoint(p, node.Visual); - clipped = !node.GeometryClip.FillContains(controlPoint); + clipped = !node.GeometryClip.FillContains(controlPoint.Value); } if (!clipped) diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index d294f111ad..9e088cb136 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -291,29 +291,6 @@ namespace Avalonia Contract.Requires(context != null); } - /// - /// Returns a transform that transforms the visual's coordinates into the coordinates - /// of the specified . - /// - /// The visual to translate the coordinates to. - /// - /// A containing the transform or null if the visuals don't share a - /// common ancestor. - /// - public Matrix? TransformToVisual(IVisual visual) - { - var common = this.FindCommonVisualAncestor(visual); - - if (common != null) - { - var thisOffset = GetOffsetFrom(common, this); - var thatOffset = GetOffsetFrom(common, visual); - return -thatOffset * thisOffset; - } - - return null; - } - /// /// Indicates that a property change should cause to be /// called. @@ -480,45 +457,6 @@ namespace Avalonia } } - /// - /// Gets the visual offset from the specified ancestor. - /// - /// The ancestor visual. - /// The visual. - /// The visual offset. - private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) - { - var result = Matrix.Identity; - - while (visual != ancestor) - { - if (visual.RenderTransform?.Value != null) - { - var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); - var offset = Matrix.CreateTranslation(origin); - var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); - - result *= renderTransform; - } - - var topLeft = visual.Bounds.TopLeft; - - if (topLeft != default) - { - result *= Matrix.CreateTranslation(topLeft); - } - - visual = visual.VisualParent; - - if (visual == null) - { - throw new ArgumentException("'visual' is not a descendant of 'ancestor'."); - } - } - - return result; - } - /// /// Called when a visual's changes. /// diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index 650921a985..b191d044db 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using Avalonia.Rendering; using Avalonia.VisualTree; namespace Avalonia @@ -20,10 +19,8 @@ namespace Avalonia /// The point in client coordinates. public static Point PointToClient(this IVisual visual, PixelPoint point) { - var (root, offset) = GetRootAndPosition(visual); - var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling); - var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y); - return root.PointToClient(screenPoint); + var rootPoint = visual.VisualRoot.PointToClient(point); + return visual.VisualRoot.TranslatePoint(rootPoint, visual).Value; } /// @@ -34,48 +31,93 @@ namespace Avalonia /// The point in screen coordinates. public static PixelPoint PointToScreen(this IVisual visual, Point point) { - var p = GetRootAndPosition(visual); - return p.Item1.PointToScreen(point + p.Item2); + var p = visual.TranslatePoint(point, visual.VisualRoot); + return visual.VisualRoot.PointToScreen(p.Value); + } + + /// + /// Returns a transform that transforms the visual's coordinates into the coordinates + /// of the specified . + /// + /// The visual whose coordinates are to be transformed. + /// The visual to translate the coordinates to. + /// + /// A containing the transform or null if the visuals don't share a + /// common ancestor. + /// + public static Matrix? TransformToVisual(this IVisual from, IVisual to) + { + var common = from.FindCommonVisualAncestor(to); + + if (common != null) + { + var thisOffset = GetOffsetFrom(common, from); + var thatOffset = GetOffsetFrom(common, to); + return -thatOffset * thisOffset; + } + + return null; } /// /// Translates a point relative to this visual to coordinates that are relative to the specified visual. - /// The visual and relativeTo should be descendants of the same root window /// /// The visual. /// The point value, as relative to this visual. /// The visual to translate the given point into. - /// A point value, now relative to the target visual rather than this source element. - public static Point TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) + /// + /// A point value, now relative to the target visual rather than this source element, or null if the + /// two elements have no common ancestor. + /// + public static Point? TranslatePoint(this IVisual visual, Point point, IVisual relativeTo) { - var pos = GetRootAndPosition(visual); - var relToPos = GetRootAndPosition(relativeTo); + var transform = visual.TransformToVisual(relativeTo); - return point - (relToPos.Item2 - pos.Item2); + if (transform.HasValue) + { + return point.Transform(transform.Value); + } + + return null; } /// - /// Gets the root of the control's visual tree and the position of the control - /// in the root's coordinate space. + /// Gets a transform from an ancestor to a descendent. /// - /// The visual. - /// A tuple containing the root and the position of the control. - private static Tuple GetRootAndPosition(IVisual v) + /// The ancestor visual. + /// The visual. + /// The transform. + private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) { - var result = new Vector(); + var result = Matrix.Identity; - while (!(v is IRenderRoot)) + while (visual != ancestor) { - result = new Vector(result.X + v.Bounds.X, result.Y + v.Bounds.Y); - v = v.VisualParent; + if (visual.RenderTransform?.Value != null) + { + var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); + var offset = Matrix.CreateTranslation(origin); + var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); + + result *= renderTransform; + } + + var topLeft = visual.Bounds.TopLeft; + + if (topLeft != default) + { + result *= Matrix.CreateTranslation(topLeft); + } + + visual = visual.VisualParent; - if (v == null) + if (visual == null) { - throw new InvalidOperationException("Control is not attached to visual tree."); + throw new ArgumentException("'visual' is not a descendant of 'ancestor'."); } } - return Tuple.Create((IRenderRoot)v, result); + return result; } } } diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index 278a802597..dd3686ce3d 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -116,16 +116,5 @@ namespace Avalonia.VisualTree /// /// The context. void Render(DrawingContext context); - - /// - /// Returns a transform that transforms the visual's coordinates into the coordinates - /// of the specified . - /// - /// The visual to translate the coordinates to. - /// - /// A containing the transform or null if the visuals don't share a - /// common ancestor. - /// - Matrix? TransformToVisual(IVisual visual); } } diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 2d417852fe..567b676b1e 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -132,8 +132,14 @@ namespace Avalonia.VisualTree Contract.Requires(visual != null); var root = visual.GetVisualRoot(); - p = visual.TranslatePoint(p, root); - return root.Renderer.HitTest(p, visual, filter); + var rootPoint = visual.TranslatePoint(p, root); + + if (rootPoint.HasValue) + { + return root.Renderer.HitTest(rootPoint.Value, visual, filter); + } + + return Enumerable.Empty(); } ///