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