Skip to content

Commit

Permalink
[macOS][X11] Release mouse capture when dialog shown (AvaloniaUI#16205)
Browse files Browse the repository at this point in the history
* Added integration test for AvaloniaUI#14525.

* Release mouse capture when dialog shown.

Fixes AvaloniaUI#14525.

* Release X11 pointer capture when dialog shown.
  • Loading branch information
grokys authored and Gillibald committed Jul 29, 2024
1 parent 1f65cb2 commit b192368
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 0 deletions.
19 changes: 19 additions & 0 deletions samples/IntegrationTestApp/DelegateCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Windows.Input;

namespace IntegrationTestApp;

internal class DelegateCommand : ICommand
{
private readonly Action _action;
private readonly Func<object?, bool> _canExecute;
public DelegateCommand(Action action, Func<object?, bool>? canExecute = default)
{
_action = action;
_canExecute = canExecute ?? new(_ => true);
}

public event EventHandler? CanExecuteChanged { add { } remove { } }
public bool CanExecute(object? parameter) => _canExecute(parameter);
public void Execute(object? parameter) => _action();
}
15 changes: 15 additions & 0 deletions samples/IntegrationTestApp/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@
</StackPanel>
</DockPanel>
</TabItem>

<TabItem Header="Pointer">
<StackPanel>
<!-- Trigger with PointerPressed rather using a Button so we have access to the pointer. -->
<Border Name="PointerPageShowDialog"
Background="{DynamicResource ButtonBackground}"
HorizontalAlignment="Left"
Padding="{DynamicResource ButtonPadding}"
AutomationProperties.AccessibilityView="Control"
PointerPressed="PointerPageShowDialogPressed">
<TextBlock>Show Dialog</TextBlock>
</Border>
<TextBlock Name="PointerCaptureStatus"/>
</StackPanel>
</TabItem>

<TabItem Header="Window">
<Grid ColumnDefinitions="*,8,*">
Expand Down
33 changes: 33 additions & 0 deletions samples/IntegrationTestApp/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
Expand Down Expand Up @@ -285,5 +286,37 @@ private void OnButtonClick(object? sender, RoutedEventArgs e)
if (source?.Name == "RestoreAll")
RestoreAll();
}

private void PointerPageShowDialogPressed(object? sender, PointerPressedEventArgs e)
{
void CaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
PointerCaptureStatus.Text = "None";
((Control)sender!).PointerCaptureLost -= CaptureLost;
}

var captured = e.Pointer.Captured as Control;

if (captured is not null)
{
captured.PointerCaptureLost += CaptureLost;
}

PointerCaptureStatus.Text = captured?.ToString() ?? "None";

var dialog = new Window
{
Width = 200,
Height = 200,
};

dialog.Content = new Button
{
Content = "Close",
Command = new DelegateCommand(() => dialog.Close()),
};

dialog.ShowDialog(this);
}
}
}
5 changes: 5 additions & 0 deletions src/Avalonia.Base/Input/MouseDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,5 +305,10 @@ public void Dispose()
{
return _pointer;
}

internal void PlatformCaptureLost()
{
_pointer.Capture(null);
}
}
}
6 changes: 6 additions & 0 deletions src/Avalonia.Base/Input/TouchDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,5 +160,11 @@ public void Dispose()
? pointer
: null;
}

internal void PlatformCaptureLost()
{
foreach (var pointer in _pointers.Values)
pointer.Capture(null);
}
}
}
7 changes: 7 additions & 0 deletions src/Avalonia.Native/WindowImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ public void SetParent(IWindowImpl parent)
public void SetEnabled(bool enable)
{
_native.SetEnabled(enable.AsComBool());

// Showing a dialog should result in mouse capture being lost. macOS doesn't have the concept of mouse
// capture, so no we have no OS-level event to hook into. Instead, release the mouse capture when the
// owner window is disabled. This behavior matches win32, which sends a WM_CANCELMODE message when
// EnableWindow(hWnd, false) is called from SetEnabled.
if (!enable && MouseDevice is MouseDevice mouse)
mouse.PlatformCaptureLost();
}

public override object TryGetFeature(Type featureType)
Expand Down
9 changes: 9 additions & 0 deletions src/Avalonia.X11/X11Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,15 @@ public void SetEnabled(bool enable)
// so setting it again forces the update
UpdateMotifHints();
}
else
{
// Showing a dialog should result in pointer capture being lost. We don't currently use XGrabPointer on
// X11 to implement pointer capture, so no we have no OS-level event to hook into. Instead, release the
// pointer capture when the owner window is disabled. This behavior matches win32, which sends a
// WM_CANCELMODE message when EnableWindow(hWnd, false) is called from SetEnabled.
_mouse.PlatformCaptureLost();
_touch.PlatformCaptureLost();
}
}

private void UpdateWMHints()
Expand Down
31 changes: 31 additions & 0 deletions tests/Avalonia.IntegrationTests.Appium/PointerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using OpenQA.Selenium.Interactions;
using Xunit;

namespace Avalonia.IntegrationTests.Appium
{
[Collection("Default")]
public class PointerTests
{
private readonly AppiumDriver _session;

public PointerTests(DefaultAppFixture fixture)
{
_session = fixture.Session;

var tabs = _session.FindElementByAccessibilityId("MainTabs");
var tab = tabs.FindElementByName("Pointer");
tab.Click();
}

[Fact]
public void Pointer_Capture_Is_Released_When_Showing_Dialog()
{
var button = _session.FindElementByAccessibilityId("PointerPageShowDialog");

button.OpenWindowWithClick().Dispose();

var status = _session.FindElementByAccessibilityId("PointerCaptureStatus");
Assert.Equal("None", status.Text);
}
}
}

0 comments on commit b192368

Please sign in to comment.