Browse Source

Merge branch 'master' into headless-platform

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

+ 28 - 1
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
@@ -1948,6 +1950,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
@@ -2029,6 +2055,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}

+ 22 - 1
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 - 0
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 */,

+ 35 - 1
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 - 0
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 - 0
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 - 0
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;
+}

+ 25 - 3
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];

+ 2 - 2
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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


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

+ 1 - 3
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 - 0
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);
     }

+ 15 - 8
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;

+ 1 - 4
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 - 0
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 - 0
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();
+        }
+        
+    }
+}

+ 16 - 0
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();
 
@@ -333,6 +340,10 @@ namespace Avalonia.Controls.Platform
                 {
                     item.Parent.SelectedItem = null;
                 }
+                else if (!item.IsPointerOverSubMenu)
+                {
+                    item.IsSubMenuOpen = false;
+                }
             }
         }
 
@@ -405,6 +416,11 @@ namespace Avalonia.Controls.Platform
         {
             Menu?.Close();
         }
+        
+        private void TopLevelLostPlatformFocus()
+        {
+            Menu?.Close();
+        }
 
         protected void Click(IMenuItem item)
         {

+ 0 - 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 - 0
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 - 0
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

+ 1 - 1
src/Avalonia.Controls/Platform/IWindowingPlatform.cs

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

+ 1 - 1
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 - 0
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()
         {

+ 2 - 2
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 - 0
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);
+        }
     }
 }

+ 1 - 6
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)
         {

+ 1 - 1
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 - 0
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 - 0
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);
+        }
     }
 }

+ 21 - 18
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)
             {

+ 1 - 1
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 - 0
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);
+        }
+    }
+}

+ 16 - 1
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();
         }
 

+ 2 - 7
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 - 0
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 - 0
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 - 0
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}");
+            }
+        }
+    }
+}

+ 4 - 1
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();
         }

+ 6 - 2
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);
 

+ 2 - 2
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)

+ 2 - 6
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);
 

+ 1 - 20
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)

+ 3 - 3
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()
         {

+ 2 - 59
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);
-        }
     }
 }

+ 7 - 1
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 - 0
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);
+        }
+    }
+}

+ 38 - 2
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 - 0
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);
+            }
+        }
+
+    }
+}

+ 1 - 1
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();

+ 15 - 1
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;
     }
 }

+ 8 - 2
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,

+ 1 - 7
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 - 0
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; }
     }
 }

+ 1 - 1
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 - 0
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; }

+ 2 - 2
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 - 0
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 - 0
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 =>

+ 1 - 1
tests/Avalonia.UnitTests/MockWindowingPlatform.cs

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

+ 4 - 4
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))

+ 3 - 1
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());
             }
         }
 

+ 4 - 4
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))