diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index 786353f05c..5acb6aa777 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -93,17 +93,17 @@ namespace Avalonia.Input bool IsEnabledCore { get; } /// - /// Gets or sets a value indicating whether the control is focused. + /// Gets a value indicating whether the control is focused. /// bool IsFocused { get; } /// - /// Gets or sets a value indicating whether the control is considered for hit testing. + /// Gets a value indicating whether the control is considered for hit testing. /// bool IsHitTestVisible { get; } /// - /// Gets or sets a value indicating whether the pointer is currently over the control. + /// Gets a value indicating whether the pointer is currently over the control. /// bool IsPointerOver { get; } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 2c0cb8656c..cbcb4382ec 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -20,7 +20,6 @@ namespace Avalonia.Input private int _clickCount; private Rect _lastClickRect; private uint _lastClickTime; - private readonly List _pointerOvers = new List(); /// /// Intializes a new instance of . @@ -87,6 +86,8 @@ namespace Avalonia.Input /// The mouse position in the control's coordinates. public Point GetPosition(IVisual relativeTo) { + Contract.Requires(relativeTo != null); + Point p = default(Point); IVisual v = relativeTo; IVisual root = null; @@ -103,6 +104,8 @@ namespace Avalonia.Input private void ProcessRawEvent(RawMouseEventArgs e) { + Contract.Requires(e != null); + var mouse = (IMouseDevice)e.Device; Position = e.Root.PointToScreen(e.Position); @@ -141,11 +144,17 @@ namespace Avalonia.Input private void LeaveWindow(IMouseDevice device, IInputRoot root) { + Contract.Requires(device != null); + Contract.Requires(root != null); + ClearPointerOver(this, root); } private bool MouseDown(IMouseDevice device, uint timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers) { + Contract.Requires(device != null); + Contract.Requires(root != null); + var hit = HitTest(root, p); if (hit != null) @@ -187,6 +196,9 @@ namespace Avalonia.Input private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers) { + Contract.Requires(device != null); + Contract.Requires(root != null); + IInputElement source; if (Captured == null) @@ -195,8 +207,7 @@ namespace Avalonia.Input } else { - var elements = Captured.GetSelfAndVisualAncestors().OfType().ToList(); - SetPointerOver(this, root, elements); + SetPointerOver(this, root, Captured); source = Captured; } @@ -208,12 +219,15 @@ namespace Avalonia.Input InputModifiers = inputModifiers }; - source.RaiseEvent(e); + source?.RaiseEvent(e); return e.Handled; } private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers) { + Contract.Requires(device != null); + Contract.Requires(root != null); + var hit = HitTest(root, p); if (hit != null) @@ -237,6 +251,9 @@ namespace Avalonia.Input private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers) { + Contract.Requires(device != null); + Contract.Requires(root != null); + var hit = HitTest(root, p); if (hit != null) @@ -260,6 +277,8 @@ namespace Avalonia.Input private IInteractive GetSource(IVisual hit) { + Contract.Requires(hit != null); + return Captured ?? (hit as IInteractive) ?? hit.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); @@ -267,22 +286,28 @@ namespace Avalonia.Input private IInputElement HitTest(IInputElement root, Point p) { + Contract.Requires(root != null); + return Captured ?? root.InputHitTest(p); } private void ClearPointerOver(IPointerDevice device, IInputRoot root) { - foreach (var control in _pointerOvers.ToList()) + Contract.Requires(device != null); + Contract.Requires(root != null); + + var element = root.PointerOverElement; + var e = new PointerEventArgs { - PointerEventArgs e = new PointerEventArgs - { - RoutedEvent = InputElement.PointerLeaveEvent, - Device = device, - Source = control, - }; + RoutedEvent = InputElement.PointerLeaveEvent, + Device = device, + }; - _pointerOvers.Remove(control); - control.RaiseEvent(e); + while (element != null) + { + e.Source = element; + element.RaiseEvent(e); + element = (IInputElement)element.VisualParent; } root.PointerOverElement = null; @@ -290,40 +315,66 @@ namespace Avalonia.Input private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p) { - var elements = root.GetInputElementsAt(p).ToList(); - return SetPointerOver(device, root, elements); + Contract.Requires(device != null); + Contract.Requires(root != null); + + var element = root.InputHitTest(p); + + if (element != root.PointerOverElement) + { + if (element != null) + { + SetPointerOver(device, root, element); + } + else + { + ClearPointerOver(device, root); + } + } + + return element; } - private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, IList elements) + private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element) { - foreach (var control in _pointerOvers.Except(elements).ToList()) + Contract.Requires(device != null); + Contract.Requires(root != null); + Contract.Requires(element != null); + + IInputElement branch = null; + + var e = new PointerEventArgs { - PointerEventArgs e = new PointerEventArgs - { - RoutedEvent = InputElement.PointerLeaveEvent, - Device = device, - Source = control, - }; + RoutedEvent = InputElement.PointerEnterEvent, + Device = device, + }; - _pointerOvers.Remove(control); - control.RaiseEvent(e); - } + var el = element; - foreach (var control in elements.Except(_pointerOvers)) + while (el != null) { - PointerEventArgs e = new PointerEventArgs + if (el.IsPointerOver) { - RoutedEvent = InputElement.PointerEnterEvent, - Device = device, - Source = control, - }; + branch = el; + break; + } - _pointerOvers.Add(control); - control.RaiseEvent(e); + e.Source = el; + el.RaiseEvent(e); + el = (IInputElement)el.VisualParent; + } + + el = root.PointerOverElement; + e.RoutedEvent = InputElement.PointerLeaveEvent; + + while (el != null && el != branch) + { + e.Source = el; + el.RaiseEvent(e); + el = (IInputElement)el.VisualParent; } - root.PointerOverElement = elements.FirstOrDefault() ?? root; - return root.PointerOverElement; + root.PointerOverElement = element; } } -} +} \ No newline at end of file