Browse Source

Merge pull request #3473 from AvaloniaUI/foreign-embed

Native window/view embedding support (target: stable)
stable/outsystems-0.9
danwalmsley 6 years ago
committed by GitHub
parent
commit
4e1895ca18
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      Avalonia.sln
  2. 22
      native/Avalonia.Native/inc/avalonia-native.h
  3. 8
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  4. 36
      native/Avalonia.Native/src/OSX/app.mm
  5. 9
      native/Avalonia.Native/src/OSX/common.h
  6. 145
      native/Avalonia.Native/src/OSX/controlhost.mm
  7. 17
      native/Avalonia.Native/src/OSX/deadlock.mm
  8. 29
      native/Avalonia.Native/src/OSX/window.mm
  9. 8
      samples/interop/NativeEmbedSample/App.xaml
  10. 22
      samples/interop/NativeEmbedSample/App.xaml.cs
  11. 121
      samples/interop/NativeEmbedSample/EmbedSample.cs
  12. 58
      samples/interop/NativeEmbedSample/GtkHelper.cs
  13. 39
      samples/interop/NativeEmbedSample/MacHelper.cs
  14. 43
      samples/interop/NativeEmbedSample/MainWindow.xaml
  15. 36
      samples/interop/NativeEmbedSample/MainWindow.xaml.cs
  16. 29
      samples/interop/NativeEmbedSample/NativeEmbedSample.csproj
  17. 17
      samples/interop/NativeEmbedSample/Program.cs
  18. 74
      samples/interop/NativeEmbedSample/WinApi.cs
  19. 1
      samples/interop/NativeEmbedSample/nodes-license.md
  20. BIN
      samples/interop/NativeEmbedSample/nodes.mp4
  21. 141
      src/Avalonia.Controls/NativeControlHost.cs
  22. 12
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  23. 32
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  24. 14
      src/Avalonia.Controls/Primitives/Popup.cs
  25. 15
      src/Avalonia.Controls/TopLevel.cs
  26. 136
      src/Avalonia.Native/NativeControlHostImpl.cs
  27. 18
      src/Avalonia.Native/WindowImplBase.cs
  28. 2
      src/Avalonia.Visuals/VisualTree/TransformedBounds.cs
  29. 10
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  30. 190
      src/Avalonia.X11/X11NativeControlHost.cs
  31. 3
      src/Avalonia.X11/X11Platform.cs
  32. 6
      src/Avalonia.X11/X11Window.cs
  33. 21
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  34. 61
      src/Windows/Avalonia.Win32/EmbeddedWindowImpl.cs
  35. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  36. 58
      src/Windows/Avalonia.Win32/OffscreenParentWindow.cs
  37. 40
      src/Windows/Avalonia.Win32/PopupImpl.cs
  38. 201
      src/Windows/Avalonia.Win32/Win32NativeControlHost.cs
  39. 25
      src/Windows/Avalonia.Win32/WindowImpl.cs

27
Avalonia.sln

@ -203,6 +203,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
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}"
EndProject
Global
@ -1920,6 +1922,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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1977,6 +2003,7 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

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

@ -24,6 +24,8 @@ struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnAppMenu;
struct IAvnAppMenuItem;
struct IAvnNativeControlHost;
struct IAvnNativeControlHostTopLevelAttachment;
struct AvnSize
{
@ -215,6 +217,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;
};
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@ -250,6 +253,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;
};
@ -396,4 +400,22 @@ AVNCOM(IAvnAppMenuItem, 19) : IUnknown
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
};
AVNCOM(IAvnNativeControlHost, 20) : IUnknown
{
virtual HRESULT CreateDefaultChild(void* parent, void** retOut) = 0;
virtual IAvnNativeControlHostTopLevelAttachment* CreateAttachment() = 0;
virtual void DestroyDefaultChild(void* child) = 0;
};
AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
{
virtual void* GetParentHandle() = 0;
virtual HRESULT InitializeWithChildHandle(void* child) = 0;
virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
virtual void MoveTo(float x, float y, float width, float height) = 0;
virtual void Hide() = 0;
virtual void ReleaseChild() = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

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

@ -10,6 +10,8 @@
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AFD334023E03C4F0042899B /* controlhost.mm */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
@ -31,6 +33,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; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = "<group>"; };
@ -84,11 +88,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 */,
@ -188,6 +194,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 */,
@ -196,6 +203,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,

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

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

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

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

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

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

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

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

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

