From 42b3639ecac6035b9e1137647e2c12daf95a0133 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Wed, 7 Jun 2023 08:20:29 -0700 Subject: [PATCH] Mac: fire MouseLeave if setting a control to disabled while mouse is over --- src/Eto.Mac/Forms/MacView.cs | 21 ++++++++++ .../UnitTests/Forms/Controls/ControlTests.cs | 42 ++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Eto.Mac/Forms/MacView.cs b/src/Eto.Mac/Forms/MacView.cs index 0f58dae09..ee727fe94 100644 --- a/src/Eto.Mac/Forms/MacView.cs +++ b/src/Eto.Mac/Forms/MacView.cs @@ -17,6 +17,7 @@ namespace Eto.Mac.Forms class MouseDelegate : NSObject { WeakReference widget; + bool entered; public IMacViewHandler Handler { get { return (IMacViewHandler)widget.Target; } set { widget = new WeakReference(value); } } @@ -31,6 +32,7 @@ public void MouseMoved(NSEvent theEvent) [Export("mouseEntered:")] public void MouseEntered(NSEvent theEvent) { + entered = true; var h = Handler; if (h == null || !h.Enabled) return; h.Callback.OnMouseEnter(h.Widget, MacConversions.GetMouseEvent(h, theEvent, false)); @@ -47,6 +49,7 @@ public void MouseExited(NSEvent theEvent) var h = Handler; if (h == null || !h.Enabled) return; h.Callback.OnMouseLeave(h.Widget, MacConversions.GetMouseEvent(h, theEvent, false)); + entered = false; } [Export("scrollWheel:")] @@ -57,6 +60,21 @@ public void ScrollWheel(NSEvent theEvent) h.Callback.OnMouseWheel(h.Widget, MacConversions.GetMouseEvent(h, theEvent, true)); } + public void FireMouseLeaveIfNeeded() + { + var h = Handler; + if (h == null || h.Enabled || !entered) return; + entered = false; + Application.Instance.AsyncInvoke(() => + { + if (!h.Widget.IsDisposed) + { + var theEvent = NSApplication.SharedApplication.CurrentEvent; + h.Callback.OnMouseLeave(h.Widget, MacConversions.GetMouseEvent(h, theEvent, false)); + } + }); + } + } public interface IMacViewHandler : IMacControlHandler @@ -1017,6 +1035,9 @@ void SetEnabled(bool parentEnabled, bool? newValue) { ControlEnabled = newEnabled; Callback.OnEnabledChanged(Widget, EventArgs.Empty); + + if (!newEnabled) + mouseDelegate?.FireMouseLeaveIfNeeded(); } } diff --git a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs index ce35bfc98..9613b9f32 100644 --- a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs @@ -409,7 +409,7 @@ public void ControlsShouldNotGetMouseOrFocusEventsWhenParentDisabled(IControlTyp bool gotMouseUp = false; bool gotMouseEnter = false; bool gotMouseLeave = false; - ManualForm("Click on the Drawable, it should not get focus", form => + ManualForm("Click on the control, it should not get focus", form => { var control = info.CreatePopulatedControl(); if (!disableWithParent) @@ -456,5 +456,45 @@ public void ControlsShouldNotGetMouseOrFocusEventsWhenParentDisabled(IControlTyp Assert.IsFalse(gotMouseDown, "#1.4 - Got MouseDown"); Assert.IsFalse(gotMouseUp, "#1.5 - Got MouseUp"); } + + [ManualTest] + [TestCaseSource(nameof(GetControlTypes))] + public void ControlShouldFireMouseLeaveIfEnteredThenDisabled(IControlTypeInfo info) + { + bool mouseLeaveCalled = false; + bool mouseEnterCalled = false; + bool mouseLeaveCalledBeforeMouseDown = false; + bool mouseLeaveCalledAfterDisabled = false; + bool mouseDownCalled = false; + ManualForm("Click on the control", form => + { + + var control = info.CreatePopulatedControl(); + control.MouseEnter += (sender, e) => + { + mouseEnterCalled = true; + }; + control.MouseLeave += (sender, e) => + { + mouseLeaveCalled = true; + if (mouseDownCalled) + form.Close(); + }; + control.MouseDown += (sender, e) => + { + mouseDownCalled = true; + mouseLeaveCalledBeforeMouseDown = mouseLeaveCalled; + control.Enabled = false; + mouseLeaveCalledAfterDisabled = mouseLeaveCalled; + e.Handled = true; + }; + return control; + }); + Assert.IsTrue(mouseEnterCalled, "#1.1 - MouseEnter did not get called"); + Assert.IsTrue(mouseLeaveCalled, "#1.2 - MouseLeave did not get called"); + Assert.IsFalse(mouseLeaveCalledBeforeMouseDown, "#1.3 - MouseLeave should not have been called before MouseDown"); + Assert.IsFalse(mouseLeaveCalledAfterDisabled, "#1.4 - MouseLeave should not be called during Enabled=false, but sometime after the MouseDown completes"); + Assert.IsTrue(mouseDownCalled, "#1.5 - MouseDown didn't get called. Did you click the control?"); + } } } \ No newline at end of file