Browse Source

Merge pull request #968 from AvaloniaUI/fix-failing-visuals-tests

Fix failing visuals tests
pull/974/head
Nikita Tsukanov 9 years ago
committed by GitHub
parent
commit
6d7bc4a37f
  1. 2
      appveyor.yml
  2. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  3. 2
      src/Avalonia.Visuals/Media/LinearGradientBrush.cs
  4. 4
      src/Avalonia.Visuals/Media/RadialGradientBrush.cs
  5. 60
      src/Avalonia.Visuals/Visual.cs
  6. 30
      src/Avalonia.Visuals/VisualTree/IVisual.cs
  7. 32
      src/Avalonia.Visuals/VisualTree/VisualExtensions.cs
  8. 2
      src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj
  9. 133
      src/Skia/Avalonia.Skia.iOS/RenderTarget.cs
  10. 23
      src/Skia/Avalonia.Skia.iOS/WindowDrawingContextImpl.cs
  11. 1
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  12. 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  13. 2
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  14. 59
      tests/Avalonia.LeakTests/ControlTests.cs
  15. 18
      tests/Avalonia.UnitTests/TestRoot.cs
  16. 193
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

2
appveyor.yml

@ -13,6 +13,8 @@ environment:
MYGET_API_KEY:
secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK
MYGET_API_URL: https://www.myget.org/F/avalonia-ci/api/v2/package
init:
- ps: (New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/appveyor/ci/master/scripts/xamarin-vs2017-151-fixed.targets', "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Microsoft.Common.Targets\ImportAfter\Xamarin.Common.targets")
install:
- if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
- if not exist dotnet-1.0.1.exe appveyor DownloadFile https://go.microsoft.com/fwlink/?linkid=843448 -FileName "dotnet-1.0.1.exe"

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
src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj

@ -37,10 +37,8 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="RenderTarget.cs" />
<Compile Include="SkiaView.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="WindowDrawingContextImpl.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />

133
src/Skia/Avalonia.Skia.iOS/RenderTarget.cs

@ -1,133 +0,0 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
using CoreGraphics;
using UIKit;
namespace Avalonia.Skia
{
internal partial class RenderTarget : IRenderTarget
{
public SKSurface Surface { get; protected set; }
public virtual DrawingContext CreateDrawingContext()
{
return
new DrawingContext(
new DrawingContextImpl(Surface.Canvas));
}
public void Dispose()
{
// Nothing to do here.
}
}
internal class WindowRenderTarget : RenderTarget
{
SKBitmap _bitmap;
int Width { get; set; }
int Height { get; set; }
public WindowRenderTarget()
{
FixSize();
}
private CGRect GetApplicationFrame()
{
// if we are excluding Status Bar then we use ApplicationFrame
// otherwise we use full screen bounds. Note that this must also match
// the Skia/AvaloniaView!!!
//
bool excludeStatusArea = false; // TODO: make this configurable later
if (excludeStatusArea)
{
return UIScreen.MainScreen.ApplicationFrame;
}
else
{
return UIScreen.MainScreen.Bounds;
}
}
private void FixSize()
{
int width, height;
GetPlatformWindowSize(out width, out height);
if (Width == width && Height == height)
return;
Width = width;
Height = height;
if (Surface != null)
{
Surface.Dispose();
}
if (_bitmap != null)
{
_bitmap.Dispose();
}
_bitmap = new SKBitmap(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul);
IntPtr length;
var pixels = _bitmap.GetPixels(out length);
// Wrap the bitmap in a Surface and keep it cached
Surface = SKSurface.Create(_bitmap.Info, pixels, _bitmap.RowBytes);
}
private void GetPlatformWindowSize(out int w, out int h)
{
var bounds = GetApplicationFrame();
w = (int)bounds.Width;
h = (int)bounds.Height;
}
public override DrawingContext CreateDrawingContext()
{
FixSize();
var canvas = Surface.Canvas;
canvas.RestoreToCount(0);
canvas.Save();
var screenScale = UIScreen.MainScreen.Scale;
canvas.Scale((float)screenScale, (float)screenScale);
canvas.Clear(SKColors.Red);
canvas.ResetMatrix();
return new DrawingContext(new WindowDrawingContextImpl(this));
}
public void Present()
{
_bitmap.LockPixels();
IntPtr length;
var pixels = _bitmap.GetPixels(out length);
const int bitmapInfo = ((int)CGBitmapFlags.ByteOrder32Big) | ((int)CGImageAlphaInfo.PremultipliedLast);
var bounds = GetApplicationFrame();
var statusBarOffset = UIScreen.MainScreen.Bounds.Height - bounds.Height;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(pixels, _bitmap.Width, _bitmap.Height, 8, _bitmap.Width * 4, colorSpace, (CGImageAlphaInfo)bitmapInfo))
using (var image = bContext.ToImage())
using (var context = UIGraphics.GetCurrentContext())
{
// flip the image for CGContext.DrawImage
context.TranslateCTM(0, bounds.Height + statusBarOffset);
context.ScaleCTM(1, -1);
context.DrawImage(bounds, image);
}
_bitmap.UnlockPixels();
}
}
}

