Skip to content

Commit

Permalink
Merge pull request #10677 from AvaloniaUI/fixes/10650-extend-client-a…
Browse files Browse the repository at this point in the history
…rea-decorations

macOS: Fix interaction between ExtendClientAreaToDecorationsHint and SystemDecorations
  • Loading branch information
Dan Walmsley committed Mar 16, 2023
2 parents 44c2025 + 456ddbb commit 978c539
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 41 deletions.
10 changes: 7 additions & 3 deletions native/Avalonia.Native/src/OSX/WindowImpl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,13 @@

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;

Expand Down Expand Up @@ -611,7 +614,8 @@
}

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];
Expand Down
6 changes: 6 additions & 0 deletions samples/IntegrationTestApp/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
4 changes: 4 additions & 0 deletions samples/IntegrationTestApp/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ private void ShowWindow()
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()!;

Expand Down Expand Up @@ -95,6 +97,8 @@ private void ShowWindow()
}

sizeTextBox.Text = string.Empty;
window.ExtendClientAreaToDecorationsHint = extendClientArea.IsChecked ?? false;
window.SystemDecorations = (SystemDecorations)systemDecorations.SelectedIndex;
window.WindowState = (WindowState)stateComboBox.SelectedIndex;

switch (modeComboBox.SelectedIndex)
Expand Down
24 changes: 18 additions & 6 deletions samples/IntegrationTestApp/ShowWindowTest.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -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}" />
Expand Down Expand Up @@ -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>
Expand Down
19 changes: 13 additions & 6 deletions tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,26 @@

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.");
Expand Down Expand Up @@ -138,7 +145,7 @@ public static IDisposable OpenWindowWithClick(this AppiumWebElement element)
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);
});
Expand Down
13 changes: 11 additions & 2 deletions tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal enum TestPlatforms
internal class PlatformFactAttribute : FactAttribute
{
private readonly string? _reason;
private string? _skip;

public PlatformFactAttribute(TestPlatforms platforms, string? reason = null)
{
Expand All @@ -28,8 +29,16 @@ public PlatformFactAttribute(TestPlatforms platforms, string? reason = null)

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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
124 changes: 102 additions & 22 deletions tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resiz
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);

Expand Down Expand Up @@ -239,17 +239,18 @@ public void WindowOrder_Owned_Is_Correct_After_Closing_Window()
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);
}
}

Expand All @@ -259,11 +260,11 @@ public void Minimize_Button_Is_Disabled_On_Modal_Dialog()
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);
}
}

Expand All @@ -274,7 +275,7 @@ public void Minimize_Button_Disabled_Owned_Window(ShowWindowMode mode)
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);
}
Expand All @@ -288,7 +289,7 @@ public void Minimize_Button_Minimizes_Window(ShowWindowMode mode)
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);
Expand Down Expand Up @@ -332,7 +333,7 @@ public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked()

// Close the window manually.
secondaryWindow = GetWindow("SecondaryWindow");
secondaryWindow.GetChromeButtons().close.Click();
secondaryWindow.FindElementByAccessibilityId("_XCUI:CloseWindow").Click();
}

[PlatformTheory(TestPlatforms.MacOS)]
Expand All @@ -344,22 +345,92 @@ public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMo
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}");
Expand All @@ -379,6 +450,15 @@ public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMo
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();
}

Expand Down

0 comments on commit 978c539

Please sign in to comment.