From c175ef318ea68a5a9e84ea7ff6140119f66627c0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 10 May 2022 00:26:27 -0400 Subject: [PATCH] Restructure native embed sample project --- Avalonia.sln | 27 ++++ .../NativeEmbedSample.Desktop.csproj | 15 +++ .../NativeEmbedSample.Desktop/Program.cs | 17 +++ .../Android/EmbedSample.Android.cs | 50 ++++++++ .../NativeEmbedSample/{App.xaml => App.axaml} | 3 +- .../interop/NativeEmbedSample/App.axaml.cs | 23 ++++ samples/interop/NativeEmbedSample/App.xaml.cs | 22 ---- .../interop/NativeEmbedSample/EmbedSample.cs | 115 ++++-------------- .../NativeEmbedSample/Gtk/EmbedSample.Gtk.cs | 41 +++++++ .../NativeEmbedSample/Gtk/GtkHelper.cs | 62 ++++++++++ .../{ => Gtk}/nodes-license.md | 0 .../NativeEmbedSample/{ => Gtk}/nodes.mp4 | Bin .../interop/NativeEmbedSample/GtkHelper.cs | 58 --------- .../NativeEmbedSample/Mac/EmbedSample.Mac.cs | 32 +++++ .../NativeEmbedSample/Mac/MacHelper.cs | 39 ++++++ .../interop/NativeEmbedSample/MacHelper.cs | 39 ------ .../interop/NativeEmbedSample/MainView.axaml | 64 ++++++++++ .../NativeEmbedSample/MainView.axaml.cs | 45 +++++++ .../NativeEmbedSample/MainWindow.axaml | 10 ++ .../NativeEmbedSample/MainWindow.axaml.cs | 17 +++ .../interop/NativeEmbedSample/MainWindow.xaml | 52 -------- .../NativeEmbedSample/MainWindow.xaml.cs | 36 ------ .../NativeEmbedSample.csproj | 24 ++-- samples/interop/NativeEmbedSample/Program.cs | 17 --- .../NativeEmbedSample/Win/EmbedSample.Win.cs | 37 ++++++ .../interop/NativeEmbedSample/Win/WinApi.cs | 75 ++++++++++++ samples/interop/NativeEmbedSample/WinApi.cs | 74 ----------- .../NativeEmbedSample/iOS/EmbedSample.iOS.cs | 65 ++++++++++ 28 files changed, 657 insertions(+), 402 deletions(-) create mode 100644 samples/interop/NativeEmbedSample.Desktop/NativeEmbedSample.Desktop.csproj create mode 100644 samples/interop/NativeEmbedSample.Desktop/Program.cs create mode 100644 samples/interop/NativeEmbedSample/Android/EmbedSample.Android.cs rename samples/interop/NativeEmbedSample/{App.xaml => App.axaml} (57%) create mode 100644 samples/interop/NativeEmbedSample/App.axaml.cs delete mode 100644 samples/interop/NativeEmbedSample/App.xaml.cs create mode 100644 samples/interop/NativeEmbedSample/Gtk/EmbedSample.Gtk.cs create mode 100644 samples/interop/NativeEmbedSample/Gtk/GtkHelper.cs rename samples/interop/NativeEmbedSample/{ => Gtk}/nodes-license.md (100%) rename samples/interop/NativeEmbedSample/{ => Gtk}/nodes.mp4 (100%) delete mode 100644 samples/interop/NativeEmbedSample/GtkHelper.cs create mode 100644 samples/interop/NativeEmbedSample/Mac/EmbedSample.Mac.cs create mode 100644 samples/interop/NativeEmbedSample/Mac/MacHelper.cs delete mode 100644 samples/interop/NativeEmbedSample/MacHelper.cs create mode 100644 samples/interop/NativeEmbedSample/MainView.axaml create mode 100644 samples/interop/NativeEmbedSample/MainView.axaml.cs create mode 100644 samples/interop/NativeEmbedSample/MainWindow.axaml create mode 100644 samples/interop/NativeEmbedSample/MainWindow.axaml.cs delete mode 100644 samples/interop/NativeEmbedSample/MainWindow.xaml delete mode 100644 samples/interop/NativeEmbedSample/MainWindow.xaml.cs delete mode 100644 samples/interop/NativeEmbedSample/Program.cs create mode 100644 samples/interop/NativeEmbedSample/Win/EmbedSample.Win.cs create mode 100644 samples/interop/NativeEmbedSample/Win/WinApi.cs delete mode 100644 samples/interop/NativeEmbedSample/WinApi.cs create mode 100644 samples/interop/NativeEmbedSample/iOS/EmbedSample.iOS.cs 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 + { + new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" } + } + }.Open((Control)sender); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == BoundsProperty) + { + var isMobile = change.GetNewValue().Width < 1200; + this.Find("FirstPanel")!.Classes.Set("mobile", isMobile); + this.Find("SecondPanel")!.Classes.Set("mobile", isMobile); + } + } +} diff --git a/samples/interop/NativeEmbedSample/MainWindow.axaml b/samples/interop/NativeEmbedSample/MainWindow.axaml new file mode 100644 index 0000000000..a615428778 --- /dev/null +++ b/samples/interop/NativeEmbedSample/MainWindow.axaml @@ -0,0 +1,10 @@ + + + + diff --git a/samples/interop/NativeEmbedSample/MainWindow.axaml.cs b/samples/interop/NativeEmbedSample/MainWindow.axaml.cs new file mode 100644 index 0000000000..a261dad5ed --- /dev/null +++ b/samples/interop/NativeEmbedSample/MainWindow.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace NativeEmbedSample; + +public class MainWindow : Window +{ + public MainWindow() + { + AvaloniaXamlLoader.Load(this); +#if DEBUG && DESKTOP + this.AttachDevTools(); +#endif + } +} + diff --git a/samples/interop/NativeEmbedSample/MainWindow.xaml b/samples/interop/NativeEmbedSample/MainWindow.xaml deleted file mode 100644 index f2161a1bea..0000000000 --- a/samples/interop/NativeEmbedSample/MainWindow.xaml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - Text - - - Tooltip - - - - - - - - - Visible - - - - - - - - Visible - - - - - - diff --git a/samples/interop/NativeEmbedSample/MainWindow.xaml.cs b/samples/interop/NativeEmbedSample/MainWindow.xaml.cs deleted file mode 100644 index 4324aa2762..0000000000 --- a/samples/interop/NativeEmbedSample/MainWindow.xaml.cs +++ /dev/null @@ -1,36 +0,0 @@ -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 - { - new MenuItem() { Header = "Test" }, new MenuItem() { Header = "Test" } - } - }.Open((Control)sender); - } - } -} diff --git a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj index c25442b52c..34206c2b63 100644 --- a/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj +++ b/samples/interop/NativeEmbedSample/NativeEmbedSample.csproj @@ -1,30 +1,32 @@  - Exe - netcoreapp2.0 - true + net6.0;net6.0-android;net6.0-ios true + + $(DefineConstants);DESKTOP + + + + - - - Designer - - + + + Gtk\Gtk.cs + + + PreserveNewest - - - diff --git a/samples/interop/NativeEmbedSample/Program.cs b/samples/interop/NativeEmbedSample/Program.cs deleted file mode 100644 index baa7837667..0000000000 --- a/samples/interop/NativeEmbedSample/Program.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Avalonia; - -namespace NativeEmbedSample -{ - 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/Win/EmbedSample.Win.cs b/samples/interop/NativeEmbedSample/Win/EmbedSample.Win.cs new file mode 100644 index 0000000000..2e14e7d766 --- /dev/null +++ b/samples/interop/NativeEmbedSample/Win/EmbedSample.Win.cs @@ -0,0 +1,37 @@ +#if DESKTOP +using System; +using System.Text; +using Avalonia.Platform; + +namespace NativeEmbedSample; + +public partial class EmbedSample +{ + 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); + } +} +#endif diff --git a/samples/interop/NativeEmbedSample/Win/WinApi.cs b/samples/interop/NativeEmbedSample/Win/WinApi.cs new file mode 100644 index 0000000000..5ec6e6bbeb --- /dev/null +++ b/samples/interop/NativeEmbedSample/Win/WinApi.cs @@ -0,0 +1,75 @@ +#if DESKTOP +using System; +using System.Runtime.InteropServices; + +namespace NativeEmbedSample; + +internal 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); +} +#endif diff --git a/samples/interop/NativeEmbedSample/WinApi.cs b/samples/interop/NativeEmbedSample/WinApi.cs deleted file mode 100644 index 8e5bcdf49e..0000000000 --- a/samples/interop/NativeEmbedSample/WinApi.cs +++ /dev/null @@ -1,74 +0,0 @@ -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); - } -} diff --git a/samples/interop/NativeEmbedSample/iOS/EmbedSample.iOS.cs b/samples/interop/NativeEmbedSample/iOS/EmbedSample.iOS.cs new file mode 100644 index 0000000000..185c2e6b9f --- /dev/null +++ b/samples/interop/NativeEmbedSample/iOS/EmbedSample.iOS.cs @@ -0,0 +1,65 @@ +#if IOS +using System; +using System.IO; +using System.Diagnostics; +using Avalonia.Controls.Platform; +using Avalonia.Platform; +using CoreGraphics; +using Foundation; +using UIKit; +using WebKit; + +namespace NativeEmbedSample; + +public partial class EmbedSample +{ + private IPlatformHandle CreateIOS(IPlatformHandle parent) + { + if (IsSecond) + { + var webView = new WKWebView(CGRect.Empty, new WKWebViewConfiguration()); + webView.LoadRequest(new NSUrlRequest(new NSUrl("https://www.apple.com/"))); + + return new UIViewHandle(webView); + } + else + { + var button = new UIButton(); + var clickCount = 0; + button.SetTitle("Hello world", UIControlState.Normal); + button.BackgroundColor = UIColor.Blue; + button.AddTarget((_, _) => + { + clickCount++; + button.SetTitle($"Click count {clickCount}", UIControlState.Normal); + }, UIControlEvent.TouchDown); + + return new UIViewHandle(button); + } + } + + private void DestroyIOS(IPlatformHandle control) + { + base.DestroyNativeControlCore(control); + } +} + +internal class UIViewHandle : INativeControlHostDestroyableControlHandle +{ + private UIView _view; + + public UIViewHandle(UIView view) + { + _view = view; + } + + public IntPtr Handle => _view?.Handle ?? IntPtr.Zero; + public string HandleDescriptor => "UIView"; + + public void Destroy() + { + _view?.Dispose(); + _view = null; + } +} +#endif