18 changed files with 948 additions and 34 deletions
@ -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() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
namespace Avalonia.Web.Blazor |
||||
|
{ |
||||
|
internal interface IBlazorSkiaSurface |
||||
|
{ |
||||
|
public PixelSize Size { get; set; } |
||||
|
|
||||
|
public double Scaling { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue