11 changed files with 318 additions and 10 deletions
@ -0,0 +1,44 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
|
|||
using Avalonia; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Web; |
|||
|
|||
using ControlCatalog.Pages; |
|||
|
|||
namespace ControlCatalog.Web; |
|||
|
|||
public class EmbedSampleWeb : INativeDemoControl |
|||
{ |
|||
public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault) |
|||
{ |
|||
if (isSecond) |
|||
{ |
|||
var iframe = EmbedInterop.CreateElement("iframe"); |
|||
iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70"); |
|||
|
|||
return new JSObjectControlHandle(iframe); |
|||
} |
|||
else |
|||
{ |
|||
var defaultHandle = (JSObjectControlHandle)createDefault(); |
|||
|
|||
_ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ => |
|||
{ |
|||
EmbedInterop.AddAppButton(defaultHandle.Object); |
|||
}); |
|||
|
|||
return defaultHandle; |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal static partial class EmbedInterop |
|||
{ |
|||
[JSImport("globalThis.document.createElement")] |
|||
public static partial JSObject CreateElement(string tagName); |
|||
|
|||
[JSImport("addAppButton", "embed.js")] |
|||
public static partial void AddAppButton(JSObject parentObject); |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
export function addAppButton(parent) { |
|||
var button = globalThis.document.createElement('button'); |
|||
button.innerText = 'Hello world'; |
|||
var clickCount = 0; |
|||
button.onclick = () => { |
|||
clickCount++; |
|||
button.innerText = 'Click count ' + clickCount; |
|||
}; |
|||
parent.appendChild(button); |
|||
return button; |
|||
} |
|||
@ -0,0 +1,136 @@ |
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
|
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Web.Interop; |
|||
|
|||
namespace Avalonia.Web |
|||
{ |
|||
internal class BrowserNativeControlHost : INativeControlHostImpl |
|||
{ |
|||
private readonly JSObject _hostElement; |
|||
|
|||
public BrowserNativeControlHost(JSObject element) |
|||
{ |
|||
_hostElement = element; |
|||
} |
|||
|
|||
public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent) |
|||
{ |
|||
var element = NativeControlHostHelper.CreateDefaultChild(null); |
|||
return new JSObjectControlHandle(element); |
|||
} |
|||
|
|||
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create) |
|||
{ |
|||
Attachment? a = null; |
|||
try |
|||
{ |
|||
var child = create(new JSObjectControlHandle(_hostElement)); |
|||
var attachmenetReference = NativeControlHostHelper.CreateAttachment(); |
|||
// It has to be assigned to the variable before property setter is called so we dispose it on exception
|
|||
#pragma warning disable IDE0017 // Simplify object initialization
|
|||
a = new Attachment(attachmenetReference, child); |
|||
#pragma warning restore IDE0017 // Simplify object initialization
|
|||
a.AttachedTo = this; |
|||
return a; |
|||
} |
|||
catch |
|||
{ |
|||
a?.Dispose(); |
|||
throw; |
|||
} |
|||
} |
|||
|
|||
public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle) |
|||
{ |
|||
var attachmenetReference = NativeControlHostHelper.CreateAttachment(); |
|||
var a = new Attachment(attachmenetReference, handle); |
|||
a.AttachedTo = this; |
|||
return a; |
|||
} |
|||
|
|||
public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle; |
|||
|
|||
private class Attachment : INativeControlHostControlTopLevelAttachment |
|||
{ |
|||
private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle"; |
|||
private const string AttachToSymbol = "AttachTo"; |
|||
private const string ShowInBoundsSymbol = "ShowInBounds"; |
|||
private const string HideWithSizeSymbol = "HideWithSize"; |
|||
private const string ReleaseChildSymbol = "ReleaseChild"; |
|||
|
|||
private JSObject? _native; |
|||
private BrowserNativeControlHost? _attachedTo; |
|||
|
|||
public Attachment(JSObject native, IPlatformHandle handle) |
|||
{ |
|||
_native = native; |
|||
NativeControlHostHelper.InitializeWithChildHandle(_native, ((JSObjectControlHandle)handle).Object); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_native != null) |
|||
{ |
|||
NativeControlHostHelper.ReleaseChild(_native); |
|||
_native.Dispose(); |
|||
_native = null; |
|||
} |
|||
} |
|||
|
|||
public INativeControlHostImpl? AttachedTo |
|||
{ |
|||
get => _attachedTo!; |
|||
set |
|||
{ |
|||
CheckDisposed(); |
|||
|
|||
var host = (BrowserNativeControlHost?)value; |
|||
if (host == null) |
|||
{ |
|||
NativeControlHostHelper.AttachTo(_native, null); |
|||
} |
|||
else |
|||
{ |
|||
NativeControlHostHelper.AttachTo(_native, host._hostElement); |
|||
} |
|||
_attachedTo = host; |
|||
} |
|||
} |
|||
|
|||
public bool IsCompatibleWith(INativeControlHostImpl host) => host is BrowserNativeControlHost; |
|||
|
|||
public void HideWithSize(Size size) |
|||
{ |
|||
CheckDisposed(); |
|||
if (_attachedTo == null) |
|||
return; |
|||
|
|||
NativeControlHostHelper.HideWithSize(_native, Math.Max(1, size.Width), Math.Max(1, size.Height)); |
|||
} |
|||
|
|||
public void ShowInBounds(Rect bounds) |
|||
{ |
|||
CheckDisposed(); |
|||
|
|||
if (_attachedTo == null) |
|||
throw new InvalidOperationException("Native control isn't attached to a toplevel"); |
|||
|
|||
bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), |
|||
Math.Max(1, bounds.Height)); |
|||
|
|||
NativeControlHostHelper.ShowInBounds(_native, bounds.X, bounds.Y, bounds.Width, bounds.Height); |
|||
} |
|||
|
|||
[MemberNotNull(nameof(_native))] |
|||
private void CheckDisposed() |
|||
{ |
|||
if (_native == null) |
|||
throw new ObjectDisposedException(nameof(Attachment)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
|
|||
namespace Avalonia.Web.Interop; |
|||
|
|||
internal static partial class NativeControlHostHelper |
|||
{ |
|||
[JSImport("NativeControlHost.createDefaultChild", "avalonia.ts")] |
|||
internal static partial JSObject CreateDefaultChild(JSObject? parent); |
|||
|
|||
[JSImport("NativeControlHost.createAttachment", "avalonia.ts")] |
|||
internal static partial JSObject CreateAttachment(); |
|||
|
|||
[JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")] |
|||
internal static partial void InitializeWithChildHandle(JSObject element, JSObject child); |
|||
|
|||
[JSImport("NativeControlHost.attachTo", "avalonia.ts")] |
|||
internal static partial void AttachTo(JSObject element, JSObject? host); |
|||
|
|||
[JSImport("NativeControlHost.showInBounds", "avalonia.ts")] |
|||
internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height); |
|||
|
|||
[JSImport("NativeControlHost.hideWithSize", "avalonia.ts")] |
|||
internal static partial void HideWithSize(JSObject element, double width, double height); |
|||
|
|||
[JSImport("NativeControlHost.releaseChild", "avalonia.ts")] |
|||
internal static partial void ReleaseChild(JSObject element); |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
|
|||
using Avalonia.Controls.Platform; |
|||
|
|||
namespace Avalonia.Web; |
|||
|
|||
public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle |
|||
{ |
|||
internal const string ElementReferenceDescriptor = "JSObject"; |
|||
|
|||
public JSObjectControlHandle(JSObject reference) |
|||
{ |
|||
Object = reference; |
|||
} |
|||
|
|||
public JSObject Object { get; } |
|||
|
|||
public IntPtr Handle => throw new NotSupportedException(); |
|||
|
|||
public string? HandleDescriptor => ElementReferenceDescriptor; |
|||
|
|||
public void Destroy() |
|||
{ |
|||
if (Object is JSObject inProcess && !inProcess.IsDisposed) |
|||
{ |
|||
inProcess.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,19 +1,20 @@ |
|||
import { RuntimeAPI } from "../types/dotnet"; |
|||
import { SizeWatcher, DpiWatcher, Canvas } from "./avalonia/canvas"; |
|||
|
|||
import { InputHelper } from "./avalonia/input"; |
|||
import { AvaloniaDOM } from "./avalonia/dom"; |
|||
import { Caniuse } from "./avalonia/caniuse"; |
|||
import { StreamHelper } from "./avalonia/stream"; |
|||
import { NativeControlHost } from "./avalonia/NativeControlHost"; |
|||
|
|||
export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> { |
|||
api.setModuleImports("avalonia.ts", { |
|||
Caniuse, |
|||
Canvas, |
|||
InputHelper, |
|||
SizeWatcher, |
|||
DpiWatcher, |
|||
AvaloniaDOM, |
|||
Caniuse, |
|||
StreamHelper |
|||
StreamHelper, |
|||
NativeControlHost |
|||
}); |
|||
} |
|||
|
|||
@ -0,0 +1,55 @@ |
|||
class NativeControlHostTopLevelAttachment { |
|||
_child?: HTMLElement; |
|||
_host?: HTMLElement; |
|||
} |
|||
|
|||
export class NativeControlHost { |
|||
public static createDefaultChild(parent?: HTMLElement): HTMLElement { |
|||
return document.createElement("div"); |
|||
} |
|||
|
|||
public static createAttachment(): NativeControlHostTopLevelAttachment { |
|||
return new NativeControlHostTopLevelAttachment(); |
|||
} |
|||
|
|||
public static initializeWithChildHandle(element: NativeControlHostTopLevelAttachment, child: HTMLElement): void { |
|||
element._child = child; |
|||
element._child.style.position = "absolute"; |
|||
} |
|||
|
|||
public static attachTo(element: NativeControlHostTopLevelAttachment, host?: HTMLElement): void { |
|||
if (element._host && element._child) { |
|||
element._host.removeChild(element._child); |
|||
} |
|||
|
|||
element._host = host; |
|||
|
|||
if (element._host && element._child) { |
|||
element._host.appendChild(element._child); |
|||
} |
|||
} |
|||
|
|||
public static showInBounds(element: NativeControlHostTopLevelAttachment, x: number, y: number, width: number, height: number): void { |
|||
if (element._child) { |
|||
element._child.style.top = `${y}px`; |
|||
element._child.style.left = `${x}px`; |
|||
element._child.style.width = `${width}px`; |
|||
element._child.style.height = `${height}px`; |
|||
element._child.style.display = "block"; |
|||
} |
|||
} |
|||
|
|||
public static hideWithSize(element: NativeControlHostTopLevelAttachment, width: number, height: number): void { |
|||
if (element._child) { |
|||
element._child.style.width = `${width}px`; |
|||
element._child.style.height = `${height}px`; |
|||
element._child.style.display = "none"; |
|||
} |
|||
} |
|||
|
|||
public static releaseChild(element: NativeControlHostTopLevelAttachment): void { |
|||
if (element._child) { |
|||
element._child = undefined; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue