committed by
GitHub
71 changed files with 1731 additions and 176 deletions
@ -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); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
#include "common.h" |
||||
|
|
||||
|
static int Counter = 0; |
||||
|
AvnInsidePotentialDeadlock::AvnInsidePotentialDeadlock() |
||||
|
{ |
||||
|
Counter++; |
||||
|
} |
||||
|
|
||||
|
AvnInsidePotentialDeadlock::~AvnInsidePotentialDeadlock() |
||||
|
{ |
||||
|
Counter--; |
||||
|
} |
||||
|
|
||||
|
bool AvnInsidePotentialDeadlock::IsInside() |
||||
|
{ |
||||
|
return Counter!=0; |
||||
|
} |
||||
@ -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> |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -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> |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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(); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
nodes.mp4 by beeple is licensed under the creative commons license, downloaded from https://vimeo.com/9936271 |
||||
Binary file not shown.
@ -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(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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; |
|
||||
} |
|
||||
} |
|
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue