diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index d3b7b4ede6..23abf1d53f 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -223,6 +223,19 @@
}
}
+// From chromium:
+//
+// > The delegate or the window class should implement this method so that
+// > -[NSWindow isZoomed] can be then determined by whether or not the current
+// > window frame is equal to the zoomed frame.
+//
+// If we don't implement this, then isZoomed always returns true for a non-
+// resizable window ¯\_(ツ)_/¯
+- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
+ defaultFrame:(NSRect)newFrame {
+ return newFrame;
+}
+
-(BOOL)canBecomeKeyWindow
{
if(_canBecomeKeyWindow)
@@ -261,10 +274,6 @@
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
-
- [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable];
- [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable];
}
-(void)becomeKeyWindow
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm
index 9820a9f052..972d03d08c 100644
--- a/native/Avalonia.Native/src/OSX/PopupImpl.mm
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm
@@ -29,7 +29,7 @@ private:
[Window setLevel:NSPopUpMenuWindowLevel];
}
protected:
- virtual NSWindowStyleMask GetStyle() override
+ virtual NSWindowStyleMask CalculateStyleMask() override
{
return NSWindowStyleMaskBorderless;
}
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
index 4c2758f6c6..93decef136 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -105,9 +105,8 @@ BEGIN_INTERFACE_MAP()
virtual void BringToFront ();
protected:
- virtual NSWindowStyleMask GetStyle();
-
- void UpdateStyle();
+ virtual NSWindowStyleMask CalculateStyleMask() = 0;
+ virtual void UpdateStyle();
private:
void CreateNSWindow (bool isDialog);
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 038e9a048c..59102e15a6 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
lastSize = NSSize { 100, 100 };
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 };
-
lastMenu = nullptr;
CreateNSWindow(usePanel);
[Window setContentView:StandardContainer];
- [Window setStyleMask:NSWindowStyleMaskBorderless];
[Window setBackingType:NSBackingStoreBuffered];
-
[Window setContentMinSize:lastMinSize];
[Window setContentMaxSize:lastMaxSize];
-
[Window setOpaque:false];
}
@@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() {
return false;
}
-NSWindowStyleMask WindowBaseImpl::GetStyle() {
- return NSWindowStyleMaskBorderless;
-}
-
void WindowBaseImpl::UpdateStyle() {
- [Window setStyleMask:GetStyle()];
+ [Window setStyleMask:CalculateStyleMask()];
}
void WindowBaseImpl::CleanNSWindow() {
@@ -580,21 +572,12 @@ void WindowBaseImpl::CleanNSWindow() {
}
}
-void WindowBaseImpl::CreateNSWindow(bool isDialog) {
- if (isDialog) {
- if (![Window isKindOfClass:[AvnPanel class]]) {
- CleanNSWindow();
-
- Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
-
- [Window setHidesOnDeactivate:false];
- }
+void WindowBaseImpl::CreateNSWindow(bool usePanel) {
+ if (usePanel) {
+ Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
+ [Window setHidesOnDeactivate:false];
} else {
- if (![Window isKindOfClass:[AvnWindow class]]) {
- CleanNSWindow();
-
- Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
- }
+ Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless];
}
}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
index 3861aaf170..29bb659039 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.h
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP()
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
- void HideOrShowTrafficLights ();
-
virtual HRESULT Show (bool activate, bool isDialog) override;
virtual HRESULT SetEnabled (bool enable) override;
@@ -100,9 +98,11 @@ BEGIN_INTERFACE_MAP()
bool CanBecomeKeyWindow ();
protected:
- virtual NSWindowStyleMask GetStyle() override;
+ virtual NSWindowStyleMask CalculateStyleMask() override;
+ void UpdateStyle () override;
private:
+ void ZOrderChildWindows();
void OnInitialiseNSWindow();
NSString *_lastTitle;
};
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index ce82f7d83f..cf1ee6943d 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
OnInitialiseNSWindow();
}
-void WindowImpl::HideOrShowTrafficLights() {
- if (Window == nil) {
- return;
- }
-
- bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
- bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
-
- [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights];
- [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights];
-}
-
void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setCanBecomeKeyWindow:true];
@@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
WindowBaseImpl::Show(activate, isDialog);
GetWindowState(&_actualWindowState);
- HideOrShowTrafficLights();
-
return SetWindowState(_lastWindowState);
}
}
@@ -134,14 +119,19 @@ void WindowImpl::BringToFront()
}
[Window invalidateShadow];
+ ZOrderChildWindows();
+ }
+}
+
+void WindowImpl::ZOrderChildWindows()
+{
+ for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
+ {
+ auto window = (*iterator)->Window;
- for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
- {
- auto window = (*iterator)->Window;
-
- // #9565: Only bring window to front if it's on the currently active space
- if ([window isOnActiveSpace])
- (*iterator)->BringToFront();
+ // #9565: Only bring window to front if it's on the currently active space
+ if ([window isOnActiveSpace]) {
+ (*iterator)->BringToFront();
}
}
}
@@ -161,13 +151,15 @@ bool WindowImpl::CanBecomeKeyWindow()
void WindowImpl::StartStateTransition() {
_transitioningWindowState = true;
+ UpdateStyle();
}
void WindowImpl::EndStateTransition() {
_transitioningWindowState = false;
-
+ UpdateStyle();
+
// Ensure correct order of child windows after fullscreen transition.
- BringToFront();
+ ZOrderChildWindows();
}
SystemDecorations WindowImpl::Decorations() {
@@ -225,16 +217,12 @@ bool WindowImpl::IsZoomed() {
}
void WindowImpl::DoZoom() {
- switch (_decorations) {
- case SystemDecorationsNone:
- case SystemDecorationsBorderOnly:
- [Window setFrame:[Window screen].visibleFrame display:true];
- break;
-
-
- case SystemDecorationsFull:
- [Window performZoom:Window];
- break;
+ if (_decorations == SystemDecorationsNone ||
+ _decorations == SystemDecorationsBorderOnly ||
+ _canResize == false) {
+ [Window setFrame:[Window screen].visibleFrame display:true];
+ } else {
+ [Window performZoom:Window];
}
}
@@ -261,8 +249,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
UpdateStyle();
- HideOrShowTrafficLights();
-
switch (_decorations) {
case SystemDecorationsNone:
[Window setHasShadow:NO];
@@ -419,9 +405,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) {
}
[GetWindowProtocol() setIsExtended:enable];
-
- HideOrShowTrafficLights();
-
UpdateStyle();
}
@@ -577,14 +560,16 @@ bool WindowImpl::IsOwned() {
return _parent != nullptr;
}
-NSWindowStyleMask WindowImpl::GetStyle() {
- unsigned long s = NSWindowStyleMaskBorderless;
+NSWindowStyleMask WindowImpl::CalculateStyleMask() {
+ // Use the current style mask and only clear the flags we're going to be modifying.
+ unsigned long s = [Window styleMask] &
+ ~(NSWindowStyleMaskFullSizeContentView |
+ NSWindowStyleMaskTitled |
+ NSWindowStyleMaskClosable |
+ NSWindowStyleMaskResizable |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskTexturedBackground);
- if(_actualWindowState == FullScreen)
- {
- s |= NSWindowStyleMaskFullScreen;
- }
-
switch (_decorations) {
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
@@ -597,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
- if (_canResize && _isEnabled) {
+ if ((_canResize && _isEnabled) || _transitioningWindowState) {
s = s | NSWindowStyleMaskResizable;
}
break;
@@ -612,3 +597,25 @@ NSWindowStyleMask WindowImpl::GetStyle() {
}
return s;
}
+
+void WindowImpl::UpdateStyle() {
+ WindowBaseImpl::UpdateStyle();
+
+ if (Window == nil) {
+ return;
+ }
+
+ bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+ bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull;
+
+ NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton];
+ NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton];
+ NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton];
+
+ [closeButton setHidden:!hasTrafficLights];
+ [closeButton setEnabled:_isEnabled];
+ [miniaturizeButton setHidden:!hasTrafficLights];
+ [miniaturizeButton setEnabled:_isEnabled];
+ [zoomButton setHidden:!hasTrafficLights];
+ [zoomButton setEnabled:_isEnabled && _canResize];
+}
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index 4e5a8463df..a6f463fdcb 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -140,6 +140,7 @@
Maximized
FullScreen
+ Can Resize
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 3cd5350cce..087f25666b 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -66,11 +66,13 @@ namespace IntegrationTestApp
var locationComboBox = this.GetControl("ShowWindowLocation");
var stateComboBox = this.GetControl("ShowWindowState");
var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null;
+ var canResizeCheckBox = this.GetControl("ShowWindowCanResize");
var owner = (Window)this.GetVisualRoot()!;
var window = new ShowWindowTest
{
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
+ CanResize = canResizeCheckBox.IsChecked.Value,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 14344f34cf..39593331c1 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -1182,8 +1182,21 @@ namespace Avalonia.Win32
var y = monitorInfo.rcWork.top;
var cx = Math.Abs(monitorInfo.rcWork.right - x);
var cy = Math.Abs(monitorInfo.rcWork.bottom - y);
+ var style = (WindowStyles)GetWindowLong(_hwnd, (int)WindowLongParam.GWL_STYLE);
- SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
+ if (!style.HasFlag(WindowStyles.WS_SIZEFRAME))
+ {
+ // When calling SetWindowPos on a maximized window it automatically adjusts
+ // for "hidden" borders which are placed offscreen, EVEN IF THE WINDOW HAS
+ // NO BORDERS, meaning that the window is placed wrong when we have CanResize
+ // == false. Account for this here.
+ var borderThickness = BorderThickness;
+ x -= (int)borderThickness.Left;
+ cx += (int)borderThickness.Left + (int)borderThickness.Right;
+ cy += (int)borderThickness.Bottom;
+ }
+
+ SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW | SetWindowPosFlags.SWP_FRAMECHANGED);
}
}
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
index 7bb991aae6..ec24caa18c 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
@@ -30,9 +30,9 @@ namespace Avalonia.IntegrationTests.Appium
[Theory]
[MemberData(nameof(StartupLocationData))]
- public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location)
+ public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location, bool canResize)
{
- using var window = OpenWindow(size, mode, location);
+ using var window = OpenWindow(size, mode, location, canResize: canResize);
var info = GetWindowInfo();
if (size.HasValue)
@@ -63,9 +63,9 @@ namespace Avalonia.IntegrationTests.Appium
[Theory]
[MemberData(nameof(WindowStateData))]
- public void WindowState(Size? size, ShowWindowMode mode, WindowState state)
+ public void WindowState(Size? size, ShowWindowMode mode, WindowState state, bool canResize)
{
- using var window = OpenWindow(size, mode, state: state);
+ using var window = OpenWindow(size, mode, state: state, canResize: canResize);
try
{
@@ -230,10 +230,10 @@ namespace Avalonia.IntegrationTests.Appium
Assert.Equal(new Rgba32(255, 0, 0), centerColor);
}
- public static TheoryData StartupLocationData()
+ public static TheoryData StartupLocationData()
{
var sizes = new Size?[] { null, new Size(400, 300) };
- var data = new TheoryData();
+ var data = new TheoryData();
foreach (var size in sizes)
{
@@ -243,7 +243,8 @@ namespace Avalonia.IntegrationTests.Appium
{
if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned))
{
- data.Add(size, mode, location);
+ data.Add(size, mode, location, true);
+ data.Add(size, mode, location, false);
}
}
}
@@ -252,10 +253,10 @@ namespace Avalonia.IntegrationTests.Appium
return data;
}
- public static TheoryData WindowStateData()
+ public static TheoryData WindowStateData()
{
var sizes = new Size?[] { null, new Size(400, 300) };
- var data = new TheoryData();
+ var data = new TheoryData();
foreach (var size in sizes)
{
@@ -273,7 +274,8 @@ namespace Avalonia.IntegrationTests.Appium
mode != ShowWindowMode.NonOwned)
continue;
- data.Add(size, mode, state);
+ data.Add(size, mode, state, true);
+ data.Add(size, mode, state, false);
}
}
}
@@ -311,25 +313,39 @@ namespace Avalonia.IntegrationTests.Appium
Size? size,
ShowWindowMode mode,
WindowStartupLocation location = WindowStartupLocation.Manual,
- WindowState state = Controls.WindowState.Normal)
+ WindowState state = Controls.WindowState.Normal,
+ bool canResize = true)
{
var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize");
var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode");
var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation");
var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState");
+ var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize");
var showButton = _session.FindElementByAccessibilityId("ShowWindow");
-
+
if (size.HasValue)
sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
- modeComboBox.Click();
- _session.FindElementByName(mode.ToString()).SendClick();
+ if (modeComboBox.GetComboBoxValue() != mode.ToString())
+ {
+ modeComboBox.Click();
+ _session.FindElementByName(mode.ToString()).SendClick();
+ }
+
+ if (locationComboBox.GetComboBoxValue() != location.ToString())
+ {
+ locationComboBox.Click();
+ _session.FindElementByName(location.ToString()).SendClick();
+ }
- locationComboBox.Click();
- _session.FindElementByName(location.ToString()).SendClick();
+ if (stateComboBox.GetComboBoxValue() != state.ToString())
+ {
+ stateComboBox.Click();
+ _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick();
+ }
- stateComboBox.Click();
- _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick();
+ if (canResizeCheckBox.GetIsChecked() != canResize)
+ canResizeCheckBox.Click();
return showButton.OpenWindowWithClick();
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
index d9817ecdd1..0839cbf183 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
@@ -323,21 +323,49 @@ namespace Avalonia.IntegrationTests.Appium
secondaryWindow.GetChromeButtons().close.Click();
}
- private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location)
+ [PlatformTheory(TestPlatforms.MacOS)]
+ [InlineData(ShowWindowMode.NonOwned)]
+ [InlineData(ShowWindowMode.Owned)]
+ [InlineData(ShowWindowMode.Modal)]
+ public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMode mode)
+ {
+ using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false))
+ {
+ var secondaryWindow = GetWindow("SecondaryWindow");
+ var (_, _, zoomButton) = secondaryWindow.GetChromeButtons();
+ Assert.False(zoomButton.Enabled);
+ }
+ }
+
+ private IDisposable OpenWindow(
+ PixelSize? size,
+ ShowWindowMode mode,
+ WindowStartupLocation location,
+ bool canResize = true)
{
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");
if (size.HasValue)
sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}");
- modeComboBox.Click();
- _session.FindElementByName(mode.ToString()).SendClick();
+ if (modeComboBox.GetComboBoxValue() != mode.ToString())
+ {
+ modeComboBox.Click();
+ _session.FindElementByName(mode.ToString()).SendClick();
+ }
- locationComboBox.Click();
- _session.FindElementByName(location.ToString()).SendClick();
+ if (locationComboBox.GetComboBoxValue() != location.ToString())
+ {
+ locationComboBox.Click();
+ _session.FindElementByName(location.ToString()).SendClick();
+ }
+
+ if (canResizeCheckBox.GetIsChecked() != canResize)
+ canResizeCheckBox.Click();
return showButton.OpenWindowWithClick();
}