From 69a06e452ee9bfecef85f138d3125080eb1ef2b0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 26 Apr 2017 09:25:30 +0200 Subject: [PATCH] Fixed some Visuals unit tests. Ported some stuff from `scenegraph` that was missed in #956, which fixes some of the failing Avalonia.Visuals.UnitTests. --- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 1 + .../Media/LinearGradientBrush.cs | 2 + .../Media/RadialGradientBrush.cs | 4 +- src/Avalonia.Visuals/Visual.cs | 60 ++++++++++--------- src/Avalonia.Visuals/VisualTree/IVisual.cs | 30 +++++----- .../VisualTree/VisualExtensions.cs | 32 ++++------ .../WindowBaseTests.cs | 2 +- tests/Avalonia.LeakTests/ControlTests.cs | 19 +----- tests/Avalonia.UnitTests/TestRoot.cs | 18 ++++-- 9 files changed, 75 insertions(+), 93 deletions(-) diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index c820a83c2a..dd9a795937 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -2,6 +2,7 @@ netstandard1.1 false + Avalonia true diff --git a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs index 6c0d500343..344e05e16e 100644 --- a/src/Avalonia.Visuals/Media/LinearGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/LinearGradientBrush.cs @@ -1,6 +1,8 @@ // 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; + namespace Avalonia.Media { /// diff --git a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs index 9f9c2733ec..003e2e05f9 100644 --- a/src/Avalonia.Visuals/Media/RadialGradientBrush.cs +++ b/src/Avalonia.Visuals/Media/RadialGradientBrush.cs @@ -4,8 +4,7 @@ namespace Avalonia.Media { /// - /// Paints an area with a radial gradient. A focal point defines the beginning of the gradient, - /// and a circle defines the end point of the gradient. + /// Paints an area with a radial gradient. /// public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush { @@ -56,6 +55,7 @@ namespace Avalonia.Media /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial /// gradient. /// + // TODO: This appears to always be relative so should use a RelativeSize struct or something. public double Radius { get { return GetValue(RadiusProperty); } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index ae5e385169..20772d8f0d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -10,7 +10,6 @@ using Avalonia.Collections; using Avalonia.Data; using Avalonia.Logging; using Avalonia.Media; -using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.VisualTree; @@ -20,10 +19,10 @@ namespace Avalonia /// Base class for controls that provides rendering and related visual properties. /// /// - /// The class acts as a node in the Avalonia scene graph and holds - /// all the information needed for an to render the control. - /// To traverse the scene graph (aka Visual Tree), use the extension methods defined - /// in . + /// The class represents elements that have a visual on-screen + /// representation and stores all the information needed for an + /// to render the control. To traverse the visual tree, use the + /// extension methods defined in . /// public class Visual : Animatable, IVisual { @@ -88,6 +87,7 @@ namespace Avalonia AvaloniaProperty.Register(nameof(ZIndex)); private Rect _bounds; + private IRenderRoot _visualRoot; private IVisual _visualParent; /// @@ -95,7 +95,12 @@ namespace Avalonia /// static Visual() { - AffectsRender(BoundsProperty, IsVisibleProperty, OpacityProperty); + AffectsRender( + BoundsProperty, + ClipProperty, + ClipToBoundsProperty, + IsVisibleProperty, + OpacityProperty); RenderTransformProperty.Changed.Subscribe(RenderTransformChanged); } @@ -122,7 +127,7 @@ namespace Avalonia public event EventHandler DetachedFromVisualTree; /// - /// Gets the bounds of the scene graph node relative to its parent. + /// Gets the bounds of the control relative to its parent. /// public Rect Bounds { @@ -131,7 +136,7 @@ namespace Avalonia } /// - /// Gets a value indicating whether the scene graph node should be clipped to its bounds. + /// Gets a value indicating whether the control should be clipped to its bounds. /// public bool ClipToBounds { @@ -149,7 +154,7 @@ namespace Avalonia } /// - /// Gets a value indicating whether this scene graph node and all its parents are visible. + /// Gets a value indicating whether this control and all its parents are visible. /// public bool IsEffectivelyVisible { @@ -157,7 +162,7 @@ namespace Avalonia } /// - /// Gets a value indicating whether this scene graph node is visible. + /// Gets a value indicating whether this control is visible. /// public bool IsVisible { @@ -166,7 +171,7 @@ namespace Avalonia } /// - /// Gets the opacity of the scene graph node. + /// Gets the opacity of the control. /// public double Opacity { @@ -174,9 +179,8 @@ namespace Avalonia set { SetValue(OpacityProperty, value); } } - /// - /// Gets the opacity mask of the scene graph node. + /// Gets the opacity mask of the control. /// public IBrush OpacityMask { @@ -185,7 +189,7 @@ namespace Avalonia } /// - /// Gets the render transform of the scene graph node. + /// Gets the render transform of the control. /// public Transform RenderTransform { @@ -194,7 +198,7 @@ namespace Avalonia } /// - /// Gets the transform origin of the scene graph node. + /// Gets the transform origin of the control. /// public RelativePoint RenderTransformOrigin { @@ -203,7 +207,7 @@ namespace Avalonia } /// - /// Gets the Z index of the node. + /// Gets the Z index of the control. /// /// /// Controls with a higher will appear in front of controls with @@ -217,7 +221,7 @@ namespace Avalonia } /// - /// Gets the control's visual children. + /// Gets the control's child visuals. /// protected IAvaloniaList VisualChildren { @@ -228,24 +232,20 @@ namespace Avalonia /// /// Gets the root of the visual tree, if the control is attached to a visual tree. /// - protected IRenderRoot VisualRoot - { - get; - private set; - } + protected IRenderRoot VisualRoot => _visualRoot ?? (this as IRenderRoot); /// - /// Gets a value indicating whether this scene graph node is attached to a visual root. + /// Gets a value indicating whether this control is attached to a visual root. /// bool IVisual.IsAttachedToVisualTree => VisualRoot != null; /// - /// Gets the scene graph node's child nodes. + /// Gets the control's child controls. /// IAvaloniaReadOnlyList IVisual.VisualChildren => VisualChildren; /// - /// Gets the scene graph node's parent node. + /// Gets the control's parent visual. /// IVisual IVisual.VisualParent => _visualParent; @@ -321,7 +321,7 @@ namespace Avalonia { Logger.Verbose(LogArea.Visual, this, "Attached to visual tree"); - VisualRoot = e.Root; + _visualRoot = e.Root; if (RenderTransform != null) { @@ -329,6 +329,7 @@ namespace Avalonia } OnAttachedToVisualTree(e); + InvalidateVisual(); if (VisualChildren != null) { @@ -348,7 +349,7 @@ namespace Avalonia { Logger.Verbose(LogArea.Visual, this, "Detached from visual tree"); - VisualRoot = null; + _visualRoot = null; if (RenderTransform != null) { @@ -356,6 +357,7 @@ namespace Avalonia } OnDetachedFromVisualTree(e); + e.Root?.Renderer?.AddDirty(this); if (VisualChildren != null) { @@ -492,11 +494,11 @@ namespace Avalonia { return; } - + var old = _visualParent; _visualParent = value; - if (VisualRoot != null) + if (_visualRoot != null) { var e = new VisualTreeAttachmentEventArgs(old, VisualRoot); OnDetachedFromVisualTreeCore(e); diff --git a/src/Avalonia.Visuals/VisualTree/IVisual.cs b/src/Avalonia.Visuals/VisualTree/IVisual.cs index b9b37b1a20..2047996c3e 100644 --- a/src/Avalonia.Visuals/VisualTree/IVisual.cs +++ b/src/Avalonia.Visuals/VisualTree/IVisual.cs @@ -9,13 +9,13 @@ using Avalonia.Rendering; namespace Avalonia.VisualTree { /// - /// Represents a node in the visual scene graph. + /// Represents control that has a visual on-screen representation. /// /// /// The interface defines the interface required for a renderer to - /// render a scene graph. You should not usually need to reference this interface unless + /// render a control. You should not usually need to reference this interface unless /// you are writing a renderer; instead use the extension methods defined in - /// to traverse the scene graph. This interface is + /// to traverse the visual tree. This interface is /// implemented by . It should not be necessary to implement it /// anywhere else. /// @@ -32,12 +32,12 @@ namespace Avalonia.VisualTree event EventHandler DetachedFromVisualTree; /// - /// Gets the bounds of the scene graph node relative to its parent. + /// Gets the bounds of the control relative to its parent. /// Rect Bounds { get; } /// - /// Gets a value indicating whether the scene graph node should be clipped to its bounds. + /// Gets a value indicating whether the control should be clipped to its bounds. /// bool ClipToBounds { get; set; } @@ -47,47 +47,47 @@ namespace Avalonia.VisualTree Geometry Clip { get; set; } /// - /// Gets a value indicating whether this scene graph node is attached to a visual root. + /// Gets a value indicating whether this control is attached to a visual root. /// bool IsAttachedToVisualTree { get; } /// - /// Gets a value indicating whether this scene graph node and all its parents are visible. + /// Gets a value indicating whether this control and all its parents are visible. /// bool IsEffectivelyVisible { get; } /// - /// Gets or sets a value indicating whether this scene graph node is visible. + /// Gets or sets a value indicating whether this control is visible. /// bool IsVisible { get; set; } /// - /// Gets or sets the opacity of the scene graph node. + /// Gets or sets the opacity of the control. /// double Opacity { get; set; } /// - /// Gets or sets the opacity mask of the scene graph node. + /// Gets or sets the opacity mask for the control. /// IBrush OpacityMask { get; set; } /// - /// Gets or sets the render transform of the scene graph node. + /// Gets or sets the render transform of the control. /// Transform RenderTransform { get; set; } /// - /// Gets or sets the render transform origin of the scene graph node. + /// Gets or sets the render transform origin of the control. /// RelativePoint RenderTransformOrigin { get; set; } /// - /// Gets the scene graph node's child nodes. + /// Gets the control's child visuals. /// IAvaloniaReadOnlyList VisualChildren { get; } /// - /// Gets the scene graph node's parent node. + /// Gets the control's parent visual. /// IVisual VisualParent { get; } @@ -107,7 +107,7 @@ namespace Avalonia.VisualTree void InvalidateVisual(); /// - /// Renders the scene graph node to a . + /// Renders the control to a . /// /// The context. void Render(DrawingContext context); diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index e7876b762f..2af267aa97 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Rendering; namespace Avalonia.VisualTree { @@ -20,6 +21,8 @@ namespace Avalonia.VisualTree /// The common ancestor, or null if not found. public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target) { + Contract.Requires(visual != null); + return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors()) .FirstOrDefault(); } @@ -49,6 +52,8 @@ namespace Avalonia.VisualTree /// The visual and its ancestors. public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual) { + Contract.Requires(visual != null); + yield return visual; foreach (var ancestor in visual.GetVisualAncestors()) @@ -102,26 +107,9 @@ namespace Avalonia.VisualTree { Contract.Requires(visual != null); - if (filter?.Invoke(visual) != false) - { - bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true; - - if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Any()) - { - foreach (var child in visual.VisualChildren.SortByZIndex()) - { - foreach (var result in child.GetVisualsAt(p, filter)) - { - yield return result; - } - } - } - - if (containsPoint) - { - yield return visual; - } - } + var root = visual.GetVisualRoot(); + p = visual.TranslatePoint(p, root); + return root.Renderer.HitTest(p, filter); } /// @@ -197,11 +185,11 @@ namespace Avalonia.VisualTree /// /// The root visual or null if the visual is not rooted. /// - public static IVisual GetVisualRoot(this IVisual visual) + public static IRenderRoot GetVisualRoot(this IVisual visual) { Contract.Requires(visual != null); - return visual.VisualRoot as IVisual; + return visual.VisualRoot as IRenderRoot ?? visual.VisualRoot; } /// diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index d1f8d5b912..d6ffb32d1c 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -198,7 +198,7 @@ namespace Avalonia.Controls.UnitTests public bool IsClosed { get; private set; } public TestWindowBase() - : base(Mock.Of()) + : base(Mock.Of(x => x.Scaling == 1)) { } diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 1aaba9cf6b..ecd27b63bd 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -4,18 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.dotMemoryUnit; -using Avalonia.Collections; using Avalonia.Controls; -using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Diagnostics; using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; +using JetBrains.dotMemoryUnit; using Moq; using Xunit; using Xunit.Abstractions; @@ -57,7 +54,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -95,7 +91,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -134,7 +129,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -172,7 +166,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -218,7 +211,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -309,7 +301,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -343,14 +334,6 @@ namespace Avalonia.LeakTests } } - private static void PurgeMoqReferences() - { - // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called; - // clear these. - var renderer = Mock.Get(AvaloniaLocator.Current.GetService()); - renderer.ResetCalls(); - } - private class Node { public string Name { get; set; } diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index 064d5d36d3..8a711c415e 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -8,12 +8,22 @@ using Avalonia.Layout; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; +using Moq; namespace Avalonia.UnitTests { public class TestRoot : Decorator, IFocusScope, ILayoutRoot, INameScope, IRenderRoot, IStyleRoot { private readonly NameScope _nameScope = new NameScope(); + private readonly IRenderTarget _renderTarget = Mock.Of( + x => x.CreateDrawingContext(It.IsAny()) == Mock.Of()); + + public TestRoot() + { + var rendererFactory = AvaloniaLocator.Current.GetService(); + var renderLoop = AvaloniaLocator.Current.GetService(); + Renderer = rendererFactory?.CreateRenderer(this, renderLoop); + } event EventHandler INameScope.Registered { @@ -41,16 +51,12 @@ namespace Avalonia.UnitTests public IRenderTarget RenderTarget => null; - public IRenderer Renderer => null; + public IRenderer Renderer { get; set; } - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } + public IRenderTarget CreateRenderTarget() => _renderTarget; public void Invalidate(Rect rect) { - throw new NotImplementedException(); } public Point PointToClient(Point p) => p;