Browse Source

Merge pull request #2067 from ahopper/fix-pointerleave-not-always-set

allow for control being removed from visual tree in :pointerover logic
pull/2252/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
2b6a65a199
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/Avalonia.Controls/TopLevel.cs
  2. 2
      src/Avalonia.Input/IPointerDevice.cs
  3. 51
      src/Avalonia.Input/MouseDevice.cs
  4. 18
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  5. 13
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  6. 5
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  7. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  8. 36
      src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs
  9. 1
      tests/Avalonia.LeakTests/ControlTests.cs

12
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);
}
/// <inheritdoc/>
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);
}
}
}

2
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);
}
}

51
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<ArgumentNullException>(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<ArgumentNullException>(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;

18
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -99,6 +99,9 @@ namespace Avalonia.Rendering
/// </summary>
public string DebugFramesPath { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Gets the render layers.
/// </summary>
@ -506,6 +509,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

13
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -18,11 +18,20 @@ namespace Avalonia.Rendering
bool DrawFps { get; set; }
/// <summary>
/// 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.
/// </summary>
bool DrawDirtyRects { get; set; }
/// <summary>
/// Raised when a portion of the scene has been invalidated.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Mark a visual as dirty and needing re-rendering.
/// </summary>
@ -63,4 +72,4 @@ namespace Avalonia.Rendering
/// </summary>
void Stop();
}
}
}

5
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -42,6 +42,9 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public bool DrawDirtyRects { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <inheritdoc/>
public void Paint(Rect rect)
{
@ -81,6 +84,8 @@ namespace Avalonia.Rendering
_renderTarget.Dispose();
_renderTarget = null;
}
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
/// <inheritdoc/>

4
src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs

@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> 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;
}

36
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
{
/// <summary>
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
/// </summary>
public class SceneInvalidatedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
/// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
{
RenderRoot = root;
DirtyRect = dirtyRect;
}
/// <summary>
/// Gets the invalidated area.
/// </summary>
public Rect DirtyRect { get; }
/// <summary>
/// Gets the render root that has been invalidated.
/// </summary>
public IRenderRoot RenderRoot { get; }
}
}

1
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<SceneInvalidatedEventArgs> SceneInvalidated;
public void AddDirty(IVisual visual)
{

Loading…
Cancel
Save