diff --git a/appveyor.yml b/appveyor.yml index 7f2b1bb395..4bf7f7f157 100644 --- a/appveyor.yml +++ b/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" 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/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj index 6b6604b88c..220fb37b81 100644 --- a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj +++ b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj @@ -37,10 +37,8 @@ true - - diff --git a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs deleted file mode 100644 index d097f35ce1..0000000000 --- a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/Skia/Avalonia.Skia.iOS/WindowDrawingContextImpl.cs b/src/Skia/Avalonia.Skia.iOS/WindowDrawingContextImpl.cs deleted file mode 100644 index db9d10a346..0000000000 --- a/src/Skia/Avalonia.Skia.iOS/WindowDrawingContextImpl.cs +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index 9d46855a58..e9c241b848 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/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() diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index e1fffd58d4..e817dd4812 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/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; + /// /// Initializes a new instance of the class. @@ -101,7 +100,6 @@ namespace Avalonia.Direct2D1.Media public override void Dispose() { WicImpl.Dispose(); - _direct2D?.Dispose(); } /// 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..b9bdee09e6 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; @@ -33,7 +30,7 @@ namespace Avalonia.LeakTests [Fact] public void Canvas_Is_Freed() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (Start()) { Func run = () => { @@ -57,7 +54,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -67,7 +63,7 @@ namespace Avalonia.LeakTests [Fact] public void Named_Canvas_Is_Freed() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (Start()) { Func run = () => { @@ -95,7 +91,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -105,7 +100,7 @@ namespace Avalonia.LeakTests [Fact] public void ScrollViewer_With_Content_Is_Freed() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (Start()) { Func run = () => { @@ -134,7 +129,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -146,7 +140,7 @@ namespace Avalonia.LeakTests [Fact] public void TextBox_Is_Freed() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (Start()) { Func run = () => { @@ -172,7 +166,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).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 run = () => { @@ -218,7 +211,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).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 run = () => { @@ -309,7 +301,6 @@ namespace Avalonia.LeakTests }; var result = run(); - PurgeMoqReferences(); dotMemory.Check(memory => Assert.Equal(0, memory.GetObjects(where => where.Type.Is()).ObjectsCount)); @@ -320,7 +311,7 @@ namespace Avalonia.LeakTests [Fact] public void RendererIsDisposed() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) + using (Start()) { var renderer = new Mock(); 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()); - 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 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 HitTest(Point p, Func filter) => null; + + public void Paint(Rect rect) + { + } + + public void Resized(Size size) + { + } + } } } 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; diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs similarity index 68% rename from tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs rename to tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs index 2ca5b8335a..fa53cba938 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/VisualExtensionsTests_GetVisualsAt.cs +++ b/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()); - - 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()); - - 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(