Browse Source

Merge pull request #6188 from AvaloniaUI/fixes/6007-child-window-activation

OSX: Fix child window activation
# Conflicts:
#	native/Avalonia.Native/src/OSX/window.mm
#	src/Avalonia.Controls/ApiCompatBaseline.txt
#	tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
release/0.10.7
Dan Walmsley 5 years ago
parent
commit
442e845629
  1. 1
      native/Avalonia.Native/src/OSX/window.h
  2. 76
      native/Avalonia.Native/src/OSX/window.mm
  3. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  4. 4
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  5. 4
      src/Avalonia.Controls/Window.cs
  6. 2
      src/Avalonia.Controls/WindowBase.cs
  7. 6
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  8. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  9. 7
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  10. 4
      src/Avalonia.Native/PopupImpl.cs
  11. 4
      src/Avalonia.Native/WindowImplBase.cs
  12. 2
      src/Avalonia.Native/avn.idl
  13. 2
      src/Avalonia.X11/X11Window.cs
  14. 2
      src/Windows/Avalonia.Win32/PopupImpl.cs
  15. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  16. 4
      src/Windows/Avalonia.Win32/WindowImpl.cs
  17. 101
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  18. 2
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  19. 2
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs

1
native/Avalonia.Native/src/OSX/window.h

@ -34,6 +34,7 @@ class WindowBaseImpl;
-(double) getScaling;
-(double) getExtendedTitleBarHeight;
-(void) setIsExtended:(bool)value;
-(bool) isDialog;
@end
struct INSWindowHolder

76
native/Avalonia.Native/src/OSX/window.mm

@ -105,7 +105,7 @@ public:
return Window;
}
virtual HRESULT Show(bool activate) override
virtual HRESULT Show(bool activate, bool isDialog) override
{
@autoreleasepool
{
@ -488,6 +488,11 @@ public:
return S_OK;
}
virtual bool IsDialog()
{
return false;
}
protected:
virtual NSWindowStyleMask GetStyle()
{
@ -518,6 +523,7 @@ private:
NSRect _preZoomSize;
bool _transitioningWindowState;
bool _isClientAreaExtended;
bool _isDialog;
AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN()
@ -573,11 +579,12 @@ private:
}
}
virtual HRESULT Show (bool activate) override
virtual HRESULT Show (bool activate, bool isDialog) override
{
@autoreleasepool
{
WindowBaseImpl::Show(activate);
{
_isDialog = isDialog;
WindowBaseImpl::Show(activate, isDialog);
HideOrShowTrafficLights();
@ -1070,6 +1077,11 @@ private:
}
}
virtual bool IsDialog() override
{
return _isDialog;
}
protected:
virtual NSWindowStyleMask GetStyle() override
{
@ -1858,6 +1870,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_isExtended = value;
}
-(bool) isDialog
{
return _parent->IsDialog();
}
-(double) getScaling
{
return _lastScaling;
@ -2036,7 +2053,22 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
-(BOOL)canBecomeKeyWindow
{
return _canBecomeKeyAndMain;
if (_canBecomeKeyAndMain)
{
// If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows])
{
auto ch = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
if (ch.isDialog)
return false;
}
return true;
}
return false;
}
-(BOOL)canBecomeMainWindow
@ -2044,22 +2076,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return _canBecomeKeyAndMain;
}
-(bool) activateAppropriateChild: (bool)activating
{
for(NSWindow* uch in [self childWindows])
{
auto ch = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
[ch activateAppropriateChild:false];
return FALSE;
}
if(!activating)
[self makeKeyAndOrderFront:self];
return TRUE;
}
-(bool)shouldTryToHandleEvents
{
return _isEnabled;
@ -2070,26 +2086,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_isEnabled = enable;
}
-(void)makeKeyWindow
{
if([self activateAppropriateChild: true])
{
[super makeKeyWindow];
}
}
-(void)becomeKeyWindow
{
[self showWindowMenuWithAppMenu];
if([self activateAppropriateChild: true])
if(_parent != nullptr)
{
if(_parent != nullptr)
{
_parent->BaseEvents->Activated();
}
_parent->BaseEvents->Activated();
}
[super becomeKeyWindow];
}
@ -2099,7 +2104,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if(parent != nil)
{
[parent removeChildWindow:self];
[parent activateAppropriateChild: false];
}
}

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -10,4 +10,7 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 11
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowBaseImpl.Show(System.Boolean, System.Boolean)' is present in the implementation but not in the contract.
Total Issues: 14

4
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -7,7 +7,9 @@ namespace Avalonia.Platform
/// <summary>
/// Shows the window.
/// </summary>
void Show(bool activate);
/// <param name="activate">Whether to activate the shown window.</param>
/// <param name="isDialog">Whether the window is being shown as a dialog.</param>
void Show(bool activate, bool isDialog);
/// <summary>
/// Hides the window.

4
src/Avalonia.Controls/Window.cs

@ -671,7 +671,7 @@ namespace Avalonia.Controls
SetWindowStartupLocation(Owner?.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
PlatformImpl?.Show(ShowActivated, false);
Renderer?.Start();
}
OnOpened(EventArgs.Empty);
@ -743,7 +743,7 @@ namespace Avalonia.Controls
SetWindowStartupLocation(owner.PlatformImpl);
PlatformImpl?.Show(ShowActivated);
PlatformImpl?.Show(ShowActivated, true);
Renderer?.Start();

2
src/Avalonia.Controls/WindowBase.cs

@ -162,7 +162,7 @@ namespace Avalonia.Controls
LayoutManager.ExecuteInitialLayoutPass();
_hasExecutedInitialLayoutPass = true;
}
PlatformImpl?.Show(true);
PlatformImpl?.Show(true, false);
Renderer?.Start();
OnOpened(EventArgs.Empty);
}

6
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote
ClientSize = new Size(1, 1);
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
}
@ -99,10 +99,6 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void ShowDialog(IWindowImpl parent)
{
}
public void SetSystemDecorations(SystemDecorations enabled)
{
}

2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -77,7 +77,7 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
}

7
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -75,7 +75,7 @@ namespace Avalonia.Headless
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; }
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
if (activate)
Dispatcher.UIThread.Post(() => Activated?.Invoke(), DispatcherPriority.Input);
@ -147,11 +147,6 @@ namespace Avalonia.Headless
}
public void ShowDialog(IWindowImpl parent)
{
Show(true);
}
public void SetSystemDecorations(bool enabled)
{

4
src/Avalonia.Native/PopupImpl.cs

@ -60,14 +60,14 @@ namespace Avalonia.Native
}
}
public override void Show(bool activate)
public override void Show(bool activate, bool isDialog)
{
var parent = _parent;
while (parent is PopupImpl p)
parent = p._parent;
if (parent is WindowImpl w)
w.Native.TakeFocusFromChildren();
base.Show(false);
base.Show(false, isDialog);
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);

4
src/Avalonia.Native/WindowImplBase.cs

@ -351,9 +351,9 @@ namespace Avalonia.Native
}
public virtual void Show(bool activate)
public virtual void Show(bool activate, bool isDialog)
{
_native.Show(activate.AsComBool());
_native.Show(activate.AsComBool(), isDialog.AsComBool());
}

2
src/Avalonia.Native/avn.idl

@ -430,7 +430,7 @@ interface IAvnString : IUnknown
[uuid(e5aca675-02b7-4129-aa79-d6e417210bda)]
interface IAvnWindowBase : IUnknown
{
HRESULT Show(bool activate);
HRESULT Show(bool activate, bool isDialog);
HRESULT Hide();
HRESULT Close();
HRESULT Activate();

2
src/Avalonia.X11/X11Window.cs

@ -808,7 +808,7 @@ namespace Avalonia.X11
XSetTransientForHint(_x11.Display, _handle, parent.Handle.Handle);
}
public void Show(bool activate)
public void Show(bool activate, bool isDialog)
{
_wasMappedAtLeastOnce = true;
XMapWindow(_x11.Display, _handle);

2
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -17,7 +17,7 @@ namespace Avalonia.Win32
[ThreadStatic]
private static IntPtr s_parentHandle;
public override void Show(bool activate)
public override void Show(bool activate, bool isDialog)
{
// Popups are always shown non-activated.
UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate);

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -246,7 +246,7 @@ namespace Avalonia.Win32
public IWindowImpl CreateEmbeddableWindow()
{
var embedded = new EmbeddedWindowImpl();
embedded.Show(true);
embedded.Show(true, false);
return embedded;
}

4
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -570,7 +570,7 @@ namespace Avalonia.Win32
_shown = false;
}
public virtual void Show(bool activate)
public virtual void Show(bool activate, bool isDialog)
{
SetParent(_parent);
ShowWindow(_showWindowState, activate);
@ -1120,7 +1120,7 @@ namespace Avalonia.Win32
SetParent(null);
if (shown)
Show(activated);
Show(activated, false);
}
}
else

101
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -123,12 +123,97 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Cancel_Light_Dismiss_Closing_Keeps_Flyout_Open()
{
using (Application())
{
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
var button = new Button
{
Height = 10,
Width = 10,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
VerticalAlignment = Layout.VerticalAlignment.Top
};
window.Content = button;
window.ApplyTemplate();
window.Show();
var tracker = 0;
var c = new ContextMenu();
c.ContextMenuClosing += (s, e) =>
{
tracker++;
e.Cancel = true;
};
button.ContextMenu = c;
c.Open(button);
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
_mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
_mouse.Up(button, MouseButton.Left, new Point(90, 90));
Assert.Equal(1, tracker);
Assert.True(c.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Never);
popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
}
}
[Fact]
public void Light_Dismiss_Closes_Flyout()
{
using (Application())
{
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var window = PreparedWindow();
window.Width = 100;
window.Height = 100;
var button = new Button
{
Height = 10,
Width = 10,
HorizontalAlignment = Layout.HorizontalAlignment.Left,
VerticalAlignment = Layout.VerticalAlignment.Top
};
window.Content = button;
window.ApplyTemplate();
window.Show();
var c = new ContextMenu();
c.PlacementMode = PlacementMode.Bottom;
c.Open(button);
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window);
_mouse.Down(overlay, MouseButton.Left, new Point(90, 90));
_mouse.Up(button, MouseButton.Left, new Point(90, 90));
Assert.False(c.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Exactly(1));
popupImpl.Verify(x => x.Show(true, false), Times.Exactly(1));
}
}
[Fact]
public void Clicking_On_Control_Toggles_ContextMenu()
{
using (Application())
{
popupImpl.Setup(x => x.Show(true)).Verifiable();
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var sut = new ContextMenu();
@ -148,7 +233,7 @@ namespace Avalonia.Controls.UnitTests
_mouse.Click(target);
Assert.False(sut.IsOpen);
popupImpl.Verify(x => x.Show(true), Times.Once);
popupImpl.Verify(x => x.Show(true, false), Times.Once);
popupImpl.Verify(x => x.Hide(), Times.Once);
}
}
@ -158,7 +243,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
popupImpl.Setup(x => x.Show(true)).Verifiable();
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
var sut = new ContextMenu();
@ -179,7 +264,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Once);
popupImpl.Verify(x => x.Show(true), Times.Exactly(2));
popupImpl.Verify(x => x.Show(true, false), Times.Exactly(2));
}
}
@ -226,7 +311,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
popupImpl.Setup(x => x.Show(true)).Verifiable();
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
bool eventCalled = false;
var sut = new ContextMenu();
@ -242,7 +327,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(eventCalled);
Assert.False(sut.IsOpen);
popupImpl.Verify(x => x.Show(true), Times.Never);
popupImpl.Verify(x => x.Show(true, false), Times.Never);
}
}
@ -346,7 +431,7 @@ namespace Avalonia.Controls.UnitTests
{
using (Application())
{
popupImpl.Setup(x => x.Show(true)).Verifiable();
popupImpl.Setup(x => x.Show(true, false)).Verifiable();
popupImpl.Setup(x => x.Hide()).Verifiable();
bool eventCalled = false;
@ -370,7 +455,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(eventCalled);
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Show(true), Times.Once());
popupImpl.Verify(x => x.Show(true, false), Times.Once());
popupImpl.Verify(x => x.Hide(), Times.Never);
}
}

2
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -137,7 +137,7 @@ namespace Avalonia.Controls.UnitTests
var target = new TestWindowBase(windowImpl.Object);
target.IsVisible = true;
windowImpl.Verify(x => x.Show(true));
windowImpl.Verify(x => x.Show(true, false));
}
}

2
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -58,7 +58,7 @@ namespace Avalonia.UnitTests
windowImpl.Object.Resized?.Invoke(clientSize);
});
windowImpl.Setup(x => x.Show(true)).Callback(() =>
windowImpl.Setup(x => x.Show(true, It.IsAny<bool>())).Callback(() =>
{
windowImpl.Object.Activated?.Invoke();
});

Loading…
Cancel
Save