Browse Source

Merge branch 'master' into CompactDensityStyle

pull/4157/head
Wiesław Šoltés 6 years ago
committed by GitHub
parent
commit
b919ccccff
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 29
      Avalonia.sln
  2. 23
      native/Avalonia.Native/inc/avalonia-native.h
  3. 8
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  4. 36
      native/Avalonia.Native/src/OSX/app.mm
  5. 9
      native/Avalonia.Native/src/OSX/common.h
  6. 145
      native/Avalonia.Native/src/OSX/controlhost.mm
  7. 17
      native/Avalonia.Native/src/OSX/deadlock.mm
  8. 28
      native/Avalonia.Native/src/OSX/window.mm
  9. 4
      nukebuild/Build.cs
  10. 1
      samples/ControlCatalog/Pages/ButtonPage.xaml
  11. 8
      samples/interop/NativeEmbedSample/App.xaml
  12. 22
      samples/interop/NativeEmbedSample/App.xaml.cs
  13. 121
      samples/interop/NativeEmbedSample/EmbedSample.cs
  14. 58
      samples/interop/NativeEmbedSample/GtkHelper.cs
  15. 39
      samples/interop/NativeEmbedSample/MacHelper.cs
  16. 43
      samples/interop/NativeEmbedSample/MainWindow.xaml
  17. 36
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  18. 30
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  19. 17
      samples/interop/NativeEmbedSample/Program.cs
  20. 74
      samples/interop/NativeEmbedSample/WinApi.cs
  21. 1
      samples/interop/NativeEmbedSample/nodes-license.md
  22. BIN
      samples/interop/NativeEmbedSample/nodes.mp4
  23. 2
      src/Android/Avalonia.Android/AndroidPlatform.cs
  24. 4
      src/Android/Avalonia.Android/AvaloniaView.cs
  25. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  26. 23
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  27. 5
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  28. 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  29. 141
      src/Avalonia.Controls/NativeControlHost.cs
  30. 12
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  31. 12
      src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs
  32. 32
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  33. 5
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  34. 2
      src/Avalonia.Controls/Platform/IWindowingPlatform.cs
  35. 2
      src/Avalonia.Controls/Platform/PlatformManager.cs
  36. 11
      src/Avalonia.Controls/Primitives/Popup.cs
  37. 4
      src/Avalonia.Controls/Remote/RemoteServer.cs
  38. 15
      src/Avalonia.Controls/TopLevel.cs
  39. 7
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  40. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  41. 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  42. 33
      src/Avalonia.Input/KeyboardNavigation.cs
  43. 39
      src/Avalonia.Input/Navigation/TabNavigation.cs
  44. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  45. 136
      src/Avalonia.Native/NativeControlHostImpl.cs
  46. 17
      src/Avalonia.Native/WindowImplBase.cs
  47. 9
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  48. 2
      src/Avalonia.Visuals/VisualTree/TransformedBounds.cs
  49. 10
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  50. 190
      src/Avalonia.X11/X11NativeControlHost.cs
  51. 5
      src/Avalonia.X11/X11Platform.cs
  52. 8
      src/Avalonia.X11/X11Window.cs
  53. 4
      src/Avalonia.X11/XI2Manager.cs
  54. 8
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  55. 21
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  56. 6
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  57. 61
      src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs
  58. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  59. 58
      src/Windows/Avalonia.Win32/OffscreenParentWindow.cs
  60. 40
      src/Windows/Avalonia.Win32/PopupImpl.cs
  61. 201
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  62. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  63. 16
      src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs
  64. 10
      src/Windows/Avalonia.Win32/WindowImpl.cs
  65. 8
      src/iOS/Avalonia.iOS/EmbeddableImpl.cs
  66. 2
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  67. 2
      src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs
  68. 22
      tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs
  69. 4
      tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs
  70. 29
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
  71. 2
      tests/Avalonia.LeakTests/ControlTests.cs
  72. 2
      tests/Avalonia.UnitTests/MockWindowingPlatform.cs
  73. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  74. 4
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
  75. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

29
Avalonia.sln

