diff --git a/Avalonia.sln b/Avalonia.sln
index c8e513f94c..0adc29ffe8 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -219,6 +219,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.SourceGenerator",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevAnalyzers", "src\tools\DevAnalyzers\DevAnalyzers.csproj", "{2B390431-288C-435C-BB6B-A374033BD8D1}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample.Desktop", "samples\interop\NativeEmbedSample.Desktop\NativeEmbedSample.Desktop.csproj", "{F2389463-DDB4-4317-B894-D4DF9FF6B763}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -1989,6 +1991,30 @@ Global
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|Any CPU.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|iPhone.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|iPhone.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|iPhone.Build.0 = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -2048,6 +2074,7 @@ Global
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CE910927-CE5A-456F-BC92-E4C757354A5C} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+ {F2389463-DDB4-4317-B894-D4DF9FF6B763} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
diff --git a/samples/interop/NativeEmbedSample.Desktop/NativeEmbedSample.Desktop.csproj b/samples/interop/NativeEmbedSample.Desktop/NativeEmbedSample.Desktop.csproj
new file mode 100644
index 0000000000..1ec852ab6d
--- /dev/null
+++ b/samples/interop/NativeEmbedSample.Desktop/NativeEmbedSample.Desktop.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net6.0
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/interop/NativeEmbedSample.Desktop/Program.cs b/samples/interop/NativeEmbedSample.Desktop/Program.cs
new file mode 100644
index 0000000000..01684d0301
--- /dev/null
+++ b/samples/interop/NativeEmbedSample.Desktop/Program.cs
@@ -0,0 +1,17 @@
+using Avalonia;
+using NativeEmbedSample;
+
+namespace NativeEmbedSample.Desktop;
+
+public class Program
+{
+ static int Main(string[] args) => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
+
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .With(new AvaloniaNativePlatformOptions()
+ {
+ })
+ .UsePlatformDetect();
+
+}
diff --git a/samples/interop/NativeEmbedSample/Android/EmbedSample.Android.cs b/samples/interop/NativeEmbedSample/Android/EmbedSample.Android.cs
new file mode 100644
index 0000000000..ed3b9aeeb0
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/Android/EmbedSample.Android.cs
@@ -0,0 +1,50 @@
+#if __ANDROID__ || ANDROID
+using System;
+using System.IO;
+using System.Diagnostics;
+using Android.Views;
+using Android.Webkit;
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+
+namespace NativeEmbedSample;
+
+public partial class EmbedSample
+{
+ private IPlatformHandle CreateAndroid(IPlatformHandle parent)
+ {
+ var button = new Android.Widget.Button(Android.App.Application.Context) { Text = "Android button" };
+
+ return new AndroidViewHandle(button);
+ }
+
+ private void DestroyAndroid(IPlatformHandle control)
+ {
+ base.DestroyNativeControlCore(control);
+ }
+}
+
+internal sealed class AndroidViewHandle : INativeControlHostDestroyableControlHandle
+{
+ private View _view;
+
+ public AndroidViewHandle(View view)
+ {
+ _view = view;
+ }
+
+ public IntPtr Handle => _view?.Handle ?? IntPtr.Zero;
+ public string HandleDescriptor => "JavaHandle";
+
+ public void Destroy()
+ {
+ _view?.Dispose();
+ _view = null;
+ }
+
+ ~AndroidViewHandle()
+ {
+ Destroy();
+ }
+}
+#endif
diff --git a/samples/interop/NativeEmbedSample/App.xaml b/samples/interop/NativeEmbedSample/App.axaml
similarity index 57%
rename from samples/interop/NativeEmbedSample/App.xaml
rename to samples/interop/NativeEmbedSample/App.axaml
index e35ade4087..d6f182ed3f 100644
--- a/samples/interop/NativeEmbedSample/App.xaml
+++ b/samples/interop/NativeEmbedSample/App.axaml
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="NativeEmbedSample.App">
-
-
+
diff --git a/samples/interop/NativeEmbedSample/App.axaml.cs b/samples/interop/NativeEmbedSample/App.axaml.cs
new file mode 100644
index 0000000000..0a89ea441b
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/App.axaml.cs
@@ -0,0 +1,23 @@
+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();
+ else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
+ singleViewLifetime.MainView = new MainView();
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
diff --git a/samples/interop/NativeEmbedSample/App.xaml.cs b/samples/interop/NativeEmbedSample/App.xaml.cs
deleted file mode 100644
index cb17cfc35d..0000000000
--- a/samples/interop/NativeEmbedSample/App.xaml.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-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();
- }
- }
-}
diff --git a/samples/interop/NativeEmbedSample/EmbedSample.cs b/samples/interop/NativeEmbedSample/EmbedSample.cs
index ab9df11e19..340068058f 100644
--- a/samples/interop/NativeEmbedSample/EmbedSample.cs
+++ b/samples/interop/NativeEmbedSample/EmbedSample.cs
@@ -6,116 +6,49 @@ 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 partial 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 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("", 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))
+#if DESKTOP
+ if (OperatingSystem.IsLinux())
return CreateLinux(parent);
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ if (OperatingSystem.IsWindows())
return CreateWin32(parent);
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ if (OperatingSystem.IsMacOS())
return CreateOSX(parent);
+#elif __ANDROID__ || ANDROID
+ if (OperatingSystem.IsAndroid())
+ return CreateAndroid(parent);
+#elif IOS
+ if (OperatingSystem.IsIOS())
+ return CreateIOS(parent);
+#endif
return base.CreateNativeControlCore(parent);
}
protected override void DestroyNativeControlCore(IPlatformHandle control)
{
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+#if DESKTOP
+ if (OperatingSystem.IsLinux())
DestroyLinux(control);
- else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ else if (OperatingSystem.IsWindows())
DestroyWin32(control);
- else if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ else if (OperatingSystem.IsMacOS())
DestroyOSX(control);
- else
- base.DestroyNativeControlCore(control);
+#elif __ANDROID__ || ANDROID
+ if (OperatingSystem.IsAndroid())
+ DestroyAndroid(control);
+#elif IOS
+ if (OperatingSystem.IsIOS())
+ DestroyIOS(control);
+#endif
+ else base.DestroyNativeControlCore(control);
}
}
}
diff --git a/samples/interop/NativeEmbedSample/Gtk/EmbedSample.Gtk.cs b/samples/interop/NativeEmbedSample/Gtk/EmbedSample.Gtk.cs
new file mode 100644
index 0000000000..24c9deca36
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/Gtk/EmbedSample.Gtk.cs
@@ -0,0 +1,41 @@
+#if DESKTOP
+using System.IO;
+using System.Diagnostics;
+using Avalonia.Platform;
+
+namespace NativeEmbedSample;
+
+public partial class EmbedSample
+{
+ 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);
+ }
+}
+#endif
diff --git a/samples/interop/NativeEmbedSample/Gtk/GtkHelper.cs b/samples/interop/NativeEmbedSample/Gtk/GtkHelper.cs
new file mode 100644
index 0000000000..567bc25acb
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/Gtk/GtkHelper.cs
@@ -0,0 +1,62 @@
+#if DESKTOP
+
+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;
+
+internal class GtkHelper
+{
+ private static Task 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;
+ }
+}
+#endif
diff --git a/samples/interop/NativeEmbedSample/nodes-license.md b/samples/interop/NativeEmbedSample/Gtk/nodes-license.md
similarity index 100%
rename from samples/interop/NativeEmbedSample/nodes-license.md
rename to samples/interop/NativeEmbedSample/Gtk/nodes-license.md
diff --git a/samples/interop/NativeEmbedSample/nodes.mp4 b/samples/interop/NativeEmbedSample/Gtk/nodes.mp4
similarity index 100%
rename from samples/interop/NativeEmbedSample/nodes.mp4
rename to samples/interop/NativeEmbedSample/Gtk/nodes.mp4
diff --git a/samples/interop/NativeEmbedSample/GtkHelper.cs b/samples/interop/NativeEmbedSample/GtkHelper.cs
deleted file mode 100644
index e389a51ef5..0000000000
--- a/samples/interop/NativeEmbedSample/GtkHelper.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-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 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;
- }
- }
-}
diff --git a/samples/interop/NativeEmbedSample/Mac/EmbedSample.Mac.cs b/samples/interop/NativeEmbedSample/Mac/EmbedSample.Mac.cs
new file mode 100644
index 0000000000..911a874c27
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/Mac/EmbedSample.Mac.cs
@@ -0,0 +1,32 @@
+#if DESKTOP
+using Avalonia.Platform;
+using Avalonia.Threading;
+using MonoMac.Foundation;
+using MonoMac.WebKit;
+
+namespace NativeEmbedSample;
+
+public partial class EmbedSample
+{
+ 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();
+ }
+}
+#endif
diff --git a/samples/interop/NativeEmbedSample/Mac/MacHelper.cs b/samples/interop/NativeEmbedSample/Mac/MacHelper.cs
new file mode 100644
index 0000000000..d72ef5479c
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/Mac/MacHelper.cs
@@ -0,0 +1,39 @@
+#if DESKTOP
+using System;
+using Avalonia.Platform;
+using MonoMac.AppKit;
+
+namespace NativeEmbedSample;
+
+internal class MacHelper
+{
+ private static bool _isInitialized;
+
+ public static void EnsureInitialized()
+ {
+ if (_isInitialized)
+ return;
+ _isInitialized = true;
+ NSApplication.Init();
+ }
+}
+
+internal 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;
+ }
+}
+#endif
diff --git a/samples/interop/NativeEmbedSample/MacHelper.cs b/samples/interop/NativeEmbedSample/MacHelper.cs
deleted file mode 100644
index 74a06a0a0c..0000000000
--- a/samples/interop/NativeEmbedSample/MacHelper.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-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;
- }
- }
-
-}
diff --git a/samples/interop/NativeEmbedSample/MainView.axaml b/samples/interop/NativeEmbedSample/MainView.axaml
new file mode 100644
index 0000000000..4bfb14b2f9
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/MainView.axaml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+ Text
+
+
+ Tooltip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/interop/NativeEmbedSample/MainView.axaml.cs b/samples/interop/NativeEmbedSample/MainView.axaml.cs
new file mode 100644
index 0000000000..976de7a97c
--- /dev/null
+++ b/samples/interop/NativeEmbedSample/MainView.axaml.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+
+namespace NativeEmbedSample;
+
+public class MainView : UserControl
+{
+ public MainView()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+
+ 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