Browse Source

Merge pull request #10677 from AvaloniaUI/fixes/10650-extend-client-area-decorations

macOS: Fix interaction between ExtendClientAreaToDecorationsHint and SystemDecorations
pull/10689/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
978c539b0e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  2. 6
      samples/IntegrationTestApp/MainWindow.axaml
  3. 4
      samples/IntegrationTestApp/MainWindow.axaml.cs
  4. 24
      samples/IntegrationTestApp/ShowWindowTest.axaml
  5. 19
      tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs
  6. 13
      tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
  7. 11
      tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs
  8. 124
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

10
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -281,10 +281,13 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
case SystemDecorationsFull:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
if (!_isClientAreaExtended) {
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
}
if (currentWindowState == Maximized) {
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
@ -611,7 +614,8 @@ void WindowImpl::UpdateStyle() {
}
bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
bool hasTrafficLights = (_decorations == SystemDecorationsFull) &&
(_isClientAreaExtended ? wantsChrome : true);
NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];

6
samples/IntegrationTestApp/MainWindow.axaml

@ -151,6 +151,12 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<ComboBox Name="ShowWindowSystemDecorations" SelectedIndex="2">
<ComboBoxItem Name="ShowWindowSystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="ShowWindowSystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowExtendClientAreaToDecorationsHint">ExtendClientAreaToDecorationsHint</CheckBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>

4
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -68,6 +68,8 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl<ComboBox>("ShowWindowLocation");
var stateComboBox = this.GetControl<ComboBox>("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
var systemDecorations = this.GetControl<ComboBox>("ShowWindowSystemDecorations");
var extendClientArea = this.GetControl<CheckBox>("ShowWindowExtendClientAreaToDecorationsHint");
var canResizeCheckBox = this.GetControl<CheckBox>("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
@ -95,6 +97,8 @@ namespace IntegrationTestApp
}
sizeTextBox.Text = string.Empty;
window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
window.WindowState = (WindowState)stateComboBox.SelectedIndex;
switch (modeComboBox.SelectedIndex)

24
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -6,7 +6,7 @@
x:DataType="Window"
Title="Show Window Test">
<integrationTestApp:MeasureBorder Name="MyBorder">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="CurrentClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}" />
@ -35,13 +35,25 @@
<ComboBoxItem Name="WindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="8" IsReadOnly="True" />
<Label Grid.Column="0" Grid.Row="8">SystemDecorations</Label>
<ComboBox Name="CurrentSystemDecorations" Grid.Column="1" Grid.Row="8" SelectedIndex="{Binding SystemDecorations}">
<ComboBoxItem Name="SystemDecorationsNone">None</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsBorderOnly">BorderOnly</ComboBoxItem>
<ComboBoxItem Name="SystemDecorationsFull">Full</ComboBoxItem>
</ComboBox>
<CheckBox Name="CurrentExtendClientAreaToDecorationsHint" Grid.ColumnSpan="2" Grid.Row="9"
IsChecked="{Binding ExtendClientAreaToDecorationsHint}">
ExtendClientAreaToDecorationsHint
</CheckBox>
<Label Grid.Column="0" Grid.Row="10">Order (mac)</Label>
<TextBox Name="CurrentOrder" Grid.Column="1" Grid.Row="10" IsReadOnly="True" />
<Label Grid.Row="9" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="9" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Label Grid.Row="11" Content="MeasuredWith:" />
<TextBlock Grid.Column="1" Grid.Row="11" Name="CurrentMeasuredWithText" Text="{Binding #MyBorder.MeasuredWith}" />
<Button Name="HideButton" Grid.Row="10" Command="{Binding $parent[Window].Hide}">Hide</Button>
<Button Name="HideButton" Grid.Row="12" Command="{Binding $parent[Window].Hide}">Hide</Button>
</Grid>
</integrationTestApp:MeasureBorder>

19
tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs

@ -11,19 +11,26 @@ using Xunit;
namespace Avalonia.IntegrationTests.Appium
{
public record class WindowChrome(
AppiumWebElement? Close,
AppiumWebElement? Minimize,
AppiumWebElement? Maximize,
AppiumWebElement? FullScreen);
internal static class ElementExtensions
{
public static IReadOnlyList<AppiumWebElement> GetChildren(this AppiumWebElement element) =>
element.FindElementsByXPath("*/*");
public static (AppiumWebElement close, AppiumWebElement minimize, AppiumWebElement maximize) GetChromeButtons(this AppiumWebElement window)
public static WindowChrome GetChromeButtons(this AppiumWebElement window)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var closeButton = window.FindElementByXPath("//XCUIElementTypeButton[1]");
var fullscreenButton = window.FindElementByXPath("//XCUIElementTypeButton[2]");
var minimizeButton = window.FindElementByXPath("//XCUIElementTypeButton[3]");
return (closeButton, minimizeButton, fullscreenButton);
var closeButton = window.FindElementsByAccessibilityId("_XCUI:CloseWindow").FirstOrDefault();
var fullscreenButton = window.FindElementsByAccessibilityId("_XCUI:FullScreenWindow").FirstOrDefault();
var minimizeButton = window.FindElementsByAccessibilityId("_XCUI:MinimizeWindow").FirstOrDefault();
var zoomButton = window.FindElementsByAccessibilityId("_XCUI:ZoomWindow").FirstOrDefault();
return new(closeButton, minimizeButton, zoomButton, fullscreenButton);
}
throw new NotSupportedException("GetChromeButtons not supported on this platform.");
@ -138,7 +145,7 @@ namespace Avalonia.IntegrationTests.Appium
var text = windows.Select(x => x.Text).ToList();
var newWindow = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow"))
.First(x => x.Text == newWindowTitle);
var (close, _, _) = ((AppiumWebElement)newWindow).GetChromeButtons();
var close = ((AppiumWebElement)newWindow).FindElementByAccessibilityId("_XCUI:CloseWindow");
close!.Click();
Thread.Sleep(1000);
});

