Browse Source

partially implement wasm backend initialisation.

pull/9028/head
Dan Walmsley 4 years ago
parent
commit
25878fa838
  1. 1
      src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
  2. 24
      src/Web/Avalonia.Web.Sample/Program.cs
  3. 5
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  4. 35
      src/Web/Avalonia.Web/AvaloniaRuntime.cs
  5. 236
      src/Web/Avalonia.Web/AvaloniaView.cs
  6. 46
      src/Web/Avalonia.Web/BlazorSingleViewLifetime.cs
  7. 26
      src/Web/Avalonia.Web/BlazorSkiaGpu.cs
  8. 37
      src/Web/Avalonia.Web/BlazorSkiaGpuRenderSession.cs
  9. 39
      src/Web/Avalonia.Web/BlazorSkiaGpuRenderTarget.cs
  10. 88
      src/Web/Avalonia.Web/BlazorSkiaRasterSurface.cs
  11. 30
      src/Web/Avalonia.Web/BlazorSkiaSurface.cs
  12. 9
      src/Web/Avalonia.Web/IBlazorSkiaSurface.cs
  13. 18
      src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
  14. 217
      src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs
  15. 52
      src/Web/Avalonia.Web/WinStubs.cs
  16. 110
      src/Web/Avalonia.Web/WindowingPlatform.cs
  17. 5
      src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts
  18. 4
      src/Web/Avalonia.Web/wwwroot/avalonia.js

1
src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj

@ -12,6 +12,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\samples\ControlCatalog\ControlCatalog.csproj" />
<ProjectReference Include="..\Avalonia.Web\Avalonia.Web.csproj" /> <ProjectReference Include="..\Avalonia.Web\Avalonia.Web.csproj" />
</ItemGroup> </ItemGroup>

24
src/Web/Avalonia.Web.Sample/Program.cs

@ -4,7 +4,10 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Avalonia;
using Avalonia.Web; using Avalonia.Web;
using Avalonia.Web.Blazor;
using ControlCatalog;
//using SkiaSharp; //using SkiaSharp;
internal partial class Program internal partial class Program
@ -15,24 +18,11 @@ internal partial class Program
private static void Main(string[] args) private static void Main(string[] args)
{ {
Console.WriteLine("Hello, Browser!"); BuildAvaloniaApp().UseBrowserWasm();
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);
} }
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>();
} }
public partial class MyClass public partial class MyClass

5
src/Web/Avalonia.Web/Avalonia.Web.csproj

@ -14,6 +14,11 @@
<Import Project="..\..\..\build\HarfBuzzSharp.props" /> <Import Project="..\..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\..\build\NullableEnable.props" /> <Import Project="..\..\..\build\NullableEnable.props" />
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="*.props"> <Content Include="*.props">
<Pack>true</Pack> <Pack>true</Pack>

35
src/Web/Avalonia.Web/AvaloniaRuntime.cs

@ -1,11 +1,23 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript; 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; namespace Avalonia.Web;
public partial class AvaloniaRuntime public partial class AvaloniaRuntime
{ {
public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth);
[DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)] [DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)]
@ -14,19 +26,26 @@ public partial class AvaloniaRuntime
[JSExport] [JSExport]
internal static void StartAvaloniaView(JSObject canvas) internal static void StartAvaloniaView(JSObject canvas)
{ {
InterceptGLObject();
// setup, get gl context... // setup, get gl context...
}
public static GLInfo InitialiseGL (JSObject canvas)
{
InterceptGLObject();
var info = InitGL(canvas, "testCanvas"); var info = InitGL(canvas, "testCanvas");
var glInfo = new GLInfo( var glInfo = new GLInfo(
info.GetPropertyAsInt32("context"), info.GetPropertyAsInt32("context"),
(uint)info.GetPropertyAsInt32("fboId"), (uint)info.GetPropertyAsInt32("fboId"),
info.GetPropertyAsInt32("stencil"), info.GetPropertyAsInt32("stencil"),
info.GetPropertyAsInt32("sample"), info.GetPropertyAsInt32("sample"),
info.GetPropertyAsInt32("depth")); info.GetPropertyAsInt32("depth"));
Console.WriteLine($"{glInfo.ContextId}, {glInfo.FboId}"); return glInfo;
} }
[JSImport("Canvas.createCanvas", "avalonia.ts")] [JSImport("Canvas.createCanvas", "avalonia.ts")]
@ -36,7 +55,7 @@ public partial class AvaloniaRuntime
public static partial void Foo(JSObject canvas); public static partial void Foo(JSObject canvas);
[JSImport("Canvas.initGL", "avalonia.ts")] [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")] [JSImport("StorageProvider.isFileApiSupported", "storage.ts")]
public static partial bool IsFileApiSupported(); public static partial bool IsFileApiSupported();

236
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<SkiaOptions>();
_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<IntPtr, SKSizeI> 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()
{
}
}
}

46
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<T>(
this T builder)
where T : AppBuilderBase<T>, 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);
}
}
}

26
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<object> surfaces)
{
foreach (var surface in surfaces)
{
if (surface is BlazorSkiaSurface blazorSkiaSurface)
{
return new BlazorSkiaGpuRenderTarget(blazorSkiaSurface);
}
}
return null;
}
public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session)
{
return null;
}
}
}

37
src/Web/Avalonia.Web/BlazorSkiaGpuRenderSession.cs

@ -0,0 +1,37 @@
using Avalonia.Skia;
using SkiaSharp;
namespace Avalonia.Web.Blazor
{
internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession
{
private readonly SKSurface _surface;
public BlazorSkiaGpuRenderSession(BlazorSkiaSurface blazorSkiaSurface, GRBackendRenderTarget renderTarget)
{
_surface = SKSurface.Create(blazorSkiaSurface.Context, renderTarget, blazorSkiaSurface.Origin, blazorSkiaSurface.ColorType);
GrContext = blazorSkiaSurface.Context;
ScaleFactor = blazorSkiaSurface.Scaling;
SurfaceOrigin = blazorSkiaSurface.Origin;
}
public void Dispose()
{
_surface.Flush();
_surface.Dispose();
}
public GRContext GrContext { get; }
public SKSurface SkSurface => _surface;
public double ScaleFactor { get; }
public GRSurfaceOrigin SurfaceOrigin { get; }
}
}

39
src/Web/Avalonia.Web/BlazorSkiaGpuRenderTarget.cs

@ -0,0 +1,39 @@
using Avalonia.Skia;
using SkiaSharp;
namespace Avalonia.Web.Blazor
{
internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget
{
private readonly GRBackendRenderTarget _renderTarget;
private readonly BlazorSkiaSurface _blazorSkiaSurface;
private readonly PixelSize _size;
public BlazorSkiaGpuRenderTarget(BlazorSkiaSurface blazorSkiaSurface)
{
_size = blazorSkiaSurface.Size;
var glFbInfo = new GRGlFramebufferInfo(blazorSkiaSurface.GlInfo.FboId, blazorSkiaSurface.ColorType.ToGlSizedFormat());
{
_blazorSkiaSurface = blazorSkiaSurface;
_renderTarget = new GRBackendRenderTarget(
(int)(blazorSkiaSurface.Size.Width * blazorSkiaSurface.Scaling),
(int)(blazorSkiaSurface.Size.Height * blazorSkiaSurface.Scaling),
blazorSkiaSurface.GlInfo.Samples,
blazorSkiaSurface.GlInfo.Stencils, glFbInfo);
}
}
public void Dispose()
{
_renderTarget.Dispose();
}
public ISkiaGpuRenderSession BeginRenderingSession()
{
return new BlazorSkiaGpuRenderSession(_blazorSkiaSurface, _renderTarget);
}
public bool IsCorrupted => _blazorSkiaSurface.Size != _size;
}
}

88
src/Web/Avalonia.Web/BlazorSkiaRasterSurface.cs

@ -0,0 +1,88 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using Avalonia.Skia;
using SkiaSharp;
namespace Avalonia.Web.Blazor
{
internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable
{
public SKColorType ColorType { get; set; }
public PixelSize Size { get; set; }
public double Scaling { get; set; }
private FramebufferData? _fbData;
private readonly Action<IntPtr, SKSizeI> _blitCallback;
private readonly Action _onDisposeAction;
public BlazorSkiaRasterSurface(
SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback)
{
ColorType = colorType;
Size = size;
Scaling = scaling;
_blitCallback = blitCallback;
_onDisposeAction = Blit;
}
public void Dispose()
{
_fbData?.Dispose();
_fbData = null;
}
public ILockedFramebuffer Lock()
{
var bytesPerPixel = 4; // TODO: derive from ColorType
var dpi = Scaling * 96.0;
var width = (int)(Size.Width * Scaling);
var height = (int)(Size.Height * Scaling);
if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height)
{
_fbData?.Dispose();
_fbData = new FramebufferData(width, height, bytesPerPixel);
}
var pixelFormat = ColorType.ToPixelFormat();
var data = _fbData.Value;
return new LockedFramebuffer(
data.Address, data.Size, data.RowBytes,
new Vector(dpi, dpi), pixelFormat, _onDisposeAction);
}
private void Blit()
{
if (_fbData != null)
{
var data = _fbData.Value;
_blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height));
}
}
private readonly struct FramebufferData
{
public PixelSize Size { get; }
public int RowBytes { get; }
public IntPtr Address { get; }
public FramebufferData(int width, int height, int bytesPerPixel)
{
Size = new PixelSize(width, height);
RowBytes = width * bytesPerPixel;
Address = Marshal.AllocHGlobal(width * height * bytesPerPixel);
}
public void Dispose()
{
Marshal.FreeHGlobal(Address);
}
}
}
}

