Browse Source

Merge branch 'master' into extra-mouse-buttons

pull/3033/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
d33b9f9615
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      native/Avalonia.Native/src/OSX/window.mm
  2. 25
      src/Avalonia.Controls/ItemsControl.cs
  3. 10
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  4. 18
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  5. 8
      src/Avalonia.Controls/TopLevel.cs
  6. 1
      src/Avalonia.Controls/Window.cs
  7. 18
      src/Avalonia.Input/FocusManager.cs
  8. 8
      src/Avalonia.Input/Pointer.cs
  9. 27
      src/Avalonia.Layout/LayoutHelper.cs
  10. 8
      src/Avalonia.Layout/Layoutable.cs
  11. 2
      src/Avalonia.Styling/StyledElement.cs
  12. 22
      src/Avalonia.Visuals/Visual.cs
  13. 155
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  14. 2
      src/Avalonia.X11/X11Clipboard.cs
  15. 5
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  16. 23
      src/Windows/Avalonia.Win32/OleContext.cs
  17. 2
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  18. 5
      src/Windows/Avalonia.Win32/WindowImpl.cs
  19. 64
      tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs
  20. 3
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  21. 4
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs
  22. 172
      tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs

1
native/Avalonia.Native/src/OSX/window.mm

@ -739,6 +739,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
self = [super init];
[self setWantsBestResolutionOpenGLSurface:true];
[self setWantsLayer:YES];
_parent = parent;
_area = nullptr;
return self;

25
src/Avalonia.Controls/ItemsControl.cs

@ -5,7 +5,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
@ -324,20 +323,24 @@ namespace Avalonia.Controls
return;
}
var current = focus.Current
.GetSelfAndVisualAncestors()
.OfType<IInputElement>()
.FirstOrDefault(x => x.VisualParent == container);
IVisual current = focus.Current;
if (current != null)
while (current != null)
{
var next = GetNextControl(container, direction.Value, current, false);
if (next != null)
if (current.VisualParent == container && current is IInputElement inputElement)
{
focus.Focus(next, NavigationMethod.Directional);
e.Handled = true;
IInputElement next = GetNextControl(container, direction.Value, inputElement, false);
if (next != null)
{
focus.Focus(next, NavigationMethod.Directional);
e.Handled = true;
}
break;
}
current = current.VisualParent;
}
}

10
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -149,15 +149,9 @@ namespace Avalonia.Controls.Notifications
/// <param name="host">The <see cref="Window"/> that will be the host.</param>
private void Install(Window host)
{
var adornerLayer = host.GetVisualDescendants()
.OfType<VisualLayerManager>()
.FirstOrDefault()
?.AdornerLayer;
var adornerLayer = host.FindDescendantOfType<VisualLayerManager>()?.AdornerLayer;
if (adornerLayer != null)
{
adornerLayer.Children.Add(this);
}
adornerLayer?.Children.Add(this);
}
}
}

18
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -13,7 +13,6 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Logging;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -269,11 +268,20 @@ namespace Avalonia.Controls.Primitives
/// <returns>The container or null if the event did not originate in a container.</returns>
protected IControl GetContainerFromEventSource(IInteractive eventSource)
{
var item = ((IVisual)eventSource).GetSelfAndVisualAncestors()
.OfType<IControl>()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
var parent = (IVisual)eventSource;
return item;
while (parent != null)
{
if (parent is IControl control && control.LogicalParent == this
&& ItemContainerGenerator?.IndexFromContainer(control) != -1)
{
return control;
}
parent = parent.VisualParent;
}
return null;
}
/// <inheritdoc/>

8
src/Avalonia.Controls/TopLevel.cs

@ -2,9 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls.Notifications;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -15,7 +13,6 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls
@ -302,10 +299,7 @@ namespace Avalonia.Controls
/// <param name="scaling">The window scaling.</param>
protected virtual void HandleScalingChanged(double scaling)
{
foreach (ILayoutable control in this.GetSelfAndVisualDescendants())
{
control.InvalidateMeasure();
}
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
}
/// <inheritdoc/>

1
src/Avalonia.Controls/Window.cs

@ -336,7 +336,6 @@ namespace Avalonia.Controls
if (close)
{
PlatformImpl?.Dispose();
HandleClosed();
}
}
}

18
src/Avalonia.Input/FocusManager.cs

@ -180,18 +180,18 @@ namespace Avalonia.Input
if (sender == e.Source && ev.MouseButton == MouseButton.Left)
{
var element = (ev.Pointer?.Captured as IInputElement) ?? (e.Source as IInputElement);
IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement;
if (element == null || !CanFocus(element))
while (element != null)
{
element = element.GetSelfAndVisualAncestors()
.OfType<IInputElement>()
.FirstOrDefault(CanFocus);
}
if (element is IInputElement inputElement && CanFocus(inputElement))
{
Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.InputModifiers);
if (element != null)
{
Instance?.Focus(element, NavigationMethod.Pointer, ev.InputModifiers);
break;
}
element = element.VisualParent;
}
}
}

8
src/Avalonia.Input/Pointer.cs

@ -55,9 +55,11 @@ namespace Avalonia.Input
Captured.DetachedFromVisualTree += OnCaptureDetached;
}
IInputElement GetNextCapture(IVisual parent) =>
parent as IInputElement ?? parent.GetVisualAncestors().OfType<IInputElement>().FirstOrDefault();
IInputElement GetNextCapture(IVisual parent)
{
return parent as IInputElement ?? parent.FindAncestorOfType<IInputElement>();
}
private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
{
Capture(GetNextCapture(e.Parent));

27
src/Avalonia.Layout/LayoutHelper.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.VisualTree;
namespace Avalonia.Layout
{
@ -61,5 +62,31 @@ namespace Avalonia.Layout
return availableSize;
}
/// <summary>
/// Invalidates measure for given control and all visual children recursively.
/// </summary>
public static void InvalidateSelfAndChildrenMeasure(ILayoutable control)
{
void InnerInvalidateMeasure(IVisual target)
{
if (target is ILayoutable targetLayoutable)
{
targetLayoutable.InvalidateMeasure();
}
var visualChildren = target.VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (int i = 0; i < visualChildrenCount; i++)
{
IVisual child = visualChildren[i];
InnerInvalidateMeasure(child);
}
}
InnerInvalidateMeasure(control);
}
}
}

8
src/Avalonia.Layout/Layoutable.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 System.Linq;
using Avalonia.Logging;
using Avalonia.VisualTree;
@ -694,12 +693,9 @@ namespace Avalonia.Layout
}
/// <inheritdoc/>
protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
foreach (ILayoutable i in this.GetSelfAndVisualDescendants())
{
i.InvalidateMeasure();
}
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
base.OnVisualParentChanged(oldParent, newParent);
}

2
src/Avalonia.Styling/StyledElement.cs

@ -288,7 +288,7 @@ namespace Avalonia
var list = new AvaloniaList<ILogical>
{
ResetBehavior = ResetBehavior.Remove,
Validate = ValidateLogicalChild
Validate = logical => ValidateLogicalChild(logical)
};
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;

22
src/Avalonia.Visuals/Visual.cs

@ -4,7 +4,6 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Logging;
@ -121,7 +120,7 @@ namespace Avalonia
{
var visualChildren = new AvaloniaList<IVisual>();
visualChildren.ResetBehavior = ResetBehavior.Remove;
visualChildren.Validate = ValidateVisualChild;
visualChildren.Validate = visual => ValidateVisualChild(visual);
visualChildren.CollectionChanged += VisualChildrenChanged;
VisualChildren = visualChildren;
}
@ -173,7 +172,22 @@ namespace Avalonia
/// </summary>
public bool IsEffectivelyVisible
{
get { return this.GetSelfAndVisualAncestors().All(x => x.IsVisible); }
get
{
IVisual node = this;
while (node != null)
{
if (!node.IsVisible)
{
return false;
}
node = node.VisualParent;
}
return true;
}
}
/// <summary>
@ -552,7 +566,7 @@ namespace Avalonia
if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true)
{
var root = this.GetVisualAncestors().OfType<IRenderRoot>().FirstOrDefault();
var root = this.FindAncestorOfType<IRenderRoot>();
var e = new VisualTreeAttachmentEventArgs(_visualParent, root);
OnAttachedToVisualTreeCore(e);
}

155
src/Avalonia.Visuals/VisualTree/VisualExtensions.cs

@ -14,7 +14,7 @@ namespace Avalonia.VisualTree
public static class VisualExtensions
{
/// <summary>
/// Calculates the distance from a visual's <see cref="IRenderRoot"/>.
/// Calculates the distance from a visual's ancestor.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="ancestor">The ancestor visual.</param>
@ -30,13 +30,39 @@ namespace Avalonia.VisualTree
while (visual != null && visual != ancestor)
{
++result;
visual = visual.VisualParent;
result++;
}
return visual != null ? result : -1;
}
/// <summary>
/// Calculates the distance from a visual's root.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>
/// The number of steps from the visual to the root.
/// </returns>
public static int CalculateDistanceFromRoot(IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
var result = 0;
visual = visual?.VisualParent;
while (visual != null)
{
visual = visual.VisualParent;
result++;
}
return result;
}
/// <summary>
/// Tries to get the first common ancestor of two visuals.
/// </summary>
@ -47,8 +73,53 @@ namespace Avalonia.VisualTree
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors())
.FirstOrDefault();
if (target is null)
{
return null;
}
void GoUpwards(ref IVisual node, int count)
{
for (int i = 0; i < count; ++i)
{
node = node.VisualParent;
}
}
// We want to find lowest node first, then make sure that both nodes are at the same height.
// By doing that we can sometimes find out that other node is our lowest common ancestor.
var firstHeight = CalculateDistanceFromRoot(visual);
var secondHeight = CalculateDistanceFromRoot(target);
if (firstHeight > secondHeight)
{
GoUpwards(ref visual, firstHeight - secondHeight);
}
else
{
GoUpwards(ref target, secondHeight - firstHeight);
}
if (visual == target)
{
return visual;
}
while (visual != null && target != null)
{
IVisual firstParent = visual.VisualParent;
IVisual secondParent = target.VisualParent;
if (firstParent == secondParent)
{
return firstParent;
}
visual = visual.VisualParent;
target = target.VisualParent;
}
return null;
}
/// <summary>
@ -69,6 +140,57 @@ namespace Avalonia.VisualTree
}
}
/// <summary>
/// Finds first ancestor of given type.
/// </summary>
/// <typeparam name="T">Ancestor type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First ancestor of given type.</returns>
public static T FindAncestorOfType<T>(this IVisual visual, bool includeSelf = false) where T : class
{
if (visual is null)
{
return null;
}
IVisual parent = includeSelf ? visual : visual.VisualParent;
while (parent != null)
{
if (parent is T result)
{
return result;
}
parent = parent.VisualParent;
}
return null;
}
/// <summary>
/// Finds first descendant of given type.
/// </summary>
/// <typeparam name="T">Descendant type.</typeparam>
/// <param name="visual">The visual.</param>
/// <param name="includeSelf">If given visual should be included in search.</param>
/// <returns>First descendant of given type.</returns>
public static T FindDescendantOfType<T>(this IVisual visual, bool includeSelf = false) where T : class
{
if (visual is null)
{
return null;
}
if (includeSelf && visual is T result)
{
return result;
}
return FindDescendantOfTypeCore<T>(visual);
}
/// <summary>
/// Enumerates an <see cref="IVisual"/> and its ancestors in the visual tree.
/// </summary>
@ -249,6 +371,31 @@ namespace Avalonia.VisualTree
.Select(x => x.Element);
}
private static T FindDescendantOfTypeCore<T>(IVisual visual) where T : class
{
var visualChildren = visual.VisualChildren;
var visualChildrenCount = visualChildren.Count;
for (var i = 0; i < visualChildrenCount; i++)
{
IVisual child = visualChildren[i];
if (child is T result)
{
return result;
}
var childResult = FindDescendantOfTypeCore<T>(child);
if (!(childResult is null))
{
return childResult;
}
}
return null;
}
private class ZOrderElement : IComparable<ZOrderElement>
{
public IVisual Element { get; set; }

2
src/Avalonia.X11/X11Clipboard.cs

@ -79,7 +79,7 @@ namespace Avalonia.X11
atoms = atoms.Concat(new[] {_x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE})
.ToArray();
XChangeProperty(_x11.Display, window, property,
target, 32, PropertyMode.Replace, atoms, atoms.Length);
_x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length);
return property;
}
else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero)

5
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1098,7 +1098,10 @@ namespace Avalonia.Win32.Interop
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target);
[DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern HRESULT RevokeDragDrop(IntPtr hwnd);
[DllImport("ole32.dll", EntryPoint = "OleInitialize")]
public static extern HRESULT OleInitialize(IntPtr val);

23
src/Windows/Avalonia.Win32/OleContext.cs

@ -7,9 +7,9 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class OleContext
internal class OleContext
{
private static OleContext fCurrent;
private static OleContext s_current;
internal static OleContext Current
{
@ -18,13 +18,12 @@ namespace Avalonia.Win32
if (!IsValidOleThread())
return null;
if (fCurrent == null)
fCurrent = new OleContext();
return fCurrent;
if (s_current == null)
s_current = new OleContext();
return s_current;
}
}
private OleContext()
{
UnmanagedMethods.HRESULT res = UnmanagedMethods.OleInitialize(IntPtr.Zero);
@ -43,9 +42,21 @@ namespace Avalonia.Win32
internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target)
{
if (hwnd?.HandleDescriptor != "HWND" || target == null)
{
return false;
}
return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK;
}
internal bool UnregisterDragDrop(IPlatformHandle hwnd)
{
if (hwnd?.HandleDescriptor != "HWND")
{
return false;
}
return UnmanagedMethods.RevokeDragDrop(hwnd.Handle) == UnmanagedMethods.HRESULT.S_OK;
}
}
}

2
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -6,7 +6,7 @@ using IDataObject = Avalonia.Input.IDataObject;
namespace Avalonia.Win32
{
class OleDropTarget : IDropTarget
internal class OleDropTarget : IDropTarget
{
private readonly IInputRoot _target;
private readonly ITopLevelImpl _tl;

5
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -253,6 +253,11 @@ namespace Avalonia.Win32
public void Dispose()
{
if (_dropTarget != null)
{
OleContext.Current?.UnregisterDragDrop(Handle);
_dropTarget = null;
}
if (_hwnd != IntPtr.Zero)
{
UnmanagedMethods.DestroyWindow(_hwnd);

64
tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs

@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Traversal
{
[MemoryDiagnoser]
public class VisualTreeTraversal
{
private readonly TestRoot _root;
private readonly List<Control> _controls = new List<Control>();
private readonly List<Control> _shuffledControls;
public VisualTreeTraversal()
{
var panel = new StackPanel();
_root = new TestRoot { Child = panel, Renderer = new NullRenderer()};
_controls.Add(panel);
_controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 4);
var random = new Random(1);
_shuffledControls = _controls.OrderBy(r => random.Next()).ToList();
_root.LayoutManager.ExecuteInitialLayoutPass(_root);
}
[Benchmark]
public void FindAncestorOfType_Linq()
{
foreach (Control control in _controls)
{
control.GetSelfAndVisualAncestors()
.OfType<TestRoot>()
.FirstOrDefault();
}
}
[Benchmark]
public void FindAncestorOfType_Optimized()
{
foreach (Control control in _controls)
{
control.FindAncestorOfType<TestRoot>();
}
}
[Benchmark]
public void FindCommonVisualAncestor()
{
foreach (IVisual first in _controls)
{
foreach (Control second in _shuffledControls)
{
first.FindCommonVisualAncestor(second);
}
}
}
}
}

3
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -228,8 +228,7 @@ namespace Avalonia.Controls.UnitTests
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
var target = new Window(windowImpl);
var target = new Window();
target.Show();
target.Close();

4
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -28,6 +28,10 @@ namespace Avalonia.UnitTests
return CreatePopupMock().Object;
});
mock.Setup(x => x.Dispose()).Callback(() =>
{
mock.Object.Closed?.Invoke();
});
PixelPoint pos = default;
mock.SetupGet(x => x.Position).Returns(() => pos);
mock.Setup(x => x.Move(It.IsAny<PixelPoint>())).Callback(new Action<PixelPoint>(np => pos = np));

172
tests/Avalonia.Visuals.UnitTests/VisualExtensionsTests.cs

@ -2,12 +2,184 @@
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Visuals.UnitTests
{
public class VisualExtensionsTests
{
[Fact]
public void FindAncestorOfType_Finds_Direct_Parent()
{
StackPanel target;
var root = new TestRoot
{
Child = target = new StackPanel()
};
Assert.Equal(root, target.FindAncestorOfType<TestRoot>());
}
[Fact]
public void FindAncestorOfType_Finds_Ancestor_Of_Nested_Child()
{
Button target;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new StackPanel
{
Children =
{
(target = new Button())
}
}
}
}
};
Assert.Equal(root, target.FindAncestorOfType<TestRoot>());
}
[Fact]
public void FindDescendantOfType_Finds_Direct_Child()
{
StackPanel target;
var root = new TestRoot
{
Child = target = new StackPanel()
};
Assert.Equal(target, root.FindDescendantOfType<StackPanel>());
}
[Fact]
public void FindDescendantOfType_Finds_Nested_Child()
{
Button target;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new StackPanel
{
Children =
{
(target = new Button())
}
}
}
}
};
Assert.Equal(target, root.FindDescendantOfType<Button>());
}
[Fact]
public void FindCommonVisualAncestor_First_Is_Parent_Of_Second()
{
Control left, right;
var root = new TestRoot
{
Child = left = new Decorator
{
Child = right = new Decorator()
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(left, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(left, ancestor);
}
[Fact]
public void FindCommonVisualAncestor_Two_Subtrees_Uniform_Height()
{
Control left, right;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new Decorator
{
Child = new Decorator
{
Child = left = new Decorator()
}
},
new Decorator
{
Child = new Decorator
{
Child = right = new Decorator()
}
}
}
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(root.Child, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(root.Child, ancestor);
}
[Fact]
public void FindCommonVisualAncestor_Two_Subtrees_NonUniform_Height()
{
Control left, right;
var root = new TestRoot
{
Child = new StackPanel
{
Children =
{
new Decorator
{
Child = new Decorator
{
Child = left = new Decorator()
}
},
new Decorator
{
Child = new Decorator
{
Child = new Decorator
{
Child = right = new Decorator()
}
}
}
}
}
};
var ancestor = left.FindCommonVisualAncestor(right);
Assert.Equal(root.Child, ancestor);
ancestor = right.FindCommonVisualAncestor(left);
Assert.Equal(root.Child, ancestor);
}
[Fact]
public void TranslatePoint_Should_Respect_RenderTransforms()
{

Loading…
Cancel
Save