@ -201,7 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
EndProject
@ -1896,6 +1898,30 @@ Global
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhone.Build.0 = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{351337F5-D66F-461B-A957-4EF60BDB4BA6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhone.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|Any CPU.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhone.Build.0 = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -1977,6 +2003,7 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

23
native/Avalonia.Native/inc/avalonia-native.h

@ -26,7 +26,8 @@ struct IAvnStringArray;
struct IAvnDndResultCallback;
struct IAvnGCHandleDeallocatorCallback;
struct IAvnMenuEvents;
struct IAvnNativeControlHost;
struct IAvnNativeControlHostTopLevelAttachment;
enum SystemDecorations {
SystemDecorationsNone = 0,
SystemDecorationsBorderOnly = 1,
@ -256,6 +257,7 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) = 0;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) = 0;
virtual HRESULT SetBlurEnabled (bool enable) = 0;
@ -295,6 +297,7 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0;
virtual void ScalingChanged(double scaling) = 0;
virtual void RunRenderPriorityJobs() = 0;
virtual void LostFocus() = 0;
virtual AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers, AvnDragDropEffects effects,
IAvnClipboard* clipboard, void* dataObjectHandle) = 0;
@ -478,4 +481,22 @@ AVNCOM(IAvnGCHandleDeallocatorCallback, 22) : IUnknown
virtual void FreeGCHandle(void* handle) = 0;
};
AVNCOM(IAvnNativeControlHost, 20) : IUnknown
{
virtual HRESULT CreateDefaultChild(void* parent, void** retOut) = 0;
virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() = 0;
virtual void DestroyDefaultChild(void* child) = 0;
};
AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
{
virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0;
virtual void Hide() = 0;
virtual void ReleaseChild() = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

8
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -10,6 +10,8 @@
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
1A465D10246AB61600C5858B /* dnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A465D0F246AB61600C5858B /* dnd.mm */; };
@ -32,6 +34,8 @@
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = "<group>"; };
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = "<group>"; };
1AFD334023E03C4F0042899B /* controlhost.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = controlhost.mm; sourceTree = "<group>"; };
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
1A465D0F246AB61600C5858B /* dnd.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dnd.mm; sourceTree = "<group>"; };
@ -86,11 +90,13 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
1A1852DB23E05814008F0DED /* deadlock.mm */,
1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */,
1AFD334023E03C4F0042899B /* controlhost.mm */,
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
@ -191,6 +197,7 @@
files = (
1A002B9E232135EE00021753 /* app.mm in Sources */,
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
@ -199,6 +206,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,

36
native/Avalonia.Native/src/OSX/app.mm

@ -29,9 +29,43 @@ NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivati
@end
@interface AvnApplication : NSApplication
@end
@implementation AvnApplication
{
BOOL _isHandlingSendEvent;
}
- (void)sendEvent:(NSEvent *)event
{
bool oldHandling = _isHandlingSendEvent;
_isHandlingSendEvent = true;
@try {
[super sendEvent: event];
} @finally {
_isHandlingSendEvent = oldHandling;
}
}
// This is needed for certain embedded controls
- (BOOL) isHandlingSendEvent
{
return _isHandlingSendEvent;
}
- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
{
_isHandlingSendEvent = handlingSendEvent;
}
@end
extern void InitializeAvnApp()
{
NSApplication* app = [NSApplication sharedApplication];
NSApplication* app = [AvnApplication sharedApplication];
id delegate = [AvnAppDelegate new];
[app setDelegate:delegate];
}

9
native/Avalonia.Native/src/OSX/common.h

@ -24,6 +24,7 @@ extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeperator();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
@ -54,4 +55,12 @@ template<typename T> inline T* objc_cast(id from) {
- (void) action;
@end
class AvnInsidePotentialDeadlock
{
public:
static bool IsInside();
AvnInsidePotentialDeadlock();
~AvnInsidePotentialDeadlock();
};
#endif

145
native/Avalonia.Native/src/OSX/controlhost.mm

@ -0,0 +1,145 @@
#include "common.h"
IAvnNativeControlHostTopLevelAttachment* CreateAttachment();
class AvnNativeControlHost :
public ComSingleObject<IAvnNativeControlHost, &IID_IAvnNativeControlHost>
{
public:
FORWARD_IUNKNOWN();
NSView* View;
AvnNativeControlHost(NSView* view)
{
View = view;
}
virtual HRESULT CreateDefaultChild(void* parent, void** retOut) override
{
NSView* view = [NSView new];
[view setWantsLayer: true];
*retOut = (__bridge_retained void*)view;
return S_OK;
};
virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() override
{
return ::CreateAttachment();
};
virtual void DestroyDefaultChild(void* child) override
{
// ARC will release the object for us
(__bridge_transfer NSView*) child;
}
};
class AvnNativeControlHostTopLevelAttachment :
public ComSingleObject<IAvnNativeControlHostTopLevelAttachment, &IID_IAvnNativeControlHostTopLevelAttachment>
{
NSView* _holder;
NSView* _child;
public:
FORWARD_IUNKNOWN();
AvnNativeControlHostTopLevelAttachment()
{
_holder = [NSView new];
[_holder setWantsLayer:true];
}
virtual ~AvnNativeControlHostTopLevelAttachment()
{
if(_child != nil && [_child superview] == _holder)
{
[_child removeFromSuperview];
}
if([_holder superview] != nil)
{
[_holder removeFromSuperview];
}
}
virtual void* GetParentHandle() override
{
return (__bridge void*)_holder;
};
virtual HRESULT InitializeWithChildHandle(void* child) override
{
if(_child != nil)
return E_FAIL;
_child = (__bridge NSView*)child;
if(_child == nil)
return E_FAIL;
[_holder addSubview:_child];
[_child setHidden: false];
return S_OK;
};
virtual HRESULT AttachTo(IAvnNativeControlHost* host) override
{
if(host == nil)
{
[_holder removeFromSuperview];
[_holder setHidden: true];
}
else
{
AvnNativeControlHost* chost = dynamic_cast<AvnNativeControlHost*>(host);
if(chost == nil || chost->View == nil)
return E_FAIL;
[_holder setHidden:true];
[chost->View addSubview:_holder];
}
return S_OK;
};
virtual void MoveTo(float x, float y, float width, float height) override
{
if(_child == nil)
return;
if(AvnInsidePotentialDeadlock::IsInside())
{
IAvnNativeControlHostTopLevelAttachment* slf = this;
slf->AddRef();
dispatch_async(dispatch_get_main_queue(), ^{
slf->MoveTo(x, y, width, height);
slf->Release();
});
return;
}
NSRect childFrame = {0, 0, width, height};
NSRect holderFrame = {x, y, width, height};
[_child setFrame: childFrame];
[_holder setFrame: holderFrame];
[_holder setHidden: false];
if([_holder superview] != nil)
[[_holder superview] setNeedsDisplay:true];
}
virtual void Hide() override
{
[_holder setHidden: true];
}
virtual void ReleaseChild() override
{
[_child removeFromSuperview];
_child = nil;
}
};
IAvnNativeControlHostTopLevelAttachment* CreateAttachment()
{
return new AvnNativeControlHostTopLevelAttachment();
}
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent)
{
return new AvnNativeControlHost(parent);
}

17
native/Avalonia.Native/src/OSX/deadlock.mm

@ -0,0 +1,17 @@
#include "common.h"
static int Counter = 0;
AvnInsidePotentialDeadlock::AvnInsidePotentialDeadlock()
{
Counter++;
}
AvnInsidePotentialDeadlock::~AvnInsidePotentialDeadlock()
{
Counter--;
}
bool AvnInsidePotentialDeadlock::IsInside()
{
return Counter!=0;
}

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

@ -390,6 +390,14 @@ public:
return *ppv == nil ? E_FAIL : S_OK;
}
virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override
{
if(View == NULL)
return E_FAIL;
*retOut = ::CreateNativeControlHost(View);
return S_OK;
}
virtual HRESULT SetBlurEnabled (bool enable) override
{
[Window setContentView: enable ? VisualEffect : View];
@ -1060,9 +1068,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
- (void)updateLayer
{
AvnInsidePotentialDeadlock deadlock;
if (_parent == nullptr)
{
return;
@ -1142,7 +1150,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return;
}
[self becomeFirstResponder];
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
@ -1169,7 +1176,16 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto timestamp = [event timestamp] * 1000;
auto modifiers = [self getModifiers:[event modifierFlags]];
[self becomeFirstResponder];
if(type != AvnRawMouseEventType::Move ||
(
[self window] != nil &&
(
[[self window] firstResponder] == nil
|| ![[[self window] firstResponder] isKindOfClass: [NSView class]]
)
)
)
[self becomeFirstResponder];
if(_parent != nullptr)
{
@ -1179,6 +1195,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
[super mouseMoved:event];
}
- (BOOL) resignFirstResponder
{
_parent->BaseEvents->LostFocus();
return YES;
}
- (void)mouseMoved:(NSEvent *)event
{
[self mouseEvent:event withType:Move];

4
nukebuild/Build.cs

@ -101,7 +101,7 @@ partial class Build : NukeBuild
.SetProjectFile(projectFile)
// This is required for VS2019 image on Azure Pipelines
.When(Parameters.IsRunningOnWindows &&
Parameters.IsRunningOnAzure, c => c
Parameters.IsRunningOnAzure, _ => _
.AddProperty("JavaSdkDirectory", GetVariable<string>("JAVA_HOME_8_X64")))
.AddProperty("PackageVersion", Parameters.Version)
.AddProperty("iOSRoslynPathHackRequired", true)
@ -176,7 +176,7 @@ partial class Build : NukeBuild
.SetFramework(fw)
.EnableNoBuild()
.EnableNoRestore()
.When(Parameters.PublishTestResults, c => c
.When(Parameters.PublishTestResults, _ => _
.SetLogger("trx")
.SetResultsDirectory(Parameters.TestResultsRoot)));
}

1
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -35,6 +35,7 @@
<Button BorderBrush="{DynamicResource ThemeAccentBrush}">Border Color</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4">Thick Border</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="4" IsEnabled="False">Disabled</Button>
<Button BorderBrush="{DynamicResource ThemeAccentBrush}" KeyboardNavigation.IsTabStop="False">IsTabStop=False</Button>
</StackPanel>
</StackPanel>
</StackPanel>

8
samples/interop/NativeEmbedSample/App.xaml

@ -0,0 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Themes.Default/DefaultTheme.xaml"/>
<StyleInclude Source="avares://Avalonia.Themes.Default/Accents/BaseLight.xaml"/>
</Application.Styles>
</Application>

22
samples/interop/NativeEmbedSample/App.xaml.cs

@ -0,0 +1,22 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
desktopLifetime.MainWindow = new MainWindow();
base.OnFrameworkInitializationCompleted();
}
}
}

121
samples/interop/NativeEmbedSample/EmbedSample.cs

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.Threading;
using MonoMac.AppKit;
using MonoMac.Foundation;
using MonoMac.WebKit;
using Encoding = SharpDX.Text.Encoding;
namespace NativeEmbedSample
{
public class EmbedSample : NativeControlHost
{
public bool IsSecond { get; set; }
private Process _mplayer;
IPlatformHandle CreateLinux(IPlatformHandle parent)
{
if (IsSecond)
{
var chooser = GtkHelper.CreateGtkFileChooser(parent.Handle);
if (chooser != null)
return chooser;
}
var control = base.CreateNativeControlCore(parent);
var nodes = Path.GetFullPath(Path.Combine(typeof(EmbedSample).Assembly.GetModules()[0].FullyQualifiedName,
"..",
"nodes.mp4"));
_mplayer = Process.Start(new ProcessStartInfo("mplayer",
$"-vo x11 -zoom -loop 0 -wid {control.Handle.ToInt64()} \"{nodes}\"")
{
UseShellExecute = false,
});
return control;
}
void DestroyLinux(IPlatformHandle handle)
{
_mplayer?.Kill();
_mplayer = null;
base.DestroyNativeControlCore(handle);
}
private const string RichText =
@"{\rtf1\ansi\ansicpg1251\deff0\nouicompat\deflang1049{\fonttbl{\f0\fnil\fcharset0 Calibri;}}
{\colortbl ;\red255\green0\blue0;\red0\green77\blue187;\red0\green176\blue80;\red155\green0\blue211;\red247\green150\blue70;\red75\green172\blue198;}
{\*\generator Riched20 6.3.9600}\viewkind4\uc1
\pard\sa200\sl276\slmult1\f0\fs22\lang9 <PREFIX>I \i am\i0 a \cf1\b Rich Text \cf0\b0\fs24 control\cf2\fs28 !\cf3\fs32 !\cf4\fs36 !\cf1\fs40 !\cf5\fs44 !\cf6\fs48 !\cf0\fs44\par
}";
IPlatformHandle CreateWin32(IPlatformHandle parent)
{
WinApi.LoadLibrary("Msftedit.dll");
var handle = WinApi.CreateWindowEx(0, "RICHEDIT50W",
@"Rich Edit",
0x800000 | 0x10000000 | 0x40000000 | 0x800000 | 0x10000 | 0x0004, 0, 0, 1, 1, parent.Handle,
IntPtr.Zero, WinApi.GetModuleHandle(null), IntPtr.Zero);
var st = new WinApi.SETTEXTEX { Codepage = 65001, Flags = 0x00000008 };
var text = RichText.Replace("<PREFIX>", IsSecond ? "\\qr " : "");
var bytes = Encoding.UTF8.GetBytes(text);
WinApi.SendMessage(handle, 0x0400 + 97, ref st, bytes);
return new PlatformHandle(handle, "HWND");
}
void DestroyWin32(IPlatformHandle handle)
{
WinApi.DestroyWindow(handle.Handle);
}
IPlatformHandle CreateOSX(IPlatformHandle parent)
{
// Note: We are using MonoMac for example purposes
// It shouldn't be used in production apps
MacHelper.EnsureInitialized();
var webView = new WebView();
Dispatcher.UIThread.Post(() =>
{
webView.MainFrame.LoadRequest(new NSUrlRequest(new NSUrl(
IsSecond ? "https://bing.com": "https://google.com/")));
});
return new MacOSViewHandle(webView);
}
void DestroyOSX(IPlatformHandle handle)
{
((MacOSViewHandle)handle).Dispose();
}
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
return CreateLinux(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
return CreateWin32(parent);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return CreateOSX(parent);
return base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
DestroyLinux(control);
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
DestroyWin32(control);
else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
DestroyOSX(control);
else
base.DestroyNativeControlCore(control);
}
}
}

