diff --git a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
index 80be694741..1d03118993 100644
--- a/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
+++ b/src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
@@ -12,6 +12,7 @@
+
diff --git a/src/Web/Avalonia.Web.Sample/Program.cs b/src/Web/Avalonia.Web.Sample/Program.cs
index 2d5bb6bec9..2a807b8cdf 100644
--- a/src/Web/Avalonia.Web.Sample/Program.cs
+++ b/src/Web/Avalonia.Web.Sample/Program.cs
@@ -4,7 +4,10 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
using System.Xml.Linq;
+using Avalonia;
using Avalonia.Web;
+using Avalonia.Web.Blazor;
+using ControlCatalog;
//using SkiaSharp;
internal partial class Program
@@ -15,24 +18,11 @@ internal partial class Program
private static void Main(string[] args)
{
- Console.WriteLine("Hello, Browser!");
-
- Console.WriteLine();
-
- foreach(var arg in args)
- {
- Console.WriteLine(arg);
- }
-
- var div = GetElementById("out");
- Console.WriteLine("got div");
-
- var canvas = AvaloniaRuntime.CreateCanvas(div);
-
- Console.WriteLine("Created canvas");
-
- AvaloniaRuntime.Foo(canvas);
+ BuildAvaloniaApp().UseBrowserWasm();
}
+
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure();
}
public partial class MyClass
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj
index 51b1d70173..13f311ba53 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.csproj
+++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj
@@ -14,6 +14,11 @@
+
+
+
+
+
true
diff --git a/src/Web/Avalonia.Web/AvaloniaRuntime.cs b/src/Web/Avalonia.Web/AvaloniaRuntime.cs
index e5c315af4c..2580f52666 100644
--- a/src/Web/Avalonia.Web/AvaloniaRuntime.cs
+++ b/src/Web/Avalonia.Web/AvaloniaRuntime.cs
@@ -1,11 +1,23 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
+using Avalonia.Controls;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Threading;
+using Avalonia.Web.Blazor;
namespace Avalonia.Web;
+
public partial class AvaloniaRuntime
{
+
+
+
public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth);
[DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)]
@@ -14,19 +26,26 @@ public partial class AvaloniaRuntime
[JSExport]
internal static void StartAvaloniaView(JSObject canvas)
{
- InterceptGLObject();
+
// setup, get gl context...
+
+ }
+
+ public static GLInfo InitialiseGL (JSObject canvas)
+ {
+ InterceptGLObject();
+
var info = InitGL(canvas, "testCanvas");
-
+
var glInfo = new GLInfo(
- info.GetPropertyAsInt32("context"),
- (uint)info.GetPropertyAsInt32("fboId"),
- info.GetPropertyAsInt32("stencil"),
- info.GetPropertyAsInt32("sample"),
+ info.GetPropertyAsInt32("context"),
+ (uint)info.GetPropertyAsInt32("fboId"),
+ info.GetPropertyAsInt32("stencil"),
+ info.GetPropertyAsInt32("sample"),
info.GetPropertyAsInt32("depth"));
- Console.WriteLine($"{glInfo.ContextId}, {glInfo.FboId}");
+ return glInfo;
}
[JSImport("Canvas.createCanvas", "avalonia.ts")]
@@ -36,7 +55,7 @@ public partial class AvaloniaRuntime
public static partial void Foo(JSObject canvas);
[JSImport("Canvas.initGL", "avalonia.ts")]
- internal static partial JSObject InitGL(JSObject canvas, string canvasId);
+ private static partial JSObject InitGL(JSObject canvas, string canvasId);
[JSImport("StorageProvider.isFileApiSupported", "storage.ts")]
public static partial bool IsFileApiSupported();
diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs
new file mode 100644
index 0000000000..ec462893df
--- /dev/null
+++ b/src/Web/Avalonia.Web/AvaloniaView.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Runtime.InteropServices.JavaScript;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform;
+using Avalonia.Input.TextInput;
+using Avalonia.Platform.Storage;
+using Avalonia.Rendering.Composition;
+using Avalonia.Web.Blazor;
+using SkiaSharp;
+using static Avalonia.Web.AvaloniaRuntime;
+
+namespace Avalonia.Web
+{
+ public partial class AvaloniaView : ITextInputMethodImpl
+ {
+ [JSImport("globalThis.document.getElementById")]
+ internal static partial JSObject GetElementById(string id);
+
+ private readonly RazorViewTopLevelImpl _topLevelImpl;
+ private EmbeddableControlRoot _topLevel;
+
+ // Interop
+ /*private SKHtmlCanvasInterop? _interop = null;
+ private SizeWatcherInterop? _sizeWatcher = null;
+ private DpiWatcherInterop? _dpiWatcher = null;*/
+ private GLInfo? _jsGlInfo = null;
+ /*private AvaloniaModule? _avaloniaModule = null;
+ private InputHelperInterop? _inputHelper = null;
+ private InputHelperInterop? _canvasHelper = null;
+ private InputHelperInterop? _containerHelper = null;
+ private NativeControlHostInterop? _nativeControlHost = null;
+ private StorageProviderInterop? _storageProvider = null;
+ private ElementReference _htmlCanvas;
+ private ElementReference _inputElement;
+ private ElementReference _containerElement;
+ private ElementReference _nativeControlsContainer;*/
+ private double _dpi = 1;
+ private SKSize _canvasSize = new(100, 100);
+
+ private GRContext? _context;
+ private GRGlInterface? _glInterface;
+ private const SKColorType ColorType = SKColorType.Rgba8888;
+
+ private bool _useGL;
+ private bool _inputElementFocused;
+
+ public AvaloniaView()
+ {
+ Console.WriteLine("In AvaloniaView");
+ var div = GetElementById("out");
+ Console.WriteLine("got div");
+
+ var canvas = AvaloniaRuntime.CreateCanvas(div);
+ canvas.SetProperty("id", "mycanvas");
+
+ AvaloniaRuntime.Foo(canvas);
+
+ _topLevelImpl = new RazorViewTopLevelImpl(this);
+
+ _topLevel = new EmbeddableControlRoot(_topLevelImpl);
+
+ Console.WriteLine("created toplevel");
+
+ _topLevel.Prepare();
+
+ Console.WriteLine("Prepped");
+
+ _topLevel.Renderer.Start();
+
+
+
+
+ //_interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame);
+
+
+ var skiaOptions = AvaloniaLocator.Current.GetService();
+ _useGL = skiaOptions?.CustomGpuFactory != null;
+
+ if (_useGL)
+ {
+ _jsGlInfo = AvaloniaRuntime.InitialiseGL(canvas);
+ Console.WriteLine("jsglinfo created - init gl");
+ }
+ else
+ {
+ throw new NotImplementedException();
+ //var rasterInitialized = _interop.InitRaster();
+ //Console.WriteLine("raster initialized: {0}", rasterInitialized);
+ }
+
+ if (_useGL)
+ {
+ // create the SkiaSharp context
+ if (_context == null)
+ {
+ Console.WriteLine("create glcontext");
+ _glInterface = GRGlInterface.Create();
+ _context = GRContext.CreateGl(_glInterface);
+
+
+ // bump the default resource cache limit
+ _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
+ Console.WriteLine("glcontext created and resource limit set");
+ }
+
+ _topLevelImpl.Surfaces = new[] { new BlazorSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize(100, 100), 1, GRSurfaceOrigin.BottomLeft) };
+ }
+ else
+ {
+ //_topLevelImpl.SetSurface(ColorType,
+ // new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
+ }
+
+ // _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+ Threading.Dispatcher.UIThread.Post(async () =>
+ {
+ //_interop.RequestAnimationFrame(true);
+
+ // _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged);
+ // _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged);
+
+ //_sizeWatcher.Start();
+ });
+ }
+
+ internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action blitCallback)
+ {
+ //_currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback);
+ }
+
+ public Control? Content
+ {
+ get => (Control)_topLevel.Content!;
+ set => _topLevel.Content = value;
+ }
+
+ public bool KeyPreventDefault { get; set; }
+
+ internal INativeControlHostImpl GetNativeControlHostImpl()
+ {
+ throw new NotImplementedException();
+ //return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
+ }
+
+ internal IStorageProvider GetStorageProvider()
+ {
+ throw new NotImplementedException();
+ //return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
+ }
+
+ private void OnRenderFrame()
+ {
+ if (_useGL && (_jsGlInfo == null))
+ {
+ Console.WriteLine("nothing to render");
+ return;
+ }
+ if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0)
+ {
+ Console.WriteLine("nothing to render");
+ return;
+ }
+
+ ManualTriggerRenderTimer.Instance.RaiseTick();
+ }
+
+ private void ForceBlit()
+ {
+ // Note: this is technically a hack, but it's a kinda unique use case when
+ // we want to blit the previous frame
+ // renderer doesn't have much control over the render target
+ // we render on the UI thread
+ // We also don't want to have it as a meaningful public API.
+ // Therefore we have InternalsVisibleTo hack here.
+
+ if (_topLevel.Renderer is CompositingRenderer dr)
+ {
+ //dr.CompositionTarget.ImmediateUIThreadRender();
+ }
+ }
+
+ private void OnDpiChanged(double newDpi)
+ {
+ if (Math.Abs(_dpi - newDpi) > 0.0001)
+ {
+ _dpi = newDpi;
+
+ //_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+ _topLevelImpl.SetClientSize(_canvasSize, _dpi);
+
+ ForceBlit();
+ }
+ }
+
+ private void OnSizeChanged(SKSize newSize)
+ {
+ if (_canvasSize != newSize)
+ {
+ _canvasSize = newSize;
+
+ //_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+ _topLevelImpl.SetClientSize(_canvasSize, _dpi);
+
+ ForceBlit();
+ }
+ }
+
+ public void SetClient(ITextInputMethodClient? client)
+ {
+
+ }
+
+ public void SetCursorRect(Rect rect)
+ {
+ }
+
+ public void SetOptions(TextInputOptions options)
+ {
+ }
+
+ public void Reset()
+ {
+
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web/BlazorSingleViewLifetime.cs
new file mode 100644
index 0000000000..04c1117510
--- /dev/null
+++ b/src/Web/Avalonia.Web/BlazorSingleViewLifetime.cs
@@ -0,0 +1,46 @@
+using System.Runtime.InteropServices.JavaScript;
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Media;
+
+namespace Avalonia.Web.Blazor
+{
+ public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime
+ {
+ public AvaloniaView? View;
+
+ public Control? MainView
+ {
+ get => View!.Content;
+ set => View!.Content = value;
+ }
+ }
+
+ public static partial class WebAppBuilder
+ {
+
+
+ public static T UseBrowserWasm(
+ this T builder)
+ where T : AppBuilderBase, new()
+ {
+ Console.WriteLine("In UseBrowserWasm");
+ var lifetime = new BlazorSingleViewLifetime();
+
+ return builder
+ .UseWindowingSubsystem(BlazorWindowingPlatform.Register)
+ .UseSkia()
+ .With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() })
+ .AfterSetup(b =>
+ {
+ var view = new AvaloniaView();
+ lifetime.View = view;
+ Console.WriteLine("After setup");
+
+ })
+ .SetupWithLifetime(lifetime);
+
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web/BlazorSkiaGpu.cs b/src/Web/Avalonia.Web/BlazorSkiaGpu.cs
new file mode 100644
index 0000000000..7191a00a66
--- /dev/null
+++ b/src/Web/Avalonia.Web/BlazorSkiaGpu.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using Avalonia.Skia;
+
+namespace Avalonia.Web.Blazor
+{
+ public class BlazorSkiaGpu : ISkiaGpu
+ {
+ public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable