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,