30
src/Web/Avalonia.Web/BlazorSkiaSurface.cs

@ -0,0 +1,30 @@
using SkiaSharp;
using static Avalonia.Web.AvaloniaRuntime;
namespace Avalonia.Web.Blazor
{
internal class BlazorSkiaSurface : IBlazorSkiaSurface
{
public BlazorSkiaSurface(GRContext context, GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin)
{
Context = context;
GlInfo = glInfo;
ColorType = colorType;
Size = size;
Scaling = scaling;
Origin = origin;
}
public SKColorType ColorType { get; set; }
public PixelSize Size { get; set; }
public GRContext Context { get; set; }
public GRSurfaceOrigin Origin { get; set; }
public double Scaling { get; set; }
public GLInfo GlInfo { get; set; }
}
}

9
src/Web/Avalonia.Web/IBlazorSkiaSurface.cs

@ -0,0 +1,9 @@
namespace Avalonia.Web.Blazor
{
internal interface IBlazorSkiaSurface
{
public PixelSize Size { get; set; }
public double Scaling { get; set; }
}
}

18
src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs

@ -0,0 +1,18 @@
using System;
using System.Diagnostics;
using Avalonia.Rendering;
namespace Avalonia.Web.Blazor
{
public class ManualTriggerRenderTimer : IRenderTimer
{
private static readonly Stopwatch s_sw = Stopwatch.StartNew();
public static ManualTriggerRenderTimer Instance { get; } = new();
public void RaiseTick() => Tick?.Invoke(s_sw.Elapsed);
public event Action<TimeSpan>? Tick;
public bool RunsInBackground => false;
}
}

217
src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs

@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using SkiaSharp;
using static Avalonia.Web.AvaloniaRuntime;
#nullable enable
namespace Avalonia.Web.Blazor
{
internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private Size _clientSize;
private IBlazorSkiaSurface? _currentSurface;
private IInputRoot? _inputRoot;
private readonly Stopwatch _sw = Stopwatch.StartNew();
private readonly AvaloniaView _avaloniaView;
private readonly TouchDevice _touchDevice;
private readonly PenDevice _penDevice;
//private string _currentCursor = CssCursor.Default;
public RazorViewTopLevelImpl(AvaloniaView avaloniaView)
{
Surfaces = Enumerable.Empty<object>();
_avaloniaView = avaloniaView;
TransparencyLevel = WindowTransparencyLevel.None;
AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
_touchDevice = new TouchDevice();
_penDevice = new PenDevice();
}
public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
public void SetClientSize(SKSize size, double dpi)
{
var newSize = new Size(size.Width, size.Height);
if (Math.Abs(RenderScaling - dpi) > 0.0001)
{
if (_currentSurface is { })
{
_currentSurface.Scaling = dpi;
}
ScalingChanged?.Invoke(dpi);
}
if (newSize != _clientSize)
{
_clientSize = newSize;
if (_currentSurface is { })
{
_currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height);
}
Resized?.Invoke(newSize, PlatformResizeReason.User);
}
}
public void RawPointerEvent(
RawPointerEventType eventType, string pointerType,
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId)
{
if (_inputRoot is { }
&& Input is { } input)
{
var device = GetPointerDevice(pointerType);
var args = device is TouchDevice ?
new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) :
new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
{
RawPointerId = touchPointId
};
input.Invoke(args);
}
}
private IPointerDevice GetPointerDevice(string pointerType)
{
return pointerType switch
{
"touch" => _touchDevice,
"pen" => _penDevice,
_ => MouseDevice
};
}
public void RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers)
{
if (_inputRoot is { })
{
Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers));
}
}
public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers)
{
/*if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
{
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
Input?.Invoke(args);
return args.Handled;
}
}
else if (Keycodes.KeyCodes.TryGetValue(key, out avkey))
{
if (_inputRoot is { })
{
var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
Input?.Invoke(args);
return args.Handled;
}
}*/
return false;
}
public void RawTextEvent(string text)
{
if (_inputRoot is { })
{
Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text));
}
}
public void Dispose()
{
}
public IRenderer CreateRenderer(IRenderRoot root)
{
var loop = AvaloniaLocator.Current.GetRequiredService<IRenderLoop>();
return new CompositingRenderer(root, new Compositor(loop, null));
}
public void Invalidate(Rect rect)
{
//Console.WriteLine("invalidate rect called");
}
public void SetInputRoot(IInputRoot inputRoot)
{
_inputRoot = inputRoot;
}
public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y);
public void SetCursor(ICursorImpl? cursor)
{
/* var val = (cursor as CssCursor)?.Value ?? CssCursor.Default;
if (_currentCursor != val)
{
SetCssCursor?.Invoke(val);
_currentCursor = val;
}*/
}
public IPopupImpl? CreatePopup()
{
return null;
}
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
}
public Size ClientSize => _clientSize;
public Size? FrameSize => null;
public double RenderScaling => _currentSurface?.Scaling ?? 1;
public IEnumerable<object> Surfaces { get; set; }
public Action<string>? SetCssCursor { get; set; }
public Action<RawInputEventArgs>? Input { get; set; }
public Action<Rect>? Paint { get; set; }
public Action<Size, PlatformResizeReason>? Resized { get; set; }
public Action<double>? ScalingChanged { get; set; }
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
public Action? Closed { get; set; }
public Action? LostFocus { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; } = BlazorWindowingPlatform.Keyboard;
public WindowTransparencyLevel TransparencyLevel { get; }
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
public ITextInputMethodImpl TextInputMethod => _avaloniaView;
public INativeControlHostImpl? NativeControlHost => _avaloniaView.GetNativeControlHostImpl();
public IStorageProvider StorageProvider => _avaloniaView.GetStorageProvider();
}
}