58
samples/interop/NativeEmbedSample/GtkHelper.cs

@ -0,0 +1,58 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.NativeDialogs.Gtk;
using static Avalonia.X11.NativeDialogs.Glib;
namespace NativeEmbedSample
{
public class GtkHelper
{
private static Task<bool> s_gtkTask;
class FileChooser : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _widget;
public FileChooser(IntPtr widget, IntPtr xid)
{
_widget = widget;
Handle = xid;
}
public IntPtr Handle { get; }
public string HandleDescriptor => "XID";
public void Destroy()
{
RunOnGlibThread(() =>
{
gtk_widget_destroy(_widget);
return 0;
}).Wait();
}
}
public static IPlatformHandle CreateGtkFileChooser(IntPtr parentXid)
{
if (s_gtkTask == null)
s_gtkTask = StartGtk();
if (!s_gtkTask.Result)
return null;
return RunOnGlibThread(() =>
{
using (var title = new Utf8Buffer("Embedded"))
{
var widget = gtk_file_chooser_dialog_new(title, IntPtr.Zero, GtkFileChooserAction.SelectFolder,
IntPtr.Zero);
gtk_widget_realize(widget);
var xid = gdk_x11_window_get_xid(gtk_widget_get_window(widget));
gtk_window_present(widget);
return new FileChooser(widget, xid);
}
}).Result;
}
}
}

39
samples/interop/NativeEmbedSample/MacHelper.cs

@ -0,0 +1,39 @@
using System;
using Avalonia.Platform;
using MonoMac.AppKit;
namespace NativeEmbedSample
{
public class MacHelper
{
private static bool _isInitialized;
public static void EnsureInitialized()
{
if (_isInitialized)
return;
_isInitialized = true;
NSApplication.Init();
}
}
class MacOSViewHandle : IPlatformHandle, IDisposable
{
private NSView _view;
public MacOSViewHandle(NSView view)
{
_view = view;
}
public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
public string HandleDescriptor => "NSView";
public void Dispose()
{
_view.Dispose();
_view = null;
}
}
}

43
samples/interop/NativeEmbedSample/MainWindow.xaml

@ -0,0 +1,43 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Width="1024" Height="800"
Title="Native embedding sample"
xmlns:local="clr-namespace:NativeEmbedSample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.MainWindow">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Test">
<MenuItem Header="SubMenu">
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
<MenuItem Header="Item 1"/>
<MenuItem Header="Item 2"/>
<MenuItem Header="Item 3"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
<Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
<TextBox Text="Lorem ipsum dolor sit amet"/>
</DockPanel>
<Grid ColumnDefinitions="*,5,*">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="firstVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsVisible="{Binding #firstVisible.IsChecked}"/>
</DockPanel>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<DockPanel Grid.Column="2">
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<CheckBox x:Name="secondVisible" IsChecked="True"/>
<TextBlock>Visible</TextBlock>
</StackPanel>
<local:EmbedSample IsSecond="True" IsVisible="{Binding #secondVisible.IsChecked}"/>
</DockPanel>
</Grid>
</DockPanel>
</Window>

36
samples/interop/NativeEmbedSample/MainWindow.xaml.cs

@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace NativeEmbedSample
{
public class MainWindow : Window
{
public MainWindow()
{
AvaloniaXamlLoader.Load(this);
this.AttachDevTools();
}
public async void ShowPopupDelay(object sender, RoutedEventArgs args)
{
await Task.Delay(3000);
ShowPopup(sender, args);
}
public void ShowPopup(object sender, RoutedEventArgs args)
{
new ContextMenu()
{
Items = new List<MenuItem>
{
new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" }
}
}.Open((Control)sender);
}
}
}

30
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
<ProjectReference Include="..\..\..\src\Avalonia.Desktop\Avalonia.Desktop.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\..\src\Avalonia.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<None Remove="nodes.mp4" />
<Content Include="nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
</ItemGroup>
<Import Project="..\..\..\build\SampleApp.props" />
<Import Project="..\..\..\build\BuildTargets.targets" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project>

17
samples/interop/NativeEmbedSample/Program.cs

@ -0,0 +1,17 @@
using Avalonia;
namespace NativeEmbedSample
{
class Program
{
static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.With(new AvaloniaNativePlatformOptions()
{
})
.UsePlatformDetect();
}
}

74
samples/interop/NativeEmbedSample/WinApi.cs

@ -0,0 +1,74 @@
using System;
using System.Runtime.InteropServices;
namespace NativeEmbedSample
{
public unsafe class WinApi
{
public enum CommonControls : uint
{
ICC_LISTVIEW_CLASSES = 0x00000001, // listview, header
ICC_TREEVIEW_CLASSES = 0x00000002, // treeview, tooltips
ICC_BAR_CLASSES = 0x00000004, // toolbar, statusbar, trackbar, tooltips
ICC_TAB_CLASSES = 0x00000008, // tab, tooltips
ICC_UPDOWN_CLASS = 0x00000010, // updown
ICC_PROGRESS_CLASS = 0x00000020, // progress
ICC_HOTKEY_CLASS = 0x00000040, // hotkey
ICC_ANIMATE_CLASS = 0x00000080, // animate
ICC_WIN95_CLASSES = 0x000000FF,
ICC_DATE_CLASSES = 0x00000100, // month picker, date picker, time picker, updown
ICC_USEREX_CLASSES = 0x00000200, // comboex
ICC_COOL_CLASSES = 0x00000400, // rebar (coolbar) control
ICC_INTERNET_CLASSES = 0x00000800,
ICC_PAGESCROLLER_CLASS = 0x00001000, // page scroller
ICC_NATIVEFNTCTL_CLASS = 0x00002000, // native font control
ICC_STANDARD_CLASSES = 0x00004000,
ICC_LINK_CLASS = 0x00008000
}
[StructLayout(LayoutKind.Sequential)]
public struct INITCOMMONCONTROLSEX
{
public int dwSize;
public uint dwICC;
}
[DllImport("Comctl32.dll")]
public static extern void InitCommonControlsEx(ref INITCOMMONCONTROLSEX init);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool DestroyWindow(IntPtr hwnd);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string lib);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr CreateWindowEx(
int dwExStyle,
string lpClassName,
string lpWindowName,
uint dwStyle,
int x,
int y,
int nWidth,
int nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[StructLayout(LayoutKind.Sequential)]
public struct SETTEXTEX
{
public uint Flags;
public uint Codepage;
}
[DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "SendMessageW")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, ref SETTEXTEX wParam, byte[] lParam);
}
}

1
samples/interop/NativeEmbedSample/nodes-license.md

@ -0,0 +1 @@
nodes.mp4 by beeple is licensed under the creative commons license, downloaded from https://vimeo.com/9936271

BIN
samples/interop/NativeEmbedSample/nodes.mp4

Binary file not shown.

2
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -67,7 +67,7 @@ namespace Avalonia.Android
throw new NotSupportedException();
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotSupportedException();
}

4
src/Android/Avalonia.Android/AvaloniaView.cs

