committed by
GitHub
39 changed files with 1642 additions and 92 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,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> |
|||
@ -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(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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