Browse Source

Fixed some Visuals unit tests.

Ported some stuff from `scenegraph` that was missed in #956, which fixes
some of the failing Avalonia.Visuals.UnitTests.
pull/968/head
Steven Kirk 10 years ago
parent
commit
69a06e452e
  1. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  2. 2
      src/Avalonia.Visuals/Media/LinearGradientBrush.cs
  3. 4
      src/Avalonia.Visuals/Media/RadialGradientBrush.cs
  4. 60
      src/Avalonia.Visuals/Visual.cs
  5. 30
      src/Avalonia.Visuals/VisualTree/IVisual.cs
  6. 32
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  7. 2
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  8. 19
      tests/Avalonia.LeakTests/ControlTests.cs
  9. 18
      tests/Avalonia.UnitTests/TestRoot.cs

1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>

2
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
{
/// <summary>

4
src/Avalonia.Visuals/Media/RadialGradientBrush.cs

@ -4,8 +4,7 @@
namespace Avalonia.Media
{
/// <summary>
/// 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.
/// </summary>
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.
/// </summary>
// TODO: This appears to always be relative so should use a RelativeSize struct or something.
public double Radius
{
get { return GetValue(RadiusProperty); }

60
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.
/// </summary>
/// <remarks>
/// The <see cref="Visual"/> class acts as a node in the Avalonia scene graph and holds
/// all the information needed for an <see cref="IRenderTarget"/> to render the control.
/// To traverse the scene graph (aka Visual Tree), use the extension methods defined
/// in <see cref="VisualExtensions"/>.
/// The <see cref="Visual"/> class represents elements that have a visual on-screen
/// representation and stores all the information needed for an
/// <see cref="IRenderer"/> to render the control. To traverse the visual tree, use the
/// extension methods defined in <see cref="VisualExtensions"/>.
/// </remarks>
public class Visual : Animatable, IVisual
{
@ -88,6 +87,7 @@ namespace Avalonia
AvaloniaProperty.Register<Visual, int>(nameof(ZIndex));
private Rect _bounds;
private IRenderRoot _visualRoot;
private IVisual _visualParent;
/// <summary>
@ -95,7 +95,12 @@ namespace Avalonia
/// </summary>
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<VisualTreeAttachmentEventArgs> DetachedFromVisualTree;
/// <summary>
/// Gets the bounds of the scene graph node relative to its parent.
/// Gets the bounds of the control relative to its parent.
/// </summary>
public Rect Bounds
{
@ -131,7 +136,7 @@ namespace Avalonia
}
/// <summary>
/// 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.
/// </summary>
public bool ClipToBounds
{
@ -149,7 +154,7 @@ namespace Avalonia
}
/// <summary>
/// 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.
/// </summary>
public bool IsEffectivelyVisible
{
@ -157,7 +162,7 @@ namespace Avalonia
}
/// <summary>
/// Gets a value indicating whether this scene graph node is visible.
/// Gets a value indicating whether this control is visible.
/// </summary>
public bool IsVisible
{
@ -166,7 +171,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the opacity of the scene graph node.
/// Gets the opacity of the control.
/// </summary>
public double Opacity
{
@ -174,9 +179,8 @@ namespace Avalonia
set { SetValue(OpacityProperty, value); }
}
/// <summary>
/// Gets the opacity mask of the scene graph node.
/// Gets the opacity mask of the control.
/// </summary>
public IBrush OpacityMask
{
@ -185,7 +189,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the render transform of the scene graph node.
/// Gets the render transform of the control.
/// </summary>
public Transform RenderTransform
{
@ -194,7 +198,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the transform origin of the scene graph node.
/// Gets the transform origin of the control.
/// </summary>
public RelativePoint RenderTransformOrigin
{
@ -203,7 +207,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the Z index of the node.
/// Gets the Z index of the control.
/// </summary>
/// <remarks>
/// Controls with a higher <see cref="ZIndex"/> will appear in front of controls with
@ -217,7 +221,7 @@ namespace Avalonia
}
/// <summary>
/// Gets the control's visual children.
/// Gets the control's child visuals.
/// </summary>
protected IAvaloniaList<IVisual> VisualChildren
{
@ -228,24 +232,20 @@ namespace Avalonia
/// <summary>
/// Gets the root of the visual tree, if the control is attached to a visual tree.
/// </summary>
protected IRenderRoot VisualRoot
{
get;
private set;
}
protected IRenderRoot VisualRoot => _visualRoot ?? (this as IRenderRoot);
/// <summary>
/// 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.
/// </summary>
bool IVisual.IsAttachedToVisualTree => VisualRoot != null;
/// <summary>
/// Gets the scene graph node's child nodes.
/// Gets the control's child controls.
/// </summary>
IAvaloniaReadOnlyList<IVisual> IVisual.VisualChildren => VisualChildren;
/// <summary>
/// Gets the scene graph node's parent node.
/// Gets the control's parent visual.
/// </summary>
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);

30
src/Avalonia.Visuals/VisualTree/IVisual.cs

@ -9,13 +9,13 @@ using Avalonia.Rendering;
namespace Avalonia.VisualTree
{
/// <summary>
/// Represents a node in the visual scene graph.
/// Represents control that has a visual on-screen representation.
/// </summary>
/// <remarks>
/// The <see cref="IVisual"/> 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
/// <see cref="VisualExtensions"/> to traverse the scene graph. This interface is
/// <see cref="VisualExtensions"/> to traverse the visual tree. This interface is
/// implemented by <see cref="Visual"/>. It should not be necessary to implement it
/// anywhere else.
/// </remarks>
@ -32,12 +32,12 @@ namespace Avalonia.VisualTree
event EventHandler<VisualTreeAttachmentEventArgs> DetachedFromVisualTree;
/// <summary>
/// Gets the bounds of the scene graph node relative to its parent.
/// Gets the bounds of the control relative to its parent.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// 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.
/// </summary>
bool ClipToBounds { get; set; }
@ -47,47 +47,47 @@ namespace Avalonia.VisualTree
Geometry Clip { get; set; }
/// <summary>
/// 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.
/// </summary>
bool IsAttachedToVisualTree { get; }
/// <summary>
/// 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.
/// </summary>
bool IsEffectivelyVisible { get; }
/// <summary>
/// Gets or sets a value indicating whether this scene graph node is visible.
/// Gets or sets a value indicating whether this control is visible.
/// </summary>
bool IsVisible { get; set; }
/// <summary>
/// Gets or sets the opacity of the scene graph node.
/// Gets or sets the opacity of the control.
/// </summary>
double Opacity { get; set; }
/// <summary>
/// Gets or sets the opacity mask of the scene graph node.
/// Gets or sets the opacity mask for the control.
/// </summary>
IBrush OpacityMask { get; set; }
/// <summary>
/// Gets or sets the render transform of the scene graph node.
/// Gets or sets the render transform of the control.
/// </summary>
Transform RenderTransform { get; set; }
/// <summary>
/// Gets or sets the render transform origin of the scene graph node.
/// Gets or sets the render transform origin of the control.
/// </summary>
RelativePoint RenderTransformOrigin { get; set; }
/// <summary>
/// Gets the scene graph node's child nodes.
/// Gets the control's child visuals.
/// </summary>
IAvaloniaReadOnlyList<IVisual> VisualChildren { get; }
/// <summary>
/// Gets the scene graph node's parent node.
/// Gets the control's parent visual.
/// </summary>
IVisual VisualParent { get; }
@ -107,7 +107,7 @@ namespace Avalonia.VisualTree
void InvalidateVisual();
/// <summary>
/// Renders the scene graph node to a <see cref="DrawingContext"/>.
/// Renders the control to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The context.</param>
void Render(DrawingContext context);

32
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
/// <returns>The common ancestor, or null if not found.</returns>
public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target)
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.GetSelfAndVisualAncestors().Intersect(target.GetSelfAndVisualAncestors())
.FirstOrDefault();
}
@ -49,6 +52,8 @@ namespace Avalonia.VisualTree
/// <returns>The visual and its ancestors.</returns>
public static IEnumerable<IVisual> GetSelfAndVisualAncestors(this IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
yield return visual;
foreach (var ancestor in visual.GetVisualAncestors())
@ -102,26 +107,9 @@ namespace Avalonia.VisualTree
{
Contract.Requires<ArgumentNullException>(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);
}
/// <summary>
@ -197,11 +185,11 @@ namespace Avalonia.VisualTree
/// <returns>
/// The root visual or null if the visual is not rooted.
/// </returns>
public static IVisual GetVisualRoot(this IVisual visual)
public static IRenderRoot GetVisualRoot(this IVisual visual)
{
Contract.Requires<ArgumentNullException>(visual != null);
return visual.VisualRoot as IVisual;
return visual.VisualRoot as IRenderRoot ?? visual.VisualRoot;
}
/// <summary>

2
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<IWindowBaseImpl>())
: base(Mock.Of<IWindowBaseImpl>(x => x.Scaling == 1))
{
}

19
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<Canvas>()).ObjectsCount));
@ -95,7 +91,6 @@ namespace Avalonia.LeakTests
};
var result = run();
PurgeMoqReferences();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Canvas>()).ObjectsCount));
@ -134,7 +129,6 @@ namespace Avalonia.LeakTests
};
var result = run();
PurgeMoqReferences();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@ -172,7 +166,6 @@ namespace Avalonia.LeakTests
};
var result = run();
PurgeMoqReferences();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@ -218,7 +211,6 @@ namespace Avalonia.LeakTests
};
var result = run();
PurgeMoqReferences();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBox>()).ObjectsCount));
@ -309,7 +301,6 @@ namespace Avalonia.LeakTests
};
var result = run();
PurgeMoqReferences();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TreeView>()).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<IRenderer>());
renderer.ResetCalls();
}
private class Node
{
public string Name { get; set; }

18
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<IRenderTarget>(
x => x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
public TestRoot()
{
var rendererFactory = AvaloniaLocator.Current.GetService<IRendererFactory>();
var renderLoop = AvaloniaLocator.Current.GetService<IRenderLoop>();
Renderer = rendererFactory?.CreateRenderer(this, renderLoop);
}
event EventHandler<NameScopeEventArgs> 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;

Loading…
Cancel
Save