13
tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs

@ -17,6 +17,7 @@ namespace Avalonia
internal class PlatformFactAttribute : FactAttribute
{
private readonly string? _reason;
private string? _skip;
public PlatformFactAttribute(TestPlatforms platforms, string? reason = null)
{
@ -28,8 +29,16 @@ namespace Avalonia
public override string? Skip
{
get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}" + (_reason is not null ? $" reason: \"{_reason}\"" : "");
set => throw new NotSupportedException();
get
{
if (_skip is not null)
return _skip;
if (!IsSupported())
return $"Ignored on {RuntimeInformation.OSDescription}" +
(_reason is not null ? $" reason: '{_reason}'" : "");
return null;
}
set => _skip = value;
}
private bool IsSupported()

11
tests/Avalonia.IntegrationTests.Appium/PlatformTheoryAttribute.cs

@ -7,14 +7,21 @@ namespace Avalonia.IntegrationTests.Appium
{
internal class PlatformTheoryAttribute : TheoryAttribute
{
private string? _skip;
public PlatformTheoryAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms;
public TestPlatforms Platforms { get; }
public override string? Skip
{
get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}";
set => throw new NotSupportedException();
get
{
if (_skip is not null)
return _skip;
return !IsSupported() ? $"Ignored on {RuntimeInformation.OSDescription}" : null;
}
set => _skip = value;
}
private bool IsSupported()

124
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@ -83,9 +83,9 @@ namespace Avalonia.IntegrationTests.Appium
public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen()
{
var mainWindow = GetWindow("MainWindow");
var buttons = mainWindow.GetChromeButtons();
var fullScreen = mainWindow.FindElementByAccessibilityId("_XCUI:FullScreenWindow");
buttons.maximize.Click();
fullScreen.Click();
Thread.Sleep(500);
@ -239,17 +239,18 @@ namespace Avalonia.IntegrationTests.Appium
public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown()
{
var window = GetWindow("MainWindow");
var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons();
var windowChrome = window.GetChromeButtons();
Assert.True(closeButton.Enabled);
Assert.True(zoomButton.Enabled);
Assert.True(miniaturizeButton.Enabled);
Assert.True(windowChrome.Close!.Enabled);
Assert.True(windowChrome.FullScreen!.Enabled);
Assert.True(windowChrome.Minimize!.Enabled);
Assert.Null(windowChrome.Maximize);
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
{
Assert.False(closeButton.Enabled);
Assert.False(zoomButton.Enabled);
Assert.False(miniaturizeButton.Enabled);
Assert.False(windowChrome.Close!.Enabled);
Assert.False(windowChrome.FullScreen!.Enabled);
Assert.False(windowChrome.Minimize!.Enabled);
}
}
@ -259,11 +260,11 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
{
var secondaryWindow = GetWindow("SecondaryWindow");
var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons();
var windowChrome = secondaryWindow.GetChromeButtons();
Assert.True(closeButton.Enabled);
Assert.True(zoomButton.Enabled);
Assert.False(miniaturizeButton.Enabled);
Assert.True(windowChrome.Close!.Enabled);
Assert.True(windowChrome.Maximize!.Enabled);
Assert.False(windowChrome.Minimize!.Enabled);
}
}
@ -274,7 +275,7 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
{
var secondaryWindow = GetWindow("SecondaryWindow");
var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons();
var miniaturizeButton = secondaryWindow.FindElementByAccessibilityId("_XCUI:MinimizeWindow");
Assert.False(miniaturizeButton.Enabled);
}
@ -288,7 +289,7 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
{
var secondaryWindow = GetWindow("SecondaryWindow");
var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons();
var miniaturizeButton = secondaryWindow.FindElementByAccessibilityId("_XCUI:MinimizeWindow");
miniaturizeButton.Click();
Thread.Sleep(1000);
@ -332,7 +333,7 @@ namespace Avalonia.IntegrationTests.Appium
// Close the window manually.
secondaryWindow = GetWindow("SecondaryWindow");
secondaryWindow.GetChromeButtons().close.Click();
secondaryWindow.FindElementByAccessibilityId("_XCUI:CloseWindow").Click();
}
[PlatformTheory(TestPlatforms.MacOS)]
@ -344,22 +345,92 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false))
{
var secondaryWindow = GetWindow("SecondaryWindow");
var (_, _, zoomButton) = secondaryWindow.GetChromeButtons();
var zoomButton = mode == ShowWindowMode.NonOwned ?
secondaryWindow.FindElementByAccessibilityId("_XCUI:FullScreenWindow") :
secondaryWindow.FindElementByAccessibilityId("_XCUI:ZoomWindow");
Assert.False(zoomButton.Enabled);
}
}
[PlatformFact(TestPlatforms.MacOS)]
public void Toggling_SystemDecorations_Should_Preserve_ExtendClientArea()
{
// #10650
using (OpenWindow(extendClientArea: true))
{
var secondaryWindow = GetWindow("SecondaryWindow");
// The XPath of the title bar text _should_ be "XCUIElementTypeStaticText"
// but Appium seems to put a fake node between the window and the title bar
// https://stackoverflow.com/a/71914227/6448
var titleBar = secondaryWindow.FindElementsByXPath("/*/XCUIElementTypeStaticText").Count;
Assert.Equal(0, titleBar);
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsNone").SendClick();
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
titleBar = secondaryWindow.FindElementsByXPath("/*/XCUIElementTypeStaticText").Count;
Assert.Equal(0, titleBar);
}
}
[PlatformTheory(TestPlatforms.MacOS)]
[InlineData(SystemDecorations.None)]
[InlineData(SystemDecorations.BorderOnly)]
[InlineData(SystemDecorations.Full)]
public void ExtendClientArea_SystemDecorations_Shows_Correct_Buttons(SystemDecorations decorations)
{
// #10650
using (OpenWindow(extendClientArea: true, systemDecorations: decorations))
{
var secondaryWindow = GetWindow("SecondaryWindow");
try
{
var chrome = secondaryWindow.GetChromeButtons();
if (decorations == SystemDecorations.Full)
{
Assert.NotNull(chrome.Close);
Assert.NotNull(chrome.Minimize);
Assert.NotNull(chrome.FullScreen);
}
else
{
Assert.Null(chrome.Close);
Assert.Null(chrome.Minimize);
Assert.Null(chrome.FullScreen);
}
}
finally
{
if (decorations != SystemDecorations.Full)
{
secondaryWindow.FindElementByAccessibilityId("CurrentSystemDecorations").Click();
_session.FindElementByAccessibilityId("SystemDecorationsFull").SendClick();
}
}
}
}
private IDisposable OpenWindow(
PixelSize? size,
ShowWindowMode mode,
WindowStartupLocation location,
bool canResize = true)
PixelSize? size = null,
ShowWindowMode mode = ShowWindowMode.NonOwned,
WindowStartupLocation location = WindowStartupLocation.Manual,
bool canResize = true,
SystemDecorations systemDecorations = SystemDecorations.Full,
bool extendClientArea = false)
{
var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize");
var showButton = _session.FindElementByAccessibilityId("ShowWindow");
var systemDecorationsComboBox = _session.FindElementByAccessibilityId("ShowWindowSystemDecorations");
var extendClientAreaCheckBox = _session.FindElementByAccessibilityId("ShowWindowExtendClientAreaToDecorationsHint");
if (size.HasValue)
sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
@ -379,6 +450,15 @@ namespace Avalonia.IntegrationTests.Appium
if (canResizeCheckBox.GetIsChecked() != canResize)
canResizeCheckBox.Click();
if (systemDecorationsComboBox.GetComboBoxValue() != systemDecorations.ToString())
{
systemDecorationsComboBox.Click();
_session.FindElementByName(systemDecorations.ToString()).SendClick();
}
if (extendClientAreaCheckBox.GetIsChecked() != extendClientArea)
extendClientAreaCheckBox.Click();
return showButton.OpenWindowWithClick();
}

Loading…
Cancel
Save