@ -393,6 +393,14 @@ public:
*ppv = [renderTarget createSurfaceRenderTarget];
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;
}
protected:
virtual NSWindowStyleMask GetStyle()
@ -787,9 +795,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height});
}
- (void)updateLayer
{
AvnInsidePotentialDeadlock deadlock;
if (_parent == nullptr)
{
return;
@ -852,7 +860,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
if([self ignoreUserInput])
return;
[self becomeFirstResponder];
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
@ -879,11 +886,27 @@ 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];
_parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
[super mouseMoved:event];
}
- (BOOL) resignFirstResponder
{
_parent->BaseEvents->LostFocus();
return YES;
}
- (void)mouseMoved:(NSEvent *)event
{
[self mouseEvent:event withType:Move];

8
samples/interop/NativeEmbedSample/App.xaml

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

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

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

121
samples/interop/NativeEmbedSample/EmbedSample.cs

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

58
samples/interop/NativeEmbedSample/GtkHelper.cs

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

39
samples/interop/NativeEmbedSample/MacHelper.cs

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

43
samples/interop/NativeEmbedSample/MainWindow.xaml

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

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

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

29
samples/interop/NativeEmbedSample/NativeEmbedSample.csproj

@ -0,0 +1,29 @@
<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.X11\Avalonia.X11.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2019013001" />
<AvaloniaResource Include="**\*.xaml">
<SubType>Designer</SubType>
</AvaloniaResource>
<None Remove="nodes.mp4" />
<Content Include="nodes.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="..\..\..\src\Avalonia.X11\NativeDialogs\Gtk.cs" />
</ItemGroup>
<Import Project="..\..\..\build\SampleApp.props" />
<Import Project="..\..\..\build\BuildTargets.targets" />
<Import Project="..\..\..\build\ReferenceCoreLibraries.props" />
</Project>

17
samples/interop/NativeEmbedSample/Program.cs

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

74
samples/interop/NativeEmbedSample/WinApi.cs

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

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

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

BIN
samples/interop/NativeEmbedSample/nodes.mp4

Binary file not shown.

141
src/Avalonia.Controls/NativeControlHost.cs

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

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

@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
@ -64,6 +65,9 @@ namespace Avalonia.Controls.Platform
window.Deactivated += WindowDeactivated;
}
if (_root is TopLevel tl && tl.PlatformImpl is IEmbeddableWindowImpl eimpl)
eimpl.LostFocus += TopLevelLostPlatformFocus;
_inputManagerSubscription = InputManager?.Process.Subscribe(RawInput);
}
@ -93,6 +97,9 @@ namespace Avalonia.Controls.Platform
{
root.Deactivated -= WindowDeactivated;
}
if (_root is TopLevel tl && tl.PlatformImpl is IEmbeddableWindowImpl eimpl)
eimpl.LostFocus -= TopLevelLostPlatformFocus;
_inputManagerSubscription?.Dispose();
@ -401,6 +408,11 @@ namespace Avalonia.Controls.Platform
{
Menu?.Close();
}
private void TopLevelLostPlatformFocus()
{
Menu?.Close();
}
protected void Click(IMenuItem item)
{

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

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

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

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reactive.Disposables;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Data;
using Avalonia.Input;
@ -13,6 +14,7 @@ using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
@ -249,6 +251,8 @@ namespace Avalonia.Controls.Primitives
if (window != null)
{
window.Deactivated += WindowDeactivated;
if (window.PlatformImpl is IEmbeddableWindowImpl reportsFocus)
reportsFocus.LostFocus += WindowLostFocus;
}
else
{
@ -300,7 +304,11 @@ namespace Avalonia.Controls.Primitives
_topLevel.RemoveHandler(PointerPressedEvent, PointerPressedOutside);
var window = _topLevel as Window;
if (window != null)
{
window.Deactivated -= WindowDeactivated;
if (window.PlatformImpl is IEmbeddableWindowImpl reportsFocus)
reportsFocus.LostFocus -= WindowLostFocus;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
@ -483,6 +491,12 @@ namespace Avalonia.Controls.Primitives
}
}
private void WindowLostFocus()
{
if(!StaysOpen)
Close();
}
private IgnoreIsOpenScope BeginIgnoringIsOpen()
{
return new IgnoreIsOpenScope(this);

15
src/Avalonia.Controls/TopLevel.cs

@ -130,6 +130,9 @@ namespace Avalonia.Controls
nameof(IResourceProvider.ResourcesChanged),
this);
}
if (impl is IEmbeddableWindowImpl embeddableWindowImpl)
embeddableWindowImpl.LostFocus += PlatformImpl_LostFocus;
}
/// <summary>
@ -365,5 +368,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, InputModifiers.None);
}
}
}

136
src/Avalonia.Native/NativeControlHostImpl.cs

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

18
src/Avalonia.Native/WindowImplBase.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
@ -45,7 +46,8 @@ namespace Avalonia.Native
}
public abstract class WindowBaseImpl : IWindowBaseImpl,
IFramebufferPlatformSurface
IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost,
IEmbeddableWindowImpl
{
IInputRoot _inputRoot;
IAvnWindowBase _native;
@ -59,6 +61,7 @@ namespace Avalonia.Native
private Size _lastRenderedLogicalSize;
private double _savedScaling;
private GlPlatformSurface _glSurface;
private NativeControlHostImpl _nativeControlHost;
internal WindowBaseImpl(AvaloniaNativePlatformOptions opts, GlPlatformFeature glFeature)
{
@ -80,6 +83,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 +105,8 @@ namespace Avalonia.Native
this
};
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
public ILockedFramebuffer Lock()
{
var w = _savedLogicalSize.Width * _savedScaling;
@ -119,6 +125,8 @@ namespace Avalonia.Native
}, (int)w, (int)h, new Vector(dpi, dpi));
}
public event Action LostFocus;
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action Closed { get; set; }
@ -201,6 +209,11 @@ namespace Avalonia.Native
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
}
void IAvnWindowBaseEvents.LostFocus()
{
_parent.LostFocus?.Invoke();
}
}
public void Activate()
@ -264,6 +277,9 @@ namespace Avalonia.Native
_native?.Dispose();
_native = null;
_nativeControlHost?.Dispose();
_nativeControlHost = null;
(Screen as ScreenImpl)?.Dispose();
}

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

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

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

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