23
src/Skia/Avalonia.Skia.iOS/WindowDrawingContextImpl.cs

@ -1,23 +0,0 @@
namespace Avalonia.Skia
{
#if !DESKTOP
// not sure we need this yet
internal class WindowDrawingContextImpl : DrawingContextImpl
{
WindowRenderTarget _target;
public WindowDrawingContextImpl(WindowRenderTarget target)
: base(target.Surface.Canvas)
{
_target = target;
}
public override void Dispose()
{
base.Dispose();
_target.Present();
}
}
#endif
}

1
src/Skia/Avalonia.Skia/BitmapImpl.cs

@ -30,6 +30,7 @@ namespace Avalonia.Skia
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
colorType = SKColorType.Bgra8888;
Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul);
Bitmap.Erase(SKColor.Empty);
}
public void Dispose()

4
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -17,8 +17,7 @@ namespace Avalonia.Direct2D1.Media
public class WicBitmapImpl : BitmapImpl
{
private readonly ImagingFactory _factory;
private SharpDX.Direct2D1.Bitmap _direct2D;
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
@ -101,7 +100,6 @@ namespace Avalonia.Direct2D1.Media
public override void Dispose()
{
WicImpl.Dispose();
_direct2D?.Dispose();
}
/// <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))
{
}

59
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;
@ -33,7 +30,7 @@ namespace Avalonia.LeakTests
[Fact]
public void Canvas_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -67,7 +63,7 @@ namespace Avalonia.LeakTests
[Fact]
public void Named_Canvas_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -105,7 +100,7 @@ namespace Avalonia.LeakTests
[Fact]
public void ScrollViewer_With_Content_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -146,7 +140,7 @@ namespace Avalonia.LeakTests
[Fact]
public void TextBox_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -182,7 +175,7 @@ namespace Avalonia.LeakTests
[Fact]
public void TextBox_With_Xaml_Binding_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -230,7 +222,7 @@ namespace Avalonia.LeakTests
[Fact]
public void TextBox_Class_Listeners_Are_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
TextBox textBox;
@ -266,7 +258,7 @@ namespace Avalonia.LeakTests
[Fact]
public void TreeView_Is_Freed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
Func<Window> run = () =>
{
@ -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));
@ -320,7 +311,7 @@ namespace Avalonia.LeakTests
[Fact]
public void RendererIsDisposed()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (Start())
{
var renderer = new Mock<IRenderer>();
renderer.Setup(x => x.Dispose());
@ -343,12 +334,10 @@ namespace Avalonia.LeakTests
}
}
private static void PurgeMoqReferences()
private IDisposable Start()
{
// 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();
var services = TestServices.StyledWindow.With(renderer: (root, loop) => new NullRenderer());
return UnitTestApplication.Start(services);
}
private class Node
@ -356,5 +345,29 @@ namespace Avalonia.LeakTests
public string Name { get; set; }
public IEnumerable<Node> Children { get; set; }
}
private class NullRenderer : IRenderer
{
public bool DrawFps { get; set; }
public bool DrawDirtyRects { get; set; }
public void AddDirty(IVisual visual)
{
}
public void Dispose()
{
}
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter) => null;
public void Paint(Rect rect)
{
}
public void Resized(Size size)
{
}
}
}
}

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;

193
tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs → tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

