diff --git a/readme.md b/readme.md index 9280125323..12f683bd55 100644 --- a/readme.md +++ b/readme.md @@ -2,9 +2,9 @@ # Avalonia -| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | -|---|---|---| -| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | +| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet | +|---|---|---|---|---| +| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) | [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) | ## About diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 9be6d2004a..5ca3647da7 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -98,6 +98,11 @@ namespace Avalonia.Controls Renderer = impl.CreateRenderer(this); + if (Renderer != null) + { + Renderer.SceneInvalidated += SceneInvalidated; + } + impl.SetInputRoot(this); impl.Closed = HandleClosed; @@ -231,7 +236,7 @@ namespace Avalonia.Controls { PlatformImpl?.Invalidate(rect); } - + /// Point IRenderRoot.PointToClient(PixelPoint p) { @@ -349,5 +354,10 @@ namespace Avalonia.Controls { _inputManager.ProcessInput(e); } + + private void SceneInvalidated(object sender, SceneInvalidatedEventArgs e) + { + (this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect); + } } } diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs index 8613717f60..932cbc989f 100644 --- a/src/Avalonia.Input/IPointerDevice.cs +++ b/src/Avalonia.Input/IPointerDevice.cs @@ -12,5 +12,7 @@ namespace Avalonia.Input void Capture(IInputElement control); Point GetPosition(IVisual relativeTo); + + void SceneInvalidated(IInputRoot root, Rect rect); } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 4855dd0c56..7945ee8ee4 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -104,6 +104,23 @@ namespace Avalonia.Input ProcessRawEvent(margs); } + public void SceneInvalidated(IInputRoot root, Rect rect) + { + var clientPoint = root.PointToClient(Position); + + if (rect.Contains(clientPoint)) + { + if (Captured == null) + { + SetPointerOver(this, root, clientPoint); + } + else + { + SetPointerOver(this, root, Captured); + } + } + } + private void ProcessRawEvent(RawMouseEventArgs e) { Contract.Requires(e != null); @@ -305,16 +322,41 @@ namespace Avalonia.Input Device = device, }; + if (element!=null && !element.IsAttachedToVisualTree) + { + // element has been removed from visual tree so do top down cleanup + if (root.IsPointerOver) + ClearChildrenPointerOver(e, root,true); + } while (element != null) { e.Source = element; + e.Handled = false; element.RaiseEvent(e); element = (IInputElement)element.VisualParent; } - + root.PointerOverElement = null; } + private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot) + { + foreach (IInputElement el in element.VisualChildren) + { + if (el.IsPointerOver) + { + ClearChildrenPointerOver(e, el, true); + break; + } + } + if(clearRoot) + { + e.Source = element; + e.Handled = false; + element.RaiseEvent(e); + } + } + private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p) { Contract.Requires(device != null); @@ -361,13 +403,18 @@ namespace Avalonia.Input el = root.PointerOverElement; e.RoutedEvent = InputElement.PointerLeaveEvent; + if (el!=null && branch!=null && !el.IsAttachedToVisualTree) + { + ClearChildrenPointerOver(e,branch,false); + } + while (el != null && el != branch) { e.Source = el; e.Handled = false; el.RaiseEvent(e); el = (IInputElement)el.VisualParent; - } + } el = root.PointerOverElement = element; e.RoutedEvent = InputElement.PointerEnterEvent; diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index 9893091e7a..60e624948e 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -29,6 +29,7 @@ namespace Avalonia.Rendering private readonly ISceneBuilder _sceneBuilder; private bool _running; + private bool _disposed; private volatile IRef _scene; private DirtyVisuals _dirty; private IRef _overlay; @@ -99,6 +100,9 @@ namespace Avalonia.Rendering /// public string DebugFramesPath { get; set; } + /// + public event EventHandler SceneInvalidated; + /// /// Gets the render layers. /// @@ -122,6 +126,9 @@ namespace Avalonia.Rendering { lock (_sceneLock) { + if (_disposed) + return; + _disposed = true; var scene = _scene; _scene = null; scene?.Dispose(); @@ -168,7 +175,7 @@ namespace Avalonia.Rendering var t = (IRenderLoopTask)this; if(t.NeedsUpdate) UpdateScene(); - if(_scene.Item != null) + if(_scene?.Item != null) Render(true); } @@ -478,6 +485,8 @@ namespace Avalonia.Rendering Dispatcher.UIThread.VerifyAccess(); lock (_sceneLock) { + if (_disposed) + return; if (_scene?.Item.Generation > _lastSceneId) return; } @@ -506,6 +515,21 @@ namespace Avalonia.Rendering oldScene?.Dispose(); } + if (SceneInvalidated != null) + { + var rect = new Rect(); + + foreach (var layer in scene.Layers) + { + foreach (var dirty in layer.Dirty) + { + rect = rect.Union(dirty); + } + } + + SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); + } + _dirty.Clear(); } else diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index 9085e63aa9..36a1f7d220 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -18,11 +18,20 @@ namespace Avalonia.Rendering bool DrawFps { get; set; } /// - /// Gets or sets a value indicating whether the renderer should a visual representation + /// Gets or sets a value indicating whether the renderer should draw a visual representation /// of its dirty rectangles. /// bool DrawDirtyRects { get; set; } + /// + /// Raised when a portion of the scene has been invalidated. + /// + /// + /// Indicates that the underlying low-level scene information has been updated. Used to + /// signal that an update to the current pointer-over state may be required. + /// + event EventHandler SceneInvalidated; + /// /// Mark a visual as dirty and needing re-rendering. /// @@ -63,4 +72,4 @@ namespace Avalonia.Rendering /// void Stop(); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 03ac004fd5..21129e38af 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -42,6 +42,9 @@ namespace Avalonia.Rendering /// public bool DrawDirtyRects { get; set; } + /// + public event EventHandler SceneInvalidated; + /// public void Paint(Rect rect) { @@ -81,6 +84,8 @@ namespace Avalonia.Rendering _renderTarget.Dispose(); _renderTarget = null; } + + SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect)); } /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index ffa0b0bcc5..ad4c475d89 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph private IEnumerable HitTest(IVisualNode node, Point p, Rect? clip, Func filter) { - if (filter?.Invoke(node.Visual) != false) + if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree) { var clipped = false; @@ -186,7 +186,7 @@ namespace Avalonia.Rendering.SceneGraph } } - if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree) + if (node.HitTest(p)) { yield return node.Visual; } diff --git a/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs new file mode 100644 index 0000000000..40cb9f3356 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs @@ -0,0 +1,36 @@ +// 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.Rendering +{ + /// + /// Provides data for the event. + /// + public class SceneInvalidatedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The render root that has been updated. + /// The updated area. + public SceneInvalidatedEventArgs( + IRenderRoot root, + Rect dirtyRect) + { + RenderRoot = root; + DirtyRect = dirtyRect; + } + + /// + /// Gets the invalidated area. + /// + public Rect DirtyRect { get; } + + /// + /// Gets the render root that has been invalidated. + /// + public IRenderRoot RenderRoot { get; } + } +} diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 4dcd98cb93..b7aa452a18 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -348,6 +348,7 @@ namespace Avalonia.LeakTests { public bool DrawFps { get; set; } public bool DrawDirtyRects { get; set; } + public event EventHandler SceneInvalidated; public void AddDirty(IVisual visual) {