52
src/Web/Avalonia.Web/WinStubs.cs

@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.IO;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Web.Blazor
{
internal class IconLoaderStub : IPlatformIconLoader
{
private class IconStub : IWindowIconImpl
{
public void Save(Stream outputStream)
{
}
}
public IWindowIconImpl LoadIcon(string fileName) => new IconStub();
public IWindowIconImpl LoadIcon(Stream stream) => new IconStub();
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub();
}
internal class ScreenStub : IScreenImpl
{
public int ScreenCount => 1;
public IReadOnlyList<Screen> AllScreens { get; } =
new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
public Screen? ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen? ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
}
}

110
src/Web/Avalonia.Web/WindowingPlatform.cs

@ -0,0 +1,110 @@
using System;
using System.Threading;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
#nullable enable
namespace Avalonia.Web.Blazor
{
public class BlazorWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
{
private bool _signaled;
private static KeyboardDevice? s_keyboard;
public IWindowImpl CreateWindow() => throw new NotSupportedException();
IWindowImpl IWindowingPlatform.CreateEmbeddableWindow()
{
throw new NotImplementedException();
}
public ITrayIconImpl? CreateTrayIcon()
{
return null;
}
public static KeyboardDevice Keyboard => s_keyboard ??
throw new InvalidOperationException("123 BlazorWindowingPlatform not registered.");
public static void Register()
{
Console.WriteLine("Registering windowing");
var instance = new BlazorWindowingPlatform();
s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
//.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
//.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToConstant(instance)
.Bind<IPlatformThreadingInterface>().ToConstant(instance)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}
public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotSupportedException();
}
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
return GetRuntimePlatform()
.StartSystemTimer(interval, () =>
{
Dispatcher.UIThread.RunJobs(priority);
tick();
});
}
public void Signal(DispatcherPriority priority)
{
if (_signaled)
return;
_signaled = true;
IDisposable? disp = null;
disp = GetRuntimePlatform()
.StartSystemTimer(TimeSpan.FromMilliseconds(1),
() =>
{
_signaled = false;
disp?.Dispose();
Signaled?.Invoke(null);
});
}
public bool CurrentThreadIsLoopThread
{
get
{
return true; // Blazor is single threaded.
}
}
public event Action<DispatcherPriority?>? Signaled;
private static IRuntimePlatform GetRuntimePlatform()
{
return AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
}
}
}

5
src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts

@ -11,9 +11,4 @@ export class AvaloniaRuntime {
Canvas Canvas
}); });
} }
createAvaloniaView(element: HTMLDivElement): void {
const canvas = document.createElement("canvas");
element.appendChild(canvas);
}
} }

4
src/Web/Avalonia.Web/wwwroot/avalonia.js

@ -109,10 +109,6 @@ var AvaloniaRuntime = class {
Canvas Canvas
}); });
} }
createAvaloniaView(element) {
const canvas = document.createElement("canvas");
element.appendChild(canvas);
}
}; };
// modules/avalonia.ts // modules/avalonia.ts

Loading…
Cancel
Save