@ -1,30 +1,27 @@
// 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 System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
using System;
using Avalonia.Controls.Shapes;
namespace Avalonia.Visuals.UnitTests.VisualTree
namespace Avalonia.Visuals.UnitTests.Rendering
{
public class VisualExtensionsTests_GetVisualsAt
public class ImmediateRendererTests_HitTesting
{
[Fact]
public void GetVisualsAt_Should_Find_Controls_At_Point()
public void HitTest_Should_Find_Controls_At_Point()
{
using (TestApplication())
{
var container = new TestRoot
var root = new TestRoot
{
Width = 200,
Height = 200,
@ -38,49 +35,23 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { container.Child }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Empty_Controls_At_Point()
{
using (TestApplication())
{
var container = new TestRoot
{
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));
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(100, 100));
var result = root.Renderer.HitTest(new Point(100, 100), null);
Assert.Empty(result);
Assert.Equal(new[] { root.Child, root }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Invisible_Controls_At_Point()
public void HitTest_Should_Not_Find_Invisible_Controls_At_Point()
{
using (TestApplication())
{
Border visible;
var container = new TestRoot
var root = new TestRoot
{
Width = 200,
Height = 200,
@ -101,21 +72,22 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(100, 100));
var result = root.Renderer.HitTest(new Point(100, 100), null);
Assert.Empty(result);
Assert.Equal(new[] { root }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Point()
public void HitTest_Should_Not_Find_Control_Outside_Point()
{
using (TestApplication())
{
var container = new TestRoot
var root = new TestRoot
{
Width = 200,
Height = 200,
@ -129,17 +101,18 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(10, 10));
var result = root.Renderer.HitTest(new Point(10, 10), null);
Assert.Empty(result);
Assert.Equal(new[] { root }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Return_Top_Controls_First()
public void HitTest_Should_Return_Top_Controls_First()
{
using (TestApplication())
{
@ -174,15 +147,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(100, 100));
var result = root.Renderer.HitTest(new Point(100, 100), null);
Assert.Equal(new[] { container.Children[1], container.Children[0] }, result);
Assert.Equal(new[] { container.Children[1], container.Children[0], container, root }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Return_Top_Controls_First_With_ZIndex()
public void HitTest_Should_Return_Top_Controls_First_With_ZIndex()
{
using (TestApplication())
{
@ -227,15 +201,25 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(100, 100));
var result = root.Renderer.HitTest(new Point(100, 100), null);
Assert.Equal(new[] { container.Children[2], container.Children[0], container.Children[1] }, result);
Assert.Equal(
new[]
{
container.Children[2],
container.Children[0],
container.Children[1],
container,
root
},
result);
}
}
[Fact]
public void GetVisualsAt_Should_Find_Control_Translated_Outside_Parent_Bounds()
public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds()
{
using (TestApplication())
{
@ -275,15 +259,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(120, 120));
var result = root.Renderer.HitTest(new Point(120, 120), null);
Assert.Equal(new IVisual[] { target, container }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped()
{
using (TestApplication())
{
@ -322,15 +307,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(50, 50));
var result = root.Renderer.HitTest(new Point(50, 50), null);
Assert.Equal(new[] { container }, result);
Assert.Equal(new IVisual[] { container, root }, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Control_Outside_Scroll_Viewport()
public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport()
{
using (TestApplication())
{
@ -394,12 +380,13 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
root.Measure(Size.Infinity);
root.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
var result = container.GetVisualsAt(new Point(50, 150)).First();
var result = root.Renderer.HitTest(new Point(50, 150), null).First();
Assert.Equal(item1, result);
result = container.GetVisualsAt(new Point(50, 50)).First();
result = root.Renderer.HitTest(new Point(50, 50), null).First();
Assert.Equal(target, result);
@ -409,86 +396,16 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
scroll.Parent.InvalidateArrange();
container.InvalidateArrange();
container.Arrange(new Rect(container.DesiredSize));
root.Renderer.Paint(new Rect(root.ClientSize));
result = container.GetVisualsAt(new Point(50, 150)).First();
result = root.Renderer.HitTest(new Point(50, 150), null).First();
Assert.Equal(item2, result);
result = container.GetVisualsAt(new Point(50, 50)).First();
result = root.Renderer.HitTest(new Point(50, 50), null).First();
Assert.Equal(target, result);
}
}
[Fact]
public void GetVisualsAt_Should_Not_Find_Path_When_Outside_Fill()
{
using (TestApplication())
{
Path path;
var container = new TestRoot
{
Width = 200,
Height = 200,
Child = path = new Path
{
Width = 200,
Height = 200,
Fill = Brushes.Red,
Data = StreamGeometry.Parse("M100,0 L0,100 100,100")
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
var result = container.GetVisualsAt(new Point(100, 100));
Assert.Equal(new[] { path }, result);
result = container.GetVisualsAt(new Point(10, 10));
Assert.Empty(result);
}
}
[Fact]
public void GetVisualsAt_Should_Respect_Geometry_Clip()
{
using (TestApplication())
{
Border border;
Canvas canvas;
var container = new TestRoot
{
Width = 400,
Height = 400,
Child = border = new Border
{
Background = Brushes.Red,
Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"),
Width = 200,
Height = 200,
Child = canvas = new Canvas
{
Background = Brushes.Yellow,
Margin = new Thickness(10),
}
}
};
container.Measure(Size.Infinity);
container.Arrange(new Rect(container.DesiredSize));
Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds);
var context = new DrawingContext(Mock.Of<IDrawingContextImpl>());
var result = container.GetVisualsAt(new Point(200, 200));
Assert.Equal(new IVisual[] { canvas, border }, result);
result = container.GetVisualsAt(new Point(110, 110));
Assert.Empty(result);
}
}
private IDisposable TestApplication()
{
return UnitTestApplication.Start(
Loading…
Cancel
Save