190
src/Avalonia.X11/X11NativeControlHost.cs

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

3
src/Avalonia.X11/X11Platform.cs

@ -26,12 +26,15 @@ 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 void Initialize(X11PlatformOptions options)
{
Options = options;
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();

6
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;
@ -179,6 +181,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
@ -1055,5 +1058,6 @@ namespace Avalonia.X11
public IPopupPositioner PopupPositioner { get; }
public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
public INativeControlHostImpl NativeControlHost { get; }
}
}

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

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

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

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

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

@ -18,7 +18,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);
@ -1003,6 +1003,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);
@ -1053,6 +1057,8 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent);
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommand nCmdShow);
[DllImport("kernel32.dll", SetLastError = true)]

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

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

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

@ -10,11 +10,31 @@ namespace Avalonia.Win32
{
class PopupImpl : WindowImpl, IPopupImpl
{
// 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;
protected override IntPtr CreateWindowOverride(ushort atom)
{
UnmanagedMethods.WindowStyles style =
@ -34,10 +54,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;
var classes = (int)UnmanagedMethods.GetClassLongPtr(result, (int)UnmanagedMethods.ClassLongIndex.GCL_STYLE);
@ -59,7 +80,22 @@ namespace Avalonia.Win32
}
}
public PopupImpl(IWindowBaseImpl parent)
// This is needed because we are calling virtual methods from constructors
// One fabulous design decision leads to another, I guess
static IWindowBaseImpl SaveParentHandle(IWindowBaseImpl parent)
{
s_parentHandle = parent.Handle.Handle;
return parent;
}
// This is needed because we are calling virtual methods from constructors
// One fabulous design decision leads to another, I guess
public PopupImpl(IWindowBaseImpl parent) : this(SaveParentHandle(parent), false)
{
}
private PopupImpl(IWindowBaseImpl parent, bool dummy) : base()
{
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}

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

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

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

@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL;
@ -18,13 +19,16 @@ using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia.Win32
{
public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
public class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithNativeControlHost,
IEmbeddableWindowImpl
{
private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
private static readonly IntPtr DefaultCursor = UnmanagedMethods.LoadCursor(
IntPtr.Zero, new IntPtr((int)UnmanagedMethods.Cursor.IDC_ARROW));
private Win32NativeControlHost _nativeControlHost;
private UnmanagedMethods.WndProc _wndProcDelegate;
private string _className;
private IntPtr _hwnd;
@ -71,6 +75,7 @@ namespace Avalonia.Win32
Win32GlManager.EglFeature.DeferredContext, this);
s_instances.Add(this);
_nativeControlHost = new Win32NativeControlHost(this);
}
public Action Activated { get; set; }
@ -93,6 +98,8 @@ namespace Avalonia.Win32
public Action<WindowState> WindowStateChanged { get; set; }
public event Action LostFocus;
public Thickness BorderThickness
{
get
@ -414,7 +421,7 @@ namespace Avalonia.Win32
0,
atom,
null,
(int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
(int)(WindowStyles.WS_OVERLAPPEDWINDOW | WindowStyles.WS_CLIPCHILDREN),
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
UnmanagedMethods.CW_USEDEFAULT,
@ -437,12 +444,11 @@ namespace Avalonia.Win32
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
const double wheelDelta = 120.0;
uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime());
RawInputEventArgs e = null;
var shouldTakeFocus = false;
switch ((UnmanagedMethods.WindowsMessage)msg)
{
@ -545,6 +551,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN:
shouldTakeFocus = ShouldTakeFocusOnClick;
if(ShouldIgnoreTouchEmulatedMessage())
break;
e = new RawPointerEventArgs(
@ -632,6 +639,7 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN:
shouldTakeFocus = ShouldTakeFocusOnClick;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
@ -751,6 +759,10 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE:
(Screen as ScreenImpl)?.InvalidateScreensCache();
return IntPtr.Zero;
case UnmanagedMethods.WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
}
#if USE_MANAGED_DRAG
@ -758,6 +770,8 @@ namespace Avalonia.Win32
if (_managedDrag.PreprocessInputEvent(ref e))
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
#endif
if (shouldTakeFocus)
SetFocus(_hwnd);
if (e != null && Input != null)
{
@ -1061,5 +1075,8 @@ namespace Avalonia.Win32
}
}
IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => Handle.Handle;
public INativeControlHostImpl NativeControlHost => _nativeControlHost;
protected virtual bool ShouldTakeFocusOnClick => true;
}
}

Loading…
Cancel
Save