diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml index 632893bcd0..4d6bc24b47 100644 --- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml +++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml @@ -16,5 +16,7 @@ + + diff --git a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs index 93855cd13d..465743afbf 100644 --- a/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs +++ b/samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs @@ -1,10 +1,19 @@ +using System; +using Avalonia.Automation; using Avalonia.Controls; +using Avalonia.Controls.Embedding; using Avalonia.Interactivity; +using IntegrationTestApp.Embedding; +using MonoMac.AppKit; +using MonoMac.CoreGraphics; +using MonoMac.ObjCRuntime; namespace IntegrationTestApp; public partial class EmbeddingPage : UserControl { + private const long NSModalResponseContinue = -1002; + public EmbeddingPage() { InitializeComponent(); @@ -19,5 +28,65 @@ public partial class EmbeddingPage : UserControl private void Reset_Click(object? sender, RoutedEventArgs e) { ResetText(); + ModalResultTextBox.Text = ""; + } + + private void RunNativeModalSession_OnClick(object? sender, RoutedEventArgs e) + { + MacHelper.EnsureInitialized(); + + var app = NSApplication.SharedApplication; + var modalWindow = CreateNativeWindow(); + var session = app.BeginModalSession(modalWindow); + + while (true) + { + if (app.RunModalSession(session) != NSModalResponseContinue) + break; + } + + app.EndModalSession(session); + } + + private NSWindow CreateNativeWindow() + { + var button = new Button + { + Name = "ButtonInModal", + Content = "Button" + }; + + AutomationProperties.SetAutomationId(button, "ButtonInModal"); + + var root = new EmbeddableControlRoot + { + Width = 200, + Height = 200, + Content = button + }; + root.Prepare(); + + var window = new NSWindow( + new CGRect(0, 0, root.Width, root.Height), + NSWindowStyle.Titled | NSWindowStyle.Closable, + NSBackingStore.Buffered, + false); + + window.Identifier = "ModalNativeWindow"; + window.WillClose += (_, _) => NSApplication.SharedApplication.StopModal(); + + button.Click += (_, _) => + { + ModalResultTextBox.Text = "Clicked"; + window.Close(); + }; + + if (root.TryGetPlatformHandle() is not { } handle) + throw new InvalidOperationException("Could not get platform handle"); + + window.ContentView = (NSView)Runtime.GetNSObject(handle.Handle)!; + root.StartRendering(); + + return window; } } diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 5a0cdad755..62105c7deb 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -34,6 +34,8 @@ namespace Avalonia.Input _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); } + internal Pointer Pointer => _pointer; + internal static TMouseDevice GetOrCreatePrimary() where TMouseDevice : MouseDevice, new() { if (_primary is TMouseDevice device) diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index f243a2e382..94643fa91e 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -77,6 +77,7 @@ namespace Avalonia.Input if (oldVisual != null) oldVisual.DetachedFromVisualTree -= OnCaptureDetached; Captured = control; + CaptureSource = source; if (source != CaptureSource.Platform) PlatformCapture(control); @@ -115,6 +116,7 @@ namespace Avalonia.Input public IInputElement? Captured { get; private set; } public PointerType Type { get; } + public bool IsPrimary { get; } /// @@ -124,6 +126,8 @@ namespace Avalonia.Input public bool IsGestureRecognitionSkipped { get; set; } + internal CaptureSource CaptureSource { get; private set; } = CaptureSource.Platform; + public void Dispose() { if (Captured != null) diff --git a/src/Avalonia.Native/TopLevelImpl.cs b/src/Avalonia.Native/TopLevelImpl.cs index d45e50d5c6..c707da379f 100644 --- a/src/Avalonia.Native/TopLevelImpl.cs +++ b/src/Avalonia.Native/TopLevelImpl.cs @@ -65,8 +65,8 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface private NativeControlHostImpl? _nativeControlHost; private PlatformBehaviorInhibition? _platformBehaviorInhibition; - private readonly MouseDevice? _mouse; - private readonly PenDevice? _pen; + private readonly MouseDevice _mouse; + private readonly PenDevice _pen; private readonly IKeyboardDevice? _keyboard; private readonly ICursorFactory? _cursorFactory; @@ -480,6 +480,18 @@ internal class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface void IAvnTopLevelEvents.LostFocus() { _parent.LostFocus?.Invoke(); + + // macOS doesn't have the concept of mouse capture. If we're losing the focus during an implicit capture + // (standard mouse down), we should release it to avoid mouse events going to an old window. + var mouse = _parent._mouse; + var captured = mouse.Pointer.Captured; + + if (captured is not null && + mouse.Pointer.CaptureSource == CaptureSource.Implicit && + TopLevel.GetTopLevel(captured as Visual)?.PlatformImpl == _parent) + { + mouse.PlatformCaptureLost(); + } } AvnDragDropEffects IAvnTopLevelEvents.DragEvent(AvnDragEventType type, AvnPoint position,