Browse Source

Merge pull request #10265 from AvaloniaUI/fixes/macos-noresize-fullscreen

Various macOS and win32 fixes
pull/10341/head
Steven Kirk 4 years ago
committed by GitHub
parent
commit
01e3062dc0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  2. 2
      native/Avalonia.Native/src/OSX/PopupImpl.mm
  3. 5
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  4. 29
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  5. 6
      native/Avalonia.Native/src/OSX/WindowImpl.h
  6. 101
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  7. 1
      samples/IntegrationTestApp/MainWindow.axaml
  8. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  9. 15
      src/Windows/Avalonia.Win32/WindowImpl.cs
  10. 52
      tests/Avalonia.IntegrationTests.Appium/WindowTests.cs
  11. 38
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

17
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

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

5
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);

29
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];
}
}

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

101
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];
}

1
samples/IntegrationTestApp/MainWindow.axaml

@ -140,6 +140,7 @@
<ComboBoxItem Name="ShowWindowStateMaximized">Maximized</ComboBoxItem>
<ComboBoxItem Name="ShowWindowStateFullScreen">FullScreen</ComboBoxItem>
</ComboBox>
<CheckBox Name="ShowWindowCanResize" IsChecked="True">Can Resize</CheckBox>
<Button Name="ShowWindow">Show Window</Button>
<Button Name="SendToBack">Send to Back</Button>
<Button Name="EnterFullscreen">Enter Fullscreen</Button>

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -66,11 +66,13 @@ 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 canResizeCheckBox = this.GetControl<CheckBox>("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)

15
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);
}
}
}

52
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<Size?, ShowWindowMode, WindowStartupLocation> StartupLocationData()
public static TheoryData<Size?, ShowWindowMode, WindowStartupLocation, bool> StartupLocationData()
{
var sizes = new Size?[] { null, new Size(400, 300) };
var data = new TheoryData<Size?, ShowWindowMode, WindowStartupLocation>();
var data = new TheoryData<Size?, ShowWindowMode, WindowStartupLocation, bool>();
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<Size?, ShowWindowMode, WindowState> WindowStateData()
public static TheoryData<Size?, ShowWindowMode, WindowState, bool> WindowStateData()
{
var sizes = new Size?[] { null, new Size(400, 300) };
var data = new TheoryData<Size?, ShowWindowMode, WindowState>();
var data = new TheoryData<Size?, ShowWindowMode, WindowState, bool>();
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();
}

38
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();
}

Loading…
Cancel
Save