@ -33,10 +33,8 @@ namespace Avalonia.Android
return _view.View.DispatchKeyEvent(e);
}
class ViewImpl : TopLevelImpl, IEmbeddableWindowImpl
class ViewImpl : TopLevelImpl
{
public event Action LostFocus;
public ViewImpl(Context context) : base(context)
{
View.Focusable = true;

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -194,6 +194,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
public IPopupImpl CreatePopup() => null;
public Action LostFocus { get; set; }
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
}

23
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
@ -49,6 +50,18 @@ namespace Avalonia.Data.Core
var accessor = plugin?.Start(reference, PropertyName);
// We need to handle accessor fallback before handling validation. Validators do not support null accessors.
if (accessor == null)
{
reference.TryGetTarget(out object instance);
var message = $"Could not find a matching property accessor for '{PropertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
accessor = new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
if (_enableValidation && Next == null)
{
foreach (var validator in ExpressionObserver.DataValidators)
@ -60,15 +73,9 @@ namespace Avalonia.Data.Core
}
}
if (accessor == null)
if (accessor is null)
{
reference.TryGetTarget(out object instance);
var message = $"Could not find a matching property accessor for '{PropertyName}' on '{instance}'";
var exception = new MissingMemberException(message);
accessor = new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
throw new AvaloniaInternalException("Data validators must return non-null accessor.");
}
_accessor = accessor;

5
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -10,7 +10,7 @@ namespace Avalonia.Controls.Embedding
{
public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, IDisposable
{
public EmbeddableControlRoot(IEmbeddableWindowImpl impl) : base(impl)
public EmbeddableControlRoot(ITopLevelImpl impl) : base(impl)
{
}
@ -19,9 +19,6 @@ namespace Avalonia.Controls.Embedding
{
}
[CanBeNull]
public new IEmbeddableWindowImpl PlatformImpl => (IEmbeddableWindowImpl) base.PlatformImpl;
protected bool EnforceClientSize { get; set; } = true;
public void Prepare()

1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -63,6 +63,7 @@ namespace Avalonia.Controls.Embedding.Offscreen
}
public Action Closed { get; set; }
public Action LostFocus { get; set; }
public abstract IMouseDevice MouseDevice { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }

141
src/Avalonia.Controls/NativeControlHost.cs

@ -0,0 +1,141 @@
using Avalonia.Controls.Platform;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Controls
{
public class NativeControlHost : Control
{
private TopLevel _currentRoot;
private INativeControlHostImpl _currentHost;
private INativeControlHostControlTopLevelAttachment _attachment;
private IPlatformHandle _nativeControlHandle;
private bool _queuedForDestruction;
static NativeControlHost()
{
IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
}
private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
=> host.UpdateHost();
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = e.Root as TopLevel;
UpdateHost();
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
_currentRoot = null;
UpdateHost();
}
void UpdateHost()
{
_currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
var needsAttachment = _currentHost != null;
var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
if (needsAttachment)
{
// If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment
if (_attachment != null && _attachment.AttachedTo != _currentHost)
{
if (_attachment != null)
{
if (_attachment.IsCompatibleWith(_currentHost))
{
_attachment.AttachedTo = _currentHost;
}
else
{
_attachment.Dispose();
_attachment = null;
}
}
}
// If there is no attachment, but the control exists,
// attempt to attach to to the current toplevel or destroy the control if it's incompatible
if (_attachment == null && _nativeControlHandle != null)
{
if (_currentHost.IsCompatibleWith(_nativeControlHandle))
_attachment = _currentHost.CreateNewAttachment(_nativeControlHandle);
else
DestroyNativeControl();
}
// There is no control handle an no attachment, create both
if (_nativeControlHandle == null)
{
_attachment = _currentHost.CreateNewAttachment(parent =>
_nativeControlHandle = CreateNativeControlCore(parent));
}
}
else
{
// Immediately detach the control from the current toplevel if there is an existing attachment
if (_attachment != null)
_attachment.AttachedTo = null;
// Don't destroy the control immediately, it might be just being reparented to another TopLevel
if (_nativeControlHandle != null && !_queuedForDestruction)
{
_queuedForDestruction = true;
Dispatcher.UIThread.Post(CheckDestruction, DispatcherPriority.Background);
}
}
if (needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
else if (needsAttachment)
_attachment?.Hide();
}
public bool TryUpdateNativeControlPosition()
{
var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
if(needsShow)
_attachment?.ShowInBounds(TransformedBounds.Value);
return needsShow;
}
void CheckDestruction()
{
_queuedForDestruction = false;
if (_currentRoot == null)
DestroyNativeControl();
}
protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
{
return _currentHost.CreateDefaultChild(parent);
}
void DestroyNativeControl()
{
if (_nativeControlHandle != null)
{
_attachment?.Dispose();
_attachment = null;
DestroyNativeControlCore(_nativeControlHandle);
_nativeControlHandle = null;
}
}
protected virtual void DestroyNativeControlCore(IPlatformHandle control)
{
((INativeControlHostDestroyableControlHandle)control).Destroy();
}
}
}

12
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
@ -67,6 +68,9 @@ namespace Avalonia.Controls.Platform
window.Deactivated += WindowDeactivated;
}
if (_root is TopLevel tl)
tl.PlatformImpl.LostFocus += TopLevelLostPlatformFocus;
_inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
}
@ -96,6 +100,9 @@ namespace Avalonia.Controls.Platform
{
root.Deactivated -= WindowDeactivated;
}
if (_root is TopLevel tl)
tl.PlatformImpl.LostFocus -= TopLevelLostPlatformFocus;
_inputManagerSubscription?.Dispose();
@ -409,6 +416,11 @@ namespace Avalonia.Controls.Platform
{
Menu?.Close();
}
private void TopLevelLostPlatformFocus()
{
Menu?.Close();
}
protected void Click(IMenuItem item)
{

12
src/Avalonia.Controls/Platform/IEmbeddableWindowImpl.cs

@ -1,12 +0,0 @@
using System;
namespace Avalonia.Platform
{
/// <summary>
/// Defines a platform-specific embeddable window implementation.
/// </summary>
public interface IEmbeddableWindowImpl : ITopLevelImpl
{
event Action LostFocus;
}
}

32
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@ -0,0 +1,32 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Platform
{
public interface INativeControlHostImpl
{
INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent);
INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create);
INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle);
bool IsCompatibleWith(IPlatformHandle handle);
}
public interface INativeControlHostDestroyableControlHandle : IPlatformHandle
{
void Destroy();
}
public interface INativeControlHostControlTopLevelAttachment : IDisposable
{
INativeControlHostImpl AttachedTo { get; set; }
bool IsCompatibleWith(INativeControlHostImpl host);
void Hide();
void ShowInBounds(TransformedBounds transformedBounds);
}
public interface ITopLevelImplWithNativeControlHost
{
INativeControlHostImpl NativeControlHost { get; }
}
}

5
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -104,6 +104,11 @@ namespace Avalonia.Platform
/// Gets or sets a method called when the underlying implementation is destroyed.
/// </summary>
Action Closed { get; set; }
/// <summary>
/// Gets or sets a method called when the input focus is lost.
/// </summary>
Action LostFocus { get; set; }
/// <summary>
/// Gets a mouse device associated with toplevel

2
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

@ -3,6 +3,6 @@ namespace Avalonia.Platform
public interface IWindowingPlatform
{
IWindowImpl CreateWindow();
IEmbeddableWindowImpl CreateEmbeddableWindow();
IWindowImpl CreateEmbeddableWindow();
}
}

2
src/Avalonia.Controls/Platform/PlatformManager.cs

@ -34,7 +34,7 @@ namespace Avalonia.Controls.Platform
return s_designerMode ? (IWindowImpl)platform.CreateEmbeddableWindow() : platform.CreateWindow();
}
public static IEmbeddableWindowImpl CreateEmbeddableWindow()
public static IWindowImpl CreateEmbeddableWindow()
{
var platform = AvaloniaLocator.Current.GetService<IWindowingPlatform>();
if (platform == null)

11
src/Avalonia.Controls/Primitives/Popup.cs

@ -9,6 +9,7 @@ using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
#nullable enable
@ -351,6 +352,10 @@ namespace Avalonia.Controls.Primitives
DeferCleanup(SubscribeToEventHandler<Window, EventHandler>(window, WindowDeactivated,
(x, handler) => x.Deactivated += handler,
(x, handler) => x.Deactivated -= handler));
DeferCleanup(SubscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, WindowLostFocus,
(x, handler) => x.LostFocus += handler,
(x, handler) => x.LostFocus -= handler));
}
else
{
@ -610,6 +615,12 @@ namespace Avalonia.Controls.Primitives
Close();
}
}
private void WindowLostFocus()
{
if(!StaysOpen)
Close();
}
private IgnoreIsOpenScope BeginIgnoringIsOpen()
{

4
src/Avalonia.Controls/Remote/RemoteServer.cs

@ -10,13 +10,13 @@ namespace Avalonia.Controls.Remote
{
private EmbeddableControlRoot _topLevel;
class EmbeddableRemoteServerTopLevelImpl : RemoteServerTopLevelImpl, IEmbeddableWindowImpl
class EmbeddableRemoteServerTopLevelImpl : RemoteServerTopLevelImpl
{
public EmbeddableRemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) : base(transport)
{
}
#pragma warning disable 67
public event Action LostFocus;
public Action LostFocus { get; set; }
}

15
src/Avalonia.Controls/TopLevel.cs

@ -11,6 +11,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using JetBrains.Annotations;
namespace Avalonia.Controls
@ -170,6 +171,8 @@ namespace Avalonia.Controls
nameof(IResourceHost.ResourcesChanged),
this);
}
impl.LostFocus += PlatformImpl_LostFocus;
}
/// <summary>
@ -471,5 +474,17 @@ namespace Avalonia.Controls
{
(this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect);
}
void PlatformImpl_LostFocus()
{
var focused = (IVisual)FocusManager.Instance.Current;
if (focused == null)
return;
while (focused.VisualParent != null)
focused = focused.VisualParent;
if (focused == this)
KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
}
}
}

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

