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 |
-|---|---|---|
-| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [](#backers) [](#sponsors) |
+| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet |
+|---|---|---|---|---|
+| [](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [](#backers) [](#sponsors) | [](https://www.nuget.org/packages/Avalonia) | [](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)
{