Browse Source

Release implicit mouse capture on focus lost (#20574)

* macOS: Release implicit mouse capture on focus lost

* Added native modal test to EmbeddingPage
pull/14729/merge
Julien Lebosquain 2 days ago
committed by GitHub
parent
commit
934ca7c9f5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      samples/IntegrationTestApp/Pages/EmbeddingPage.axaml
  2. 69
      samples/IntegrationTestApp/Pages/EmbeddingPage.axaml.cs
  3. 2
      src/Avalonia.Base/Input/MouseDevice.cs
  4. 4
      src/Avalonia.Base/Input/Pointer.cs
  5. 16
      src/Avalonia.Native/TopLevelImpl.cs

2
samples/IntegrationTestApp/Pages/EmbeddingPage.axaml

@ -16,5 +16,7 @@
</Popup>
</StackPanel>
<Button Name="Reset" Click="Reset_Click">Reset</Button>
<Button Name="RunNativeModalSession" Click="RunNativeModalSession_OnClick">Open Native Modal</Button>
<TextBox Name="ModalResultTextBox" />
</StackPanel>
</UserControl>

69
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;
}
}

2
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<TMouseDevice>() where TMouseDevice : MouseDevice, new()
{
if (_primary is TMouseDevice device)

4
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; }
/// <summary>
@ -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)

16
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,

Loading…
Cancel
Save