@ -10,7 +10,7 @@ using Avalonia.Threading;
namespace Avalonia.DesignerSupport.Remote
{
class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl, IEmbeddableWindowImpl
class PreviewerWindowImpl : RemoteServerTopLevelImpl, IWindowImpl
{
private readonly IAvaloniaRemoteTransportConnection _transport;
@ -45,11 +45,6 @@ namespace Avalonia.DesignerSupport.Remote
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Size MaxAutoSizeHint { get; } = new Size(4096, 4096);
public event Action LostFocus
{
add {}
remove {}
}
protected override void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
{

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

@ -19,7 +19,7 @@ namespace Avalonia.DesignerSupport.Remote
public IWindowImpl CreateWindow() => new WindowStub();
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
if (s_lastWindow != null)
{

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

@ -29,6 +29,7 @@ namespace Avalonia.DesignerSupport.Remote
public Action<double> ScalingChanged { get; set; }
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
public Action LostFocus { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public IPopupImpl CreatePopup() => new WindowStub(this);

33
src/Avalonia.Input/KeyboardNavigation.cs

@ -30,6 +30,19 @@ namespace Avalonia.Input
"TabOnceActiveElement",
typeof(KeyboardNavigation));
/// <summary>
/// Defines the IsTabStop attached property.
/// </summary>
/// <remarks>
/// The IsTabStop attached property determines whether the control is focusable by tab navigation.
/// </remarks>
public static readonly AttachedProperty<bool> IsTabStopProperty =
AvaloniaProperty.RegisterAttached<InputElement, bool>(
"IsTabStop",
typeof(KeyboardNavigation),
true);
/// <summary>
/// Gets the <see cref="TabNavigationProperty"/> for a container.
/// </summary>
@ -69,5 +82,25 @@ namespace Avalonia.Input
{
element.SetValue(TabOnceActiveElementProperty, value);
}
/// <summary>
/// Sets the <see cref="IsTabStopProperty"/> for a container.
/// </summary>
/// <param name="element">The container.</param>
/// <param name="value">Value indicating whether the container is a tab stop.</param>
public static void SetIsTabStop(InputElement element, bool value)
{
element.SetValue(IsTabStopProperty, value);
}
/// <summary>
/// Gets the <see cref="IsTabStopProperty"/> for a container.
/// </summary>
/// <param name="element">The container.</param>
/// <returns>Whether the container is a tab stop.</returns>
public static bool GetIsTabStop(InputElement element)
{
return element.GetValue(IsTabStopProperty);
}
}
}

39
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -77,7 +77,8 @@ namespace Avalonia.Input.Navigation
/// <param name="element">The element.</param>
/// <param name="direction">The tab direction. Must be Next or Previous.</param>
/// <returns>The element's focusable descendants.</returns>
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element, NavigationDirection direction)
private static IEnumerable<IInputElement> GetFocusableDescendants(IInputElement element,
NavigationDirection direction)
{
var mode = KeyboardNavigation.GetTabNavigation((InputElement)element);
@ -113,7 +114,7 @@ namespace Avalonia.Input.Navigation
}
else
{
if (child.CanFocus())
if (child.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement)child))
{
yield return child;
}
@ -122,7 +123,10 @@ namespace Avalonia.Input.Navigation
{
foreach (var descendant in GetFocusableDescendants(child, direction))
{
yield return descendant;
if (KeyboardNavigation.GetIsTabStop((InputElement)descendant))
{
yield return descendant;
}
}
}
}
@ -167,7 +171,9 @@ namespace Avalonia.Input.Navigation
{
element = navigable.GetControl(direction, element, false);
if (element != null && element.CanFocus())
if (element != null &&
element.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) element))
{
break;
}
@ -233,26 +239,22 @@ namespace Avalonia.Input.Navigation
return customNext.next;
}
if (sibling.CanFocus())
if (sibling.CanFocus() && KeyboardNavigation.GetIsTabStop((InputElement) sibling))
{
return sibling;
}
else
next = direction == NavigationDirection.Next ?
GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendants(sibling, direction).LastOrDefault();
if (next != null)
{
next = direction == NavigationDirection.Next ?
GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendants(sibling, direction).LastOrDefault();
if(next != null)
{
return next;
}
return next;
}
}
if (next == null)
{
next = GetFirstInNextContainer(element, parent, direction);
}
next = GetFirstInNextContainer(element, parent, direction);
}
else
{
@ -264,7 +266,8 @@ namespace Avalonia.Input.Navigation
return next;
}
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, NavigationDirection direction)
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element,
NavigationDirection direction)
{
if (element is ICustomKeyboardNavigation custom)
{

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -122,7 +122,7 @@ namespace Avalonia.Native
return new WindowImpl(_factory, _options, _glFeature);
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}

136
src/Avalonia.Native/NativeControlHostImpl.cs

@ -0,0 +1,136 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Native.Interop;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Native
{
class NativeControlHostImpl : IDisposable, INativeControlHostImpl
{
private IAvnNativeControlHost _host;
public NativeControlHostImpl(IAvnNativeControlHost host)
{
_host = host;
}
public void Dispose()
{
_host?.Dispose();
_host = null;
}
class DestroyableNSView : INativeControlHostDestroyableControlHandle
{
private IAvnNativeControlHost _impl;
private IntPtr _nsView;
public DestroyableNSView(IAvnNativeControlHost impl)
{
_impl = new IAvnNativeControlHost(impl.NativePointer);
_impl.AddRef();
_nsView = _impl.CreateDefaultChild(IntPtr.Zero);
}
public IntPtr Handle => _nsView;
public string HandleDescriptor => "NSView";
public void Destroy()
{
if (_impl != null)
{
_impl.DestroyDefaultChild(_nsView);
_impl.Dispose();
_impl = null;
_nsView = IntPtr.Zero;
}
}
}
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
=> new DestroyableNSView(_host);
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(
Func<IPlatformHandle, IPlatformHandle> create)
{
var a = new Attachment(_host.CreateAttachment());
try
{
var child = create(a.GetParentHandle());
a.InitWithChild(child);
a.AttachedTo = this;
return a;
}
catch
{
a.Dispose();
throw;
}
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
{
var a = new Attachment(_host.CreateAttachment());
a.InitWithChild(handle);
a.AttachedTo = this;
return a;
}
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "NSView";
class Attachment : INativeControlHostControlTopLevelAttachment
{
private IAvnNativeControlHostTopLevelAttachment _native;
private NativeControlHostImpl _attachedTo;
public IPlatformHandle GetParentHandle() => new PlatformHandle(_native.ParentHandle, "NSView");
public Attachment(IAvnNativeControlHostTopLevelAttachment native)
{
_native = native;
}
public void Dispose()
{
if (_native != null)
{
_native.ReleaseChild();
_native.Dispose();
_native = null;
}
}
public INativeControlHostImpl AttachedTo
{
get => _attachedTo;
set
{
var host = (NativeControlHostImpl)value;
if(host == null)
_native.AttachTo(null);
else
_native.AttachTo(host._host);
_attachedTo = host;
}
}
public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
public void Hide()
{
_native?.Hide();
}
public void ShowInBounds(TransformedBounds transformedBounds)
{
if (_attachedTo == null)
throw new InvalidOperationException("Native control isn't attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
Math.Max(1, bounds.Height));
_native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
}
public void InitWithChild(IPlatformHandle handle)
=> _native.InitializeWithChildHandle(handle.Handle);
}
}
}

17
src/Avalonia.Native/WindowImplBase.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -43,7 +44,7 @@ namespace Avalonia.Native
}
public abstract class WindowBaseImpl : IWindowBaseImpl,
IFramebufferPlatformSurface
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost
{
IInputRoot _inputRoot;
IAvnWindowBase _native;
@ -57,6 +58,7 @@ namespace Avalonia.Native
private Size _lastRenderedLogicalSize;
private double _savedScaling;
private GlPlatformSurface _glSurface;
private NativeControlHostImpl _nativeControlHost;
private IGlContext _glContext;
internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
@ -80,6 +82,7 @@ namespace Avalonia.Native
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;
_nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost());
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
@ -101,6 +104,8 @@ namespace Avalonia.Native
this
};
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
public ILockedFramebuffer Lock()
{
var w = _savedLogicalSize.Width * _savedScaling;
@ -119,6 +124,8 @@ namespace Avalonia.Native
}, (int)w, (int)h, new Vector(dpi, dpi));
}
public Action LostFocus { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action Closed { get; set; }
@ -201,6 +208,11 @@ namespace Avalonia.Native
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
}
void IAvnWindowBaseEvents.LostFocus()
{
_parent.LostFocus?.Invoke();
}
public AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers,
@ -288,6 +300,9 @@ namespace Avalonia.Native
_native?.Dispose();
_native = null;
_nativeControlHost?.Dispose();
_nativeControlHost = null;
(Screen as ScreenImpl)?.Dispose();
}

9
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@ -394,13 +394,8 @@ namespace Avalonia.Rendering.SceneGraph
}
}
private static bool ShouldStartLayer(IVisual visual)
{
var o = visual as IAvaloniaObject;
return visual.VisualChildren.Count > 0 &&
o != null &&
o.IsAnimating(Visual.OpacityProperty);
}
// HACK: Disabled layers because they're broken in current renderer. See #2244.
private static bool ShouldStartLayer(IVisual visual) => false;
private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
{

2
src/Avalonia.Visuals/VisualTree/TransformedBounds.cs

@ -76,5 +76,7 @@ namespace Avalonia.VisualTree
{
return !left.Equals(right);
}
public override string ToString() => $"Bounds: {Bounds} Clip: {Clip} Transform {Transform}";
}
}

10
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -207,6 +207,9 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern void gtk_widget_destroy(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
@ -219,6 +222,13 @@ namespace Avalonia.X11.NativeDialogs
[DllImport(GdkName)]
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[DllImport(GdkName)]
public static extern IntPtr gdk_x11_window_get_xid(IntPtr window);
[DllImport(GtkName)]
public static extern IntPtr gtk_container_add(IntPtr container, IntPtr widget);
[DllImport(GdkName)]
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);

190
src/Avalonia.X11/X11NativeControlHost.cs

@ -0,0 +1,190 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.VisualTree;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
// TODO: Actually implement XEmbed instead of simply using XReparentWindow
class X11NativeControlHost : INativeControlHostImpl
{
private readonly AvaloniaX11Platform _platform;
public X11Window Window { get; }
public X11NativeControlHost(AvaloniaX11Platform platform, X11Window window)
{
_platform = platform;
Window = window;
}
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
{
var ch = new DumbWindow(_platform.Info);
XSync(_platform.Display, false);
return ch;
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
{
var holder = new DumbWindow(_platform.Info, Window.Handle.Handle);
Attachment attachment = null;
try
{
var child = create(holder);
// ReSharper disable once UseObjectOrCollectionInitializer
// It has to be assigned to the variable before property setter is called so we dispose it on exception
attachment = new Attachment(_platform.Display, holder, _platform.OrphanedWindow, child);
attachment.AttachedTo = this;
return attachment;
}
catch
{
attachment?.Dispose();
holder?.Destroy();
throw;
}
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
{
if (!IsCompatibleWith(handle))
throw new ArgumentException(handle.HandleDescriptor + " is not compatible with the current window",
nameof(handle));
var attachment = new Attachment(_platform.Display, new DumbWindow(_platform.Info, Window.Handle.Handle),
_platform.OrphanedWindow, handle) { AttachedTo = this };
return attachment;
}
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "XID";
class DumbWindow : INativeControlHostDestroyableControlHandle
{
private readonly IntPtr _display;
public DumbWindow(X11Info x11, IntPtr? parent = null)
{
_display = x11.Display;
/*Handle = XCreateSimpleWindow(x11.Display, XLib.XDefaultRootWindow(_display),
0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero);*/
var attr = new XSetWindowAttributes
{
backing_store = 1,
bit_gravity = Gravity.NorthWestGravity,
win_gravity = Gravity.NorthWestGravity,
};
parent = parent ?? XDefaultRootWindow(x11.Display);
Handle = XCreateWindow(_display, parent.Value, 0, 0,
1,1, 0, 0,
(int)CreateWindowArgs.InputOutput,
IntPtr.Zero,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
SetWindowValuemask.BackPixel |
SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
}
public IntPtr Handle { get; private set; }
public string HandleDescriptor => "XID";
public void Destroy()
{
if (Handle != IntPtr.Zero)
{
XDestroyWindow(_display, Handle);
Handle = IntPtr.Zero;
}
}
}
class Attachment : INativeControlHostControlTopLevelAttachment
{
private readonly IntPtr _display;
private readonly IntPtr _orphanedWindow;
private DumbWindow _holder;
private IPlatformHandle _child;
private X11NativeControlHost _attachedTo;
private bool _mapped;
public Attachment(IntPtr display, DumbWindow holder, IntPtr orphanedWindow, IPlatformHandle child)
{
_display = display;
_orphanedWindow = orphanedWindow;
_holder = holder;
_child = child;
XReparentWindow(_display, child.Handle, holder.Handle, 0, 0);
XMapWindow(_display, child.Handle);
}
public void Dispose()
{
if (_child != null)
{
XReparentWindow(_display, _child.Handle, _orphanedWindow, 0, 0);
_child = null;
}
_holder?.Destroy();
_holder = null;
_attachedTo = null;
}
void CheckDisposed()
{
if (_child == null)
throw new ObjectDisposedException("X11 INativeControlHostControlTopLevelAttachment");
}
public INativeControlHostImpl AttachedTo
{
get => _attachedTo;
set
{
CheckDisposed();
_attachedTo = (X11NativeControlHost)value;
if (value == null)
{
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
XReparentWindow(_display, _holder.Handle, _orphanedWindow, 0, 0);
}
else
{
XReparentWindow(_display, _holder.Handle, _attachedTo.Window.Handle.Handle, 0, 0);
}
}
}
public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost;
public void Hide()
{
if(_attachedTo == null || _child == null)
return;
_mapped = false;
XUnmapWindow(_display, _holder.Handle);
}
public void ShowInBounds(TransformedBounds transformedBounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height);
XMoveResizeWindow(_display, _holder.Handle, pixelRect.X, pixelRect.Y, pixelRect.Width,
pixelRect.Height);
if (!_mapped)
{
XMapWindow(_display, _holder.Handle);
XRaiseWindow(_display, _holder.Handle);
_mapped = true;
}
Console.WriteLine($"Moved {_child.Handle} to {pixelRect}");
}
}
}
}

5
src/Avalonia.X11/X11Platform.cs

@ -26,6 +26,7 @@ namespace Avalonia.X11
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
public X11PlatformOptions Options { get; private set; }
public IntPtr OrphanedWindow { get; private set; }
public X11Globals Globals { get; private set; }
public void Initialize(X11PlatformOptions options)
{
@ -33,6 +34,8 @@ namespace Avalonia.X11
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
OrphanedWindow = XCreateSimpleWindow(Display, XDefaultRootWindow(Display), 0, 0, 1, 1, 0, IntPtr.Zero,
IntPtr.Zero);
if (Display == IntPtr.Zero)
throw new Exception("XOpenDisplay failed");
XError.Init();
@ -82,7 +85,7 @@ namespace Avalonia.X11
return new X11Window(this, null);
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotSupportedException();
}

8
src/Avalonia.X11/X11Window.cs

@ -21,7 +21,9 @@ using static Avalonia.X11.XLib;
// ReSharper disable StringLiteralTypo
namespace Avalonia.X11
{
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client, ITopLevelImplWithNativeMenuExporter
unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client,
ITopLevelImplWithNativeMenuExporter,
ITopLevelImplWithNativeControlHost
{
private readonly AvaloniaX11Platform _platform;
private readonly IWindowImpl _popupParent;
@ -185,6 +187,7 @@ namespace Avalonia.X11
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
if (platform.Options.UseDBusMenu)
NativeMenuExporter = DBusMenuExporter.TryCreate(_handle);
NativeControlHost = new X11NativeControlHost(_platform, this);
}
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
@ -313,6 +316,7 @@ namespace Avalonia.X11
public Action Closed { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action LostFocus { get; set; }
public IRenderer CreateRenderer(IRenderRoot root)
{
@ -1094,7 +1098,7 @@ namespace Avalonia.X11
public IPopupPositioner PopupPositioner { get; }
public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
public INativeControlHostImpl NativeControlHost { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
_transparencyHelper.SetTransparencyRequest(transparencyLevel);

4
src/Avalonia.X11/XI2Manager.cs

@ -239,13 +239,13 @@ namespace Avalonia.X11
if (ev.Type == XiEventType.XI_ButtonPress && ev.Button >= 4 && ev.Button <= 7 && !ev.Emulated)
{
Vector? scrollDelta = ev.Button switch
var scrollDelta = ev.Button switch
{
4 => new Vector(0, 1),
5 => new Vector(0, -1),
6 => new Vector(1, 0),
7 => new Vector(-1, 0),
_ => null
_ => (Vector?)null
};
if (scrollDelta.HasValue)

8
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -10,7 +10,7 @@ using Avalonia.Rendering;
namespace Avalonia.LinuxFramebuffer
{
class FramebufferToplevelImpl : IEmbeddableWindowImpl, IScreenInfoProvider
class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider
{
private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend;
@ -75,11 +75,7 @@ namespace Avalonia.LinuxFramebuffer
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
public Action Closed { get; set; }
public event Action LostFocus
{
add {}
remove {}
}
public Action LostFocus { get; set; }
public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);

21
src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs

@ -24,9 +24,7 @@ namespace Avalonia.Win32.Embedding
if (_root.IsFocused)
FocusManager.Instance.Focus(null);
_root.GotFocus += RootGotFocus;
// ReSharper disable once PossibleNullReferenceException
// Always non-null at this point
_root.PlatformImpl.LostFocus += PlatformImpl_LostFocus;
FixPosition();
}
@ -36,23 +34,6 @@ namespace Avalonia.Win32.Embedding
set { _root.Content = value; }
}
void Unfocus()
{
var focused = (IVisual)FocusManager.Instance.Current;
if (focused == null)
return;
while (focused.VisualParent != null)
focused = focused.VisualParent;
if (focused == _root)
KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
}
private void PlatformImpl_LostFocus()
{
Unfocus();
}
protected override void Dispose(bool disposing)
{
if (disposing)

6
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -18,11 +18,11 @@ using MouseButton = System.Windows.Input.MouseButton;
namespace Avalonia.Win32.Interop.Wpf
{
class WpfTopLevelImpl : FrameworkElement, IEmbeddableWindowImpl
class WpfTopLevelImpl : FrameworkElement, ITopLevelImpl
{
private HwndSource _currentHwndSource;
private readonly HwndSourceHook _hook;
private readonly IEmbeddableWindowImpl _ttl;
private readonly ITopLevelImpl _ttl;
private IInputRoot _inputRoot;
private readonly IEnumerable<object> _surfaces;
private readonly IMouseDevice _mouse;
@ -241,7 +241,7 @@ namespace Avalonia.Win32.Interop.Wpf
Action<WindowTransparencyLevel> ITopLevelImpl.TransparencyLevelChanged { get; set; }
Action ITopLevelImpl.Closed { get; set; }
public new event Action LostFocus;
public new Action LostFocus { get; set; }
internal Vector GetScaling()
{

61
src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs

@ -6,11 +6,8 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class EmbeddedWindowImpl : WindowImpl, IEmbeddableWindowImpl
class EmbeddedWindowImpl : WindowImpl
{
private static IntPtr DefaultParentWindow = CreateParentWindow();
private static UnmanagedMethods.WndProc _wndProcDelegate;
protected override IntPtr CreateWindowOverride(ushort atom)
{
var hWnd = UnmanagedMethods.CreateWindowEx(
@ -22,66 +19,12 @@ namespace Avalonia.Win32
0,
640,
480,
DefaultParentWindow,
OffscreenParentWindow.Handle,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
return hWnd;
}
protected override IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == (uint)UnmanagedMethods.WindowsMessage.WM_KILLFOCUS)
LostFocus?.Invoke();
return base.WndProc(hWnd, msg, wParam, lParam);
}
public event Action LostFocus;
private static IntPtr CreateParentWindow()
{
_wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc);
var wndClassEx = new UnmanagedMethods.WNDCLASSEX
{
cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
hInstance = UnmanagedMethods.GetModuleHandle(null),
lpfnWndProc = _wndProcDelegate,
lpszClassName = "AvaloniaEmbeddedWindow-" + Guid.NewGuid(),
};
var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
if (atom == 0)
{
throw new Win32Exception();
}
var hwnd = UnmanagedMethods.CreateWindowEx(
0,
atom,
null,
(int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
if (hwnd == IntPtr.Zero)
{
throw new Win32Exception();
}
return hwnd;
}
private static IntPtr ParentWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
}
}

8
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -15,7 +15,7 @@ namespace Avalonia.Win32.Interop
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Using Win32 naming for consistency.")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements must be documented", Justification = "Look in Win32 docs.")]
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items must be documented", Justification = "Look in Win32 docs.")]
internal static class UnmanagedMethods
internal unsafe static class UnmanagedMethods
{
public const int CW_USEDEFAULT = unchecked((int)0x80000000);
@ -1001,6 +1001,10 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, ref RECT lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool InvalidateRect(IntPtr hWnd, RECT* lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool IsWindowEnabled(IntPtr hWnd);
@ -1051,6 +1055,8 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow);
[DllImport("kernel32.dll", SetLastError = true)]

58
src/Windows/Avalonia.Win32/OffscreenParentWindow.cs

@ -0,0 +1,58 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class OffscreenParentWindow
{
public static IntPtr Handle { get; } = CreateParentWindow();
private static UnmanagedMethods.WndProc s_wndProcDelegate;
private static IntPtr CreateParentWindow()
{
s_wndProcDelegate = new UnmanagedMethods.WndProc(ParentWndProc);
var wndClassEx = new UnmanagedMethods.WNDCLASSEX
{
cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
hInstance = UnmanagedMethods.GetModuleHandle(null),
lpfnWndProc = s_wndProcDelegate,
lpszClassName = "AvaloniaEmbeddedWindow-" + Guid.NewGuid(),
};
var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
if (atom == 0)
{
throw new Win32Exception();
}
var hwnd = UnmanagedMethods.CreateWindowEx(
0,
atom,
null,
(int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
if (hwnd == IntPtr.Zero)
{
throw new Win32Exception();
}
return hwnd;
}
private static IntPtr ParentWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
}
}

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

@ -10,11 +10,31 @@ namespace Avalonia.Win32
private bool _dropShadowHint = true;
private Size? _maxAutoSize;
// This is needed because we are calling virtual methods from constructors
// One fabulous design decision leads to another, I guess
[ThreadStatic]
private static IntPtr s_parentHandle;
public override void Show()
{
UnmanagedMethods.ShowWindow(Handle.Handle, UnmanagedMethods.ShowWindowCommand.ShowNoActivate);
var parent = UnmanagedMethods.GetParent(Handle.Handle);
if (parent != IntPtr.Zero)
{
IntPtr nextParent = parent;
while (nextParent != IntPtr.Zero)
{
parent = nextParent;
nextParent = UnmanagedMethods.GetParent(parent);
}
UnmanagedMethods.SetFocus(parent);
}
}
protected override bool ShouldTakeFocusOnClick => false;
public override Size MaxAutoSizeHint
{
get
@ -56,10 +76,11 @@ namespace Avalonia.Win32
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
IntPtr.Zero,
s_parentHandle,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
s_parentHandle = IntPtr.Zero;
EnableBoxShadow(result, _dropShadowHint);
@ -80,7 +101,22 @@ namespace Avalonia.Win32
}
}
public PopupImpl(IWindowBaseImpl parent)
// This is needed because we are calling virtual methods from constructors
// One fabulous design decision leads to another, I guess
static IWindowBaseImpl SaveParentHandle(IWindowBaseImpl parent)
{
s_parentHandle = parent.Handle.Handle;
return parent;
}
// This is needed because we are calling virtual methods from constructors
// One fabulous design decision leads to another, I guess
public PopupImpl(IWindowBaseImpl parent) : this(SaveParentHandle(parent), false)
{
}
private PopupImpl(IWindowBaseImpl parent, bool dummy) : base()
{
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}

201
src/Windows/Avalonia.Win32/Win32NativeControlHost.cs

@ -0,0 +1,201 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.VisualTree;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32
{
class Win32NativeControlHost : INativeControlHostImpl
{
public WindowImpl Window { get; }
public Win32NativeControlHost(WindowImpl window)
{
Window = window;
}
void AssertCompatible(IPlatformHandle handle)
{
if (!IsCompatibleWith(handle))
throw new ArgumentException($"Don't know what to do with {handle.HandleDescriptor}");
}
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
{
AssertCompatible(parent);
return new DumbWindow(parent.Handle);
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
{
var holder = new DumbWindow(Window.Handle.Handle);
Win32NativeControlAttachment attachment = null;
try
{
var child = create(holder);
// ReSharper disable once UseObjectOrCollectionInitializer
// It has to be assigned to the variable before property setter is called so we dispose it on exception
attachment = new Win32NativeControlAttachment(holder, child);
attachment.AttachedTo = this;
return attachment;
}
catch
{
attachment?.Dispose();
holder?.Destroy();
throw;
}
}
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
{
AssertCompatible(handle);
return new Win32NativeControlAttachment(new DumbWindow(Window.Handle.Handle),
handle) { AttachedTo = this };
}
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == "HWND";
class DumbWindow : IDisposable, INativeControlHostDestroyableControlHandle
{
public IntPtr Handle { get;}
public string HandleDescriptor => "HWND";
public void Destroy() => Dispose();
UnmanagedMethods.WndProc _wndProcDelegate;
private readonly string _className;
public DumbWindow(IntPtr? parent = null)
{
_wndProcDelegate = WndProc;
var wndClassEx = new UnmanagedMethods.WNDCLASSEX
{
cbSize = Marshal.SizeOf<UnmanagedMethods.WNDCLASSEX>(),
hInstance = UnmanagedMethods.GetModuleHandle(null),
lpfnWndProc = _wndProcDelegate,
lpszClassName = _className = "AvaloniaDumbWindow-" + Guid.NewGuid(),
};
var atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
Handle = UnmanagedMethods.CreateWindowEx(
0,
atom,
null,
(int)UnmanagedMethods.WindowStyles.WS_CHILD,
0,
0,
640,
480,
parent ?? OffscreenParentWindow.Handle,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
}
private void ReleaseUnmanagedResources()
{
UnmanagedMethods.DestroyWindow(Handle);
UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null));
}
public void Dispose()
{
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
~DumbWindow()
{
ReleaseUnmanagedResources();
}
}
class Win32NativeControlAttachment : INativeControlHostControlTopLevelAttachment
{
private DumbWindow _holder;
private IPlatformHandle _child;
private Win32NativeControlHost _attachedTo;
public Win32NativeControlAttachment(DumbWindow holder, IPlatformHandle child)
{
_holder = holder;
_child = child;
UnmanagedMethods.SetParent(child.Handle, _holder.Handle);
UnmanagedMethods.ShowWindow(child.Handle, UnmanagedMethods.ShowWindowCommand.Show);
}
void CheckDisposed()
{
if (_holder == null)
throw new ObjectDisposedException(nameof(Win32NativeControlAttachment));
}
public void Dispose()
{
if (_child != null)
UnmanagedMethods.SetParent(_child.Handle, OffscreenParentWindow.Handle);
_holder?.Dispose();
_holder = null;
_child = null;
_attachedTo = null;
}
public INativeControlHostImpl AttachedTo
{
get => _attachedTo;
set
{
CheckDisposed();
_attachedTo = (Win32NativeControlHost) value;
if (_attachedTo == null)
{
UnmanagedMethods.ShowWindow(_holder.Handle, UnmanagedMethods.ShowWindowCommand.Hide);
UnmanagedMethods.SetParent(_holder.Handle, OffscreenParentWindow.Handle);
}
else
UnmanagedMethods.SetParent(_holder.Handle, _attachedTo.Window.Handle.Handle);
}
}
public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost;
public void Hide()
{
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero,
-100, -100, 1, 1,
UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW |
UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
}
public unsafe void ShowInBounds(TransformedBounds transformedBounds)
{
CheckDisposed();
if (_attachedTo == null)
throw new InvalidOperationException("The control isn't currently attached to a toplevel");
var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) *
new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling);
var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width),
Math.Max(1, (int)bounds.Height));
UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, pixelRect.Width, pixelRect.Height, true);
UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, pixelRect.X, pixelRect.Y, pixelRect.Width,
pixelRect.Height,
UnmanagedMethods.SetWindowPosFlags.SWP_SHOWWINDOW
| UnmanagedMethods.SetWindowPosFlags.SWP_NOZORDER
| UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE);
UnmanagedMethods.InvalidateRect(_attachedTo.Window.Handle.Handle, null, false);
}
}
}
}

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

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

16
src/Windows/Avalonia.Win32/WindowImpl.WndProc.cs

@ -5,6 +5,7 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Win32.Input;
@ -22,7 +23,8 @@ namespace Avalonia.Win32
uint timestamp = unchecked((uint)GetMessageTime());
RawInputEventArgs e = null;
var shouldTakeFocus = false;
switch ((WindowsMessage)msg)
{
case WindowsMessage.WM_ACTIVATE:
@ -149,6 +151,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_MBUTTONDOWN:
case WindowsMessage.WM_XBUTTONDOWN:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
@ -177,6 +180,7 @@ namespace Avalonia.Win32
case WindowsMessage.WM_MBUTTONUP:
case WindowsMessage.WM_XBUTTONUP:
{
shouldTakeFocus = ShouldTakeFocusOnClick;
if (ShouldIgnoreTouchEmulatedMessage())
{
break;
@ -429,12 +433,18 @@ namespace Avalonia.Win32
(Screen as ScreenImpl)?.InvalidateScreensCache();
return IntPtr.Zero;
}
case WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
}
#if USE_MANAGED_DRAG
if (_managedDrag.PreprocessInputEvent(ref e))
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
#endif
if (shouldTakeFocus)
SetFocus(_hwnd);
if (e != null && Input != null)
{
@ -519,5 +529,9 @@ namespace Avalonia.Win32
return modifiers;
}
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
protected virtual bool ShouldTakeFocusOnClick => true;
}
}

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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media;
@ -18,7 +19,8 @@ namespace Avalonia.Win32
/// <summary>
/// Window implementation for Win32 platform.
/// </summary>
public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithNativeControlHost
{
private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
@ -52,6 +54,7 @@ namespace Avalonia.Win32
private readonly FramebufferManager _framebuffer;
private readonly IGlPlatformSurface _gl;
private Win32NativeControlHost _nativeControlHost;
private WndProc _wndProcDelegate;
private string _className;
private IntPtr _hwnd;
@ -101,6 +104,7 @@ namespace Avalonia.Win32
Screen = new ScreenImpl();
_nativeControlHost = new Win32NativeControlHost(this);
s_instances.Add(this);
}
@ -123,6 +127,8 @@ namespace Avalonia.Win32
public Action<PixelPoint> PositionChanged { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
@ -523,7 +529,7 @@ namespace Avalonia.Win32
0,
atom,
null,
(int)WindowStyles.WS_OVERLAPPEDWINDOW,
(int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,

8
src/iOS/Avalonia.iOS/EmbeddableImpl.cs

@ -4,7 +4,7 @@ using Avalonia.Platform;
namespace Avalonia.iOS
{
class EmbeddableImpl : TopLevelImpl, IEmbeddableWindowImpl
class EmbeddableImpl : TopLevelImpl
{
public void SetTitle(string title)
{
@ -23,11 +23,5 @@ namespace Avalonia.iOS
public void SetSystemDecorations(SystemDecorations enabled)
{
}
public event Action LostFocus
{
add {}
remove {}
}
}
}

2
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -139,5 +139,7 @@ namespace Avalonia.iOS
public ILockedFramebuffer Lock() => new EmulatedFramebuffer(this);
public IPopupImpl CreatePopup() => null;
public Action LostFocus { get; set; }
}
}

2
src/iOS/Avalonia.iOS/WindowingPlatformImpl.cs

@ -10,7 +10,7 @@ namespace Avalonia.iOS
throw new NotSupportedException();
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public WindowImpl CreateEmbeddableWindow()
{
throw new NotSupportedException();
}

22
tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

@ -578,6 +578,28 @@ namespace Avalonia.Base.UnitTests.Data.Core
Assert.True(true);
}
[Fact]
public void Should_Not_Throw_Exception_When_Enabling_Data_Validation_On_Missing_Member()
{
var source = new Class1();
var target = new PropertyAccessorNode("NotFound", true);
target.Target = new WeakReference<object>(source);
var result = new List<object>();
target.Subscribe(x => result.Add(x));
Assert.Equal(
new object[]
{
new BindingNotification(
new MissingMemberException("Could not find a matching property accessor for 'NotFound' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+Class1'"),
BindingErrorType.Error),
},
result);
}
private interface INext
{
int PropertyChangedSubscriptionCount { get; }

4
tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs

@ -20,11 +20,11 @@ namespace Avalonia.Controls.UnitTests
return _windowImpl?.Invoke() ?? Mock.Of<IWindowImpl>(x => x.Scaling == 1);
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of<IPopupImpl>(x => x.Scaling == 1);
}
}
}

29
tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs

@ -185,6 +185,35 @@ namespace Avalonia.Input.UnitTests
Assert.Equal(next, result);
}
[Fact]
public void Next_Skips_Non_TabStop_Siblings()
{
Button current;
Button next;
var top = new StackPanel
{
Children =
{
new StackPanel
{
Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
(current = new Button { Name = "Button3" }),
new Button { Name="Button4", [KeyboardNavigation.IsTabStopProperty] = false }
}
},
(next = new Button { Name = "Button5" }),
}
};
var result = KeyboardNavigationHandler.GetNext(current, NavigationDirection.Next);
Assert.Equal(next, result);
}
[Fact]
public void Next_Continue_Returns_First_Control_In_Next_Uncle_Container()
{

2
tests/Avalonia.LeakTests/ControlTests.cs

@ -451,6 +451,7 @@ namespace Avalonia.LeakTests
AttachShowAndDetachContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>
@ -486,6 +487,7 @@ namespace Avalonia.LeakTests
BuildAndShowContextMenu(window);
BuildAndShowContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>

2
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

@ -118,7 +118,7 @@ namespace Avalonia.UnitTests
}
}
public IEmbeddableWindowImpl CreateEmbeddableWindow()
public IWindowImpl CreateEmbeddableWindow()
{
throw new NotImplementedException();
}

8
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -557,7 +557,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -599,7 +599,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -629,7 +629,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -658,7 +658,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Should_Draw_Transparent_Layer_With_Correct_Opacity()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))

4
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs

@ -810,7 +810,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
};
Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray());
Assert.Equal(expected, scene.Layers[border].Dirty.ToArray());
// Layers are disabled. See #2244
// Assert.Equal(expected, scene.Layers[border].Dirty.ToArray());
}
}

8
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs

@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
public partial class SceneBuilderTests
{
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -118,7 +118,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -168,7 +168,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void Hiding_Transparent_Control_Should_Remove_Layers()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
@ -218,7 +218,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
}
[Fact]
[Fact(Skip = "Layers are disabled. See #2244")]
public void GeometryClip_Should_Affect_Child_Layers()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))

Loading…
Cancel
Save