diff --git a/Avalonia.sln b/Avalonia.sln index 326efd287d..2de9706649 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -232,7 +232,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvv EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Blazor", "src\Web\Avalonia.Blazor\Avalonia.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}" EndProject diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs index a058b4cbb0..a6d7c0476a 100644 --- a/samples/ControlCatalog.Web/App.razor.cs +++ b/samples/ControlCatalog.Web/App.razor.cs @@ -1,4 +1,4 @@ -using Avalonia.Blazor; +using Avalonia.Web.Blazor; namespace ControlCatalog.Web; diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index 31eff00707..67c130648f 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -19,19 +19,19 @@ + + - - - + - + diff --git a/samples/ControlCatalog.Web/Pages/Index.razor b/samples/ControlCatalog.Web/Pages/Index.razor index 9bd1bd7e50..93ca07f9f1 100644 --- a/samples/ControlCatalog.Web/Pages/Index.razor +++ b/samples/ControlCatalog.Web/Pages/Index.razor @@ -1,5 +1,5 @@ @page "/" -@using Avalonia.Blazor +@using Avalonia.Web.Blazor diff --git a/samples/ControlCatalog.Web/Program.cs b/samples/ControlCatalog.Web/Program.cs index 08f35a1697..d1a7925813 100644 --- a/samples/ControlCatalog.Web/Program.cs +++ b/samples/ControlCatalog.Web/Program.cs @@ -15,7 +15,7 @@ public class Program public static WebAssemblyHostBuilder CreateHostBuilder(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); - + builder.RootComponents.Add("#app"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); diff --git a/samples/ControlCatalog.Web/libHarfBuzzSharp.a b/samples/ControlCatalog.Web/libHarfBuzzSharp.a deleted file mode 100644 index e5914ea295..0000000000 Binary files a/samples/ControlCatalog.Web/libHarfBuzzSharp.a and /dev/null differ diff --git a/samples/ControlCatalog.Web/wwwroot/index.html b/samples/ControlCatalog.Web/wwwroot/index.html index a253e71406..e07b93cfc8 100644 --- a/samples/ControlCatalog.Web/wwwroot/index.html +++ b/samples/ControlCatalog.Web/wwwroot/index.html @@ -6,7 +6,6 @@ Avalonia Sample - diff --git a/src/Web/Avalonia.Blazor/Interop/ActionHelper.cs b/src/Web/Avalonia.Blazor/Interop/ActionHelper.cs deleted file mode 100644 index c3cb913e62..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/ActionHelper.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.ComponentModel; -using Microsoft.JSInterop; - -namespace Avalonia.Blazor.Interop -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class ActionHelper - { - private readonly Action action; - - public ActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke() => action?.Invoke(); - } -} diff --git a/src/Web/Avalonia.Blazor/Interop/DpiWatcherInterop.cs b/src/Web/Avalonia.Blazor/Interop/DpiWatcherInterop.cs deleted file mode 100644 index 309be3c8b8..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/DpiWatcherInterop.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.JSInterop; - -namespace Avalonia.Blazor.Interop -{ - internal class DpiWatcherInterop : JSModuleInterop - { - private const string JsFilename = "./_content/Avalonia.Blazor/DpiWatcher.js"; - private const string StartSymbol = "DpiWatcher.start"; - private const string StopSymbol = "DpiWatcher.stop"; - private const string GetDpiSymbol = "DpiWatcher.getDpi"; - - private static DpiWatcherInterop? instance; - - private event Action? callbacksEvent; - private readonly FloatFloatActionHelper callbackHelper; - - private DotNetObjectReference? callbackReference; - - public static async Task ImportAsync(IJSRuntime js, Action? callback = null) - { - var interop = Get(js); - await interop.ImportAsync(); - if (callback != null) - interop.Subscribe(callback); - return interop; - } - - public static DpiWatcherInterop Get(IJSRuntime js) => - instance ??= new DpiWatcherInterop(js); - - private DpiWatcherInterop(IJSRuntime js) - : base(js, JsFilename) - { - callbackHelper = new FloatFloatActionHelper((o, n) => callbacksEvent?.Invoke(n)); - } - - protected override void OnDisposingModule() => - Stop(); - - public void Subscribe(Action callback) - { - var shouldStart = callbacksEvent == null; - - callbacksEvent += callback; - - var dpi = shouldStart - ? Start() - : GetDpi(); - - callback(dpi); - } - - public void Unsubscribe(Action callback) - { - callbacksEvent -= callback; - - if (callbacksEvent == null) - Stop(); - } - - private double Start() - { - if (callbackReference != null) - return GetDpi(); - - callbackReference = DotNetObjectReference.Create(callbackHelper); - - return Invoke(StartSymbol, callbackReference); - } - - private void Stop() - { - if (callbackReference == null) - return; - - Invoke(StopSymbol); - - callbackReference?.Dispose(); - callbackReference = null; - } - - public double GetDpi() => - Invoke(GetDpiSymbol); - } -} diff --git a/src/Web/Avalonia.Blazor/Interop/FloatFloatActionHelper.cs b/src/Web/Avalonia.Blazor/Interop/FloatFloatActionHelper.cs deleted file mode 100644 index 6eff79ac42..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/FloatFloatActionHelper.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.ComponentModel; -using Microsoft.JSInterop; - -namespace Avalonia.Blazor.Interop -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public class FloatFloatActionHelper - { - private readonly Action action; - - public FloatFloatActionHelper(Action action) - { - this.action = action; - } - - [JSInvokable] - public void Invoke(float width, float height) => action?.Invoke(width, height); - } -} diff --git a/src/Web/Avalonia.Blazor/Interop/JSModuleInterop.cs b/src/Web/Avalonia.Blazor/Interop/JSModuleInterop.cs deleted file mode 100644 index 424668fe8c..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/JSModuleInterop.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.JSInterop; - -namespace Avalonia.Blazor.Interop -{ - internal class JSModuleInterop : IDisposable - { - private readonly Task moduleTask; - private IJSUnmarshalledObjectReference? module; - - public JSModuleInterop(IJSRuntime js, string filename) - { - if (js is not IJSInProcessRuntime) - throw new NotSupportedException("SkiaSharp currently only works on Web Assembly."); - - moduleTask = js.InvokeAsync("import", filename).AsTask(); - } - - public async Task ImportAsync() - { - module = await moduleTask; - } - - public void Dispose() - { - OnDisposingModule(); - Module.Dispose(); - } - - protected IJSUnmarshalledObjectReference Module => - module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); - - protected void Invoke(string identifier, params object?[]? args) => - Module.InvokeVoid(identifier, args); - - protected TValue Invoke(string identifier, params object?[]? args) => - Module.Invoke(identifier, args); - - protected virtual void OnDisposingModule() { } - } -} diff --git a/src/Web/Avalonia.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Blazor/Interop/SKHtmlCanvasInterop.cs deleted file mode 100644 index 67f098c4ca..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/SKHtmlCanvasInterop.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Blazor.Interop -{ - internal class SKHtmlCanvasInterop : JSModuleInterop - { - private const string JsFilename = "./_content/Avalonia.Blazor/SKHtmlCanvas.js"; - private const string InitGLSymbol = "SKHtmlCanvas.initGL"; - private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; - private const string DeinitSymbol = "SKHtmlCanvas.deinit"; - private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame"; - private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; - - private readonly ElementReference htmlCanvas; - private readonly string htmlElementId; - private readonly ActionHelper callbackHelper; - - private DotNetObjectReference? callbackReference; - - public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) - { - var interop = new SKHtmlCanvasInterop(js, element, callback); - await interop.ImportAsync(); - return interop; - } - - public SKHtmlCanvasInterop(IJSRuntime js, ElementReference element, Action renderFrameCallback) - : base(js, JsFilename) - { - htmlCanvas = element; - htmlElementId = element.Id; - - callbackHelper = new ActionHelper(renderFrameCallback); - } - - protected override void OnDisposingModule() => - Deinit(); - - public GLInfo InitGL() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(callbackHelper); - - return Invoke(InitGLSymbol, htmlCanvas, htmlElementId, callbackReference); - } - - public bool InitRaster() - { - if (callbackReference != null) - throw new InvalidOperationException("Unable to initialize the same canvas more than once."); - - callbackReference = DotNetObjectReference.Create(callbackHelper); - - return Invoke(InitRasterSymbol, htmlCanvas, htmlElementId, callbackReference); - } - - public void Deinit() - { - if (callbackReference == null) - return; - - Invoke(DeinitSymbol, htmlElementId); - - callbackReference?.Dispose(); - } - - public void RequestAnimationFrame(bool enableRenderLoop, int rawWidth, int rawHeight) => - Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop, rawWidth, rawHeight); - - public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => - Invoke(PutImageDataSymbol, htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); - - public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); - } -} diff --git a/src/Web/Avalonia.Blazor/Interop/SizeWatcherInterop.cs b/src/Web/Avalonia.Blazor/Interop/SizeWatcherInterop.cs deleted file mode 100644 index bc53c75c1a..0000000000 --- a/src/Web/Avalonia.Blazor/Interop/SizeWatcherInterop.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using SkiaSharp; - -namespace Avalonia.Blazor.Interop -{ - internal class SizeWatcherInterop : JSModuleInterop - { - private const string JsFilename = "./_content/Avalonia.Blazor/SizeWatcher.js"; - private const string ObserveSymbol = "SizeWatcher.observe"; - private const string UnobserveSymbol = "SizeWatcher.unobserve"; - - private readonly ElementReference htmlElement; - private readonly string htmlElementId; - private readonly FloatFloatActionHelper callbackHelper; - - private DotNetObjectReference? callbackReference; - - public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) - { - var interop = new SizeWatcherInterop(js, element, callback); - await interop.ImportAsync(); - interop.Start(); - return interop; - } - - public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action callback) - : base(js, JsFilename) - { - htmlElement = element; - htmlElementId = element.Id; - callbackHelper = new FloatFloatActionHelper((x, y) => callback(new SKSize(x, y))); - } - - protected override void OnDisposingModule() => - Stop(); - - public void Start() - { - if (callbackReference != null) - return; - - callbackReference = DotNetObjectReference.Create(callbackHelper); - - Invoke(ObserveSymbol, htmlElement, htmlElementId, callbackReference); - } - - public void Stop() - { - if (callbackReference == null) - return; - - Invoke(UnobserveSymbol, htmlElementId); - - callbackReference?.Dispose(); - callbackReference = null; - } - } -} diff --git a/src/Web/Avalonia.Blazor/SKTypefaceCollection.cs b/src/Web/Avalonia.Blazor/SKTypefaceCollection.cs deleted file mode 100644 index af7855f929..0000000000 --- a/src/Web/Avalonia.Blazor/SKTypefaceCollection.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Concurrent; -using Avalonia.Media; -using SkiaSharp; - -namespace Avalonia.Skia -{ - internal class SKTypefaceCollection - { - private readonly ConcurrentDictionary _typefaces = - new ConcurrentDictionary(); - - public void AddTypeface(Typeface key, SKTypeface typeface) - { - _typefaces.TryAdd(key, typeface); - } - - public SKTypeface Get(Typeface typeface) - { - return GetNearestMatch(_typefaces, typeface); - } - - private static SKTypeface GetNearestMatch(IDictionary typefaces, Typeface key) - { - if (typefaces.TryGetValue(key, out var typeface)) - { - return typeface; - } - - var initialWeight = (int)key.Weight; - - var weight = (int)key.Weight; - - weight -= weight % 50; // make sure we start at a full weight - - for (var i = 0; i < 2; i++) - { - for (var j = 0; j < initialWeight; j += 50) - { - if (weight - j >= 100) - { - if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight - j)), out typeface)) - { - return typeface; - } - } - - if (weight + j > 900) - { - continue; - } - - if (typefaces.TryGetValue(new Typeface(key.FontFamily, (FontStyle)i, (FontWeight)(weight + j)), out typeface)) - { - return typeface; - } - } - } - - //Nothing was found so we try to get a regular typeface. - return typefaces.TryGetValue(new Typeface(key.FontFamily), out typeface) ? typeface : null; - } - } -} diff --git a/src/Web/Avalonia.Blazor/SKTypefaceCollectionCache.cs b/src/Web/Avalonia.Blazor/SKTypefaceCollectionCache.cs deleted file mode 100644 index de77c9186e..0000000000 --- a/src/Web/Avalonia.Blazor/SKTypefaceCollectionCache.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Concurrent; -using Avalonia.Media; -using Avalonia.Media.Fonts; -using Avalonia.Platform; -using SkiaSharp; - -namespace Avalonia.Skia -{ - internal static class SKTypefaceCollectionCache - { - private static readonly ConcurrentDictionary s_cachedCollections; - - static SKTypefaceCollectionCache() - { - s_cachedCollections = new ConcurrentDictionary(); - } - - /// - /// Gets the or add typeface collection. - /// - /// The font family. - /// - public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily) - { - return s_cachedCollections.GetOrAdd(fontFamily, x => CreateCustomFontCollection(fontFamily)); - } - - /// - /// Creates the custom font collection. - /// - /// The font family. - /// - private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily) - { - var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamily.Key); - - var typeFaceCollection = new SKTypefaceCollection(); - - var assetLoader = AvaloniaLocator.Current.GetService(); - - foreach (var asset in fontAssets) - { - var assetStream = assetLoader.Open(asset); - - if (assetStream == null) - throw new InvalidOperationException("Asset could not be loaded."); - - var typeface = SKTypeface.FromStream(assetStream); - - if (typeface == null) - throw new InvalidOperationException("Typeface could not be loaded."); - - if (typeface.FamilyName != fontFamily.Name) - { - continue; - } - - var key = new Typeface(fontFamily, typeface.FontSlant.ToAvalonia(), - (FontWeight)typeface.FontWeight); - - typeFaceCollection.AddTypeface(key, typeface); - } - - return typeFaceCollection; - } - } -} diff --git a/src/Web/Avalonia.Blazor/package-lock.json b/src/Web/Avalonia.Blazor/package-lock.json deleted file mode 100644 index 170a47c6ee..0000000000 --- a/src/Web/Avalonia.Blazor/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Avalonia.Blazor", - "lockfileVersion": 2, - "requires": true, - "packages": {} -} diff --git a/src/Web/Avalonia.Blazor/wwwroot/background.png b/src/Web/Avalonia.Blazor/wwwroot/background.png deleted file mode 100644 index e15a3bde6e..0000000000 Binary files a/src/Web/Avalonia.Blazor/wwwroot/background.png and /dev/null differ diff --git a/src/Web/Avalonia.Blazor/Assets/NotoMono-Regular.ttf b/src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf similarity index 100% rename from src/Web/Avalonia.Blazor/Assets/NotoMono-Regular.ttf rename to src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf diff --git a/src/Web/Avalonia.Blazor/Assets/NotoSans-Italic.ttf b/src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf similarity index 100% rename from src/Web/Avalonia.Blazor/Assets/NotoSans-Italic.ttf rename to src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf diff --git a/src/Web/Avalonia.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf b/src/Web/Avalonia.Web.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf similarity index 100% rename from src/Web/Avalonia.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf rename to src/Web/Avalonia.Web.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf diff --git a/src/Web/Avalonia.Blazor/Avalonia.Blazor.csproj b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj similarity index 56% rename from src/Web/Avalonia.Blazor/Avalonia.Blazor.csproj rename to src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj index 188b05ff15..c6d5c744ea 100644 --- a/src/Web/Avalonia.Blazor/Avalonia.Blazor.csproj +++ b/src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj @@ -7,13 +7,30 @@ Avalonia.Web.Blazor preview - - + + + wwwroot + true + true + + + + false + true + + + true + false + + + + + diff --git a/src/Web/Avalonia.Blazor/AvaloniaBlazorAppBuilder.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs similarity index 85% rename from src/Web/Avalonia.Blazor/AvaloniaBlazorAppBuilder.cs rename to src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs index 493b2c1c02..a8016e2f0c 100644 --- a/src/Web/Avalonia.Blazor/AvaloniaBlazorAppBuilder.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs @@ -2,11 +2,11 @@ using System; using Avalonia.Controls; using Avalonia.Platform; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class AvaloniaBlazorAppBuilder : AppBuilderBase { - public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action platformServices) + public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action platformServices) : base(platform, platformServices) { } diff --git a/src/Web/Avalonia.Blazor/AvaloniaView.razor b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor similarity index 100% rename from src/Web/Avalonia.Blazor/AvaloniaView.razor rename to src/Web/Avalonia.Web.Blazor/AvaloniaView.razor diff --git a/src/Web/Avalonia.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs similarity index 86% rename from src/Web/Avalonia.Blazor/AvaloniaView.razor.cs rename to src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index d2111c3f35..05412cfef0 100644 --- a/src/Web/Avalonia.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -1,15 +1,15 @@ -using Avalonia.Blazor.Interop; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Embedding; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Web.Blazor.Interop; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public partial class AvaloniaView : ITextInputMethodImpl { @@ -103,36 +103,48 @@ namespace Avalonia.Blazor static RawInputModifiers GetModifiers(WheelEventArgs e) { - RawInputModifiers modifiers = RawInputModifiers.None; + var modifiers = RawInputModifiers.None; - if (e.CtrlKey) modifiers |= RawInputModifiers.Control; - if (e.AltKey) modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) modifiers |= RawInputModifiers.Meta; + if (e.CtrlKey) + modifiers |= RawInputModifiers.Control; + if (e.AltKey) + modifiers |= RawInputModifiers.Alt; + if (e.ShiftKey) + modifiers |= RawInputModifiers.Shift; + if (e.MetaKey) + modifiers |= RawInputModifiers.Meta; return modifiers; } static RawInputModifiers GetModifiers(MouseEventArgs e) { - RawInputModifiers modifiers = RawInputModifiers.None; + var modifiers = RawInputModifiers.None; - if (e.CtrlKey) modifiers |= RawInputModifiers.Control; - if (e.AltKey) modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) modifiers |= RawInputModifiers.Meta; + if (e.CtrlKey) + modifiers |= RawInputModifiers.Control; + if (e.AltKey) + modifiers |= RawInputModifiers.Alt; + if (e.ShiftKey) + modifiers |= RawInputModifiers.Shift; + if (e.MetaKey) + modifiers |= RawInputModifiers.Meta; return modifiers; } static RawInputModifiers GetModifiers(KeyboardEventArgs e) { - RawInputModifiers modifiers = RawInputModifiers.None; + var modifiers = RawInputModifiers.None; - if (e.CtrlKey) modifiers |= RawInputModifiers.Control; - if (e.AltKey) modifiers |= RawInputModifiers.Alt; - if (e.ShiftKey) modifiers |= RawInputModifiers.Shift; - if (e.MetaKey) modifiers |= RawInputModifiers.Meta; + if (e.CtrlKey) + modifiers |= RawInputModifiers.Control; + if (e.AltKey) + modifiers |= RawInputModifiers.Alt; + if (e.ShiftKey) + modifiers |= RawInputModifiers.Shift; + if (e.MetaKey) + modifiers |= RawInputModifiers.Meta; return modifiers; } @@ -163,7 +175,7 @@ namespace Avalonia.Blazor { if (firstRender) { - Avalonia.Threading.Dispatcher.UIThread.Post(async () => + Threading.Dispatcher.UIThread.Post(async () => { await Js.InvokeVoidAsync("hideInput"); await Js.InvokeVoidAsync("setCursor", "default"); diff --git a/src/Web/Avalonia.Blazor/BlazorRuntimePlatform.cs b/src/Web/Avalonia.Web.Blazor/BlazorRuntimePlatform.cs similarity index 96% rename from src/Web/Avalonia.Blazor/BlazorRuntimePlatform.cs rename to src/Web/Avalonia.Web.Blazor/BlazorRuntimePlatform.cs index 7267294b7f..ecff76c5ea 100644 --- a/src/Web/Avalonia.Blazor/BlazorRuntimePlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorRuntimePlatform.cs @@ -1,10 +1,8 @@ -using System; using System.Runtime.InteropServices; -using System.Threading; using Avalonia.Platform; using Avalonia.Shared.PlatformSupport; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { class BlazorRuntimePlatform : IRuntimePlatform { @@ -46,7 +44,7 @@ namespace Avalonia.Blazor public int Size { get; } public bool IsDisposed => _data == IntPtr.Zero; } - + public IUnmanagedBlob AllocBlob(int size) { return new BasicBlob(size); diff --git a/src/Web/Avalonia.Blazor/BlazorSingleViewLifetime.cs b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs similarity index 81% rename from src/Web/Avalonia.Blazor/BlazorSingleViewLifetime.cs rename to src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs index 5a1429e8d9..4bbe816c73 100644 --- a/src/Web/Avalonia.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs @@ -1,7 +1,8 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Platform; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime { @@ -10,7 +11,7 @@ namespace Avalonia.Blazor public static class BlazorSingleViewLifetimeExtensions { - + public static AvaloniaBlazorAppBuilder SetupWithBlazorSingleViewLifetime() where TApp : Application, new() @@ -20,6 +21,8 @@ namespace Avalonia.Blazor .With(new SkiaOptions() { CustomGpuFactory = () => new BlazorSkiaGpu() }) .SetupWithLifetime(new BlazorSingleViewLifetime()); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new CustomFontManagerImpl()); + return builder; } } diff --git a/src/Web/Avalonia.Blazor/BlazorSkiaGpu.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs similarity index 93% rename from src/Web/Avalonia.Blazor/BlazorSkiaGpu.cs rename to src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs index cc3d9039a8..fe6873cba6 100644 --- a/src/Web/Avalonia.Blazor/BlazorSkiaGpu.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; -using Avalonia; using Avalonia.Skia; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class BlazorSkiaGpu : ISkiaGpu { diff --git a/src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderSession.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs similarity index 96% rename from src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderSession.cs rename to src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs index fc88b93711..1a88284311 100644 --- a/src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderSession.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs @@ -1,7 +1,7 @@ using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession { diff --git a/src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderTarget.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs similarity index 98% rename from src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderTarget.cs rename to src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs index 928ede2a0e..ee7374634f 100644 --- a/src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderTarget.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs @@ -1,7 +1,7 @@ using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget { diff --git a/src/Web/Avalonia.Blazor/BlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs similarity index 83% rename from src/Web/Avalonia.Blazor/BlazorSkiaSurface.cs rename to src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs index 167d526576..7631967493 100644 --- a/src/Web/Avalonia.Blazor/BlazorSkiaSurface.cs +++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs @@ -1,8 +1,7 @@ -using Avalonia; -using Avalonia.Blazor.Interop; +using Avalonia.Web.Blazor.Interop; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { internal class BlazorSkiaSurface { diff --git a/src/Web/Avalonia.Blazor/CustomFontManagerImpl.cs b/src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs similarity index 91% rename from src/Web/Avalonia.Blazor/CustomFontManagerImpl.cs rename to src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs index 98db5b3f98..e0dd7e59ee 100644 --- a/src/Web/Avalonia.Blazor/CustomFontManagerImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; using Avalonia.Skia; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class CustomFontManagerImpl : IFontManagerImpl { @@ -14,11 +14,11 @@ namespace Avalonia.Blazor private readonly string _defaultFamilyName; private readonly Typeface _defaultTypeface = - new Typeface("avares://Avalonia.Blazor/Assets#Noto Mono"); + new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Mono"); private readonly Typeface _italicTypeface = - new Typeface("avares://Avalonia.Blazor/Assets#Noto Sans"); + new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Sans"); private readonly Typeface _emojiTypeface = - new Typeface("avares://Avalonia.Blazor/Assets#Twitter Color Emoji"); + new Typeface("avares://Avalonia.Web.Blazor/Assets#Twitter Color Emoji"); public CustomFontManagerImpl() { diff --git a/src/Web/Avalonia.Blazor/ExampleJsInterop.cs b/src/Web/Avalonia.Web.Blazor/ExampleJsInterop.cs similarity index 97% rename from src/Web/Avalonia.Blazor/ExampleJsInterop.cs rename to src/Web/Avalonia.Web.Blazor/ExampleJsInterop.cs index 63a32724f1..4136afc843 100644 --- a/src/Web/Avalonia.Blazor/ExampleJsInterop.cs +++ b/src/Web/Avalonia.Web.Blazor/ExampleJsInterop.cs @@ -1,6 +1,6 @@ using Microsoft.JSInterop; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { // This class provides an example of how JavaScript functionality can be wrapped // in a .NET class for easy consumption. The associated JavaScript module is diff --git a/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs b/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs new file mode 100644 index 0000000000..d6d504e07e --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel; +using Microsoft.JSInterop; + +namespace Avalonia.Web.Blazor.Interop +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public class ActionHelper + { + private readonly Action action; + + public ActionHelper(Action action) + { + this.action = action; + } + + [JSInvokable] + public void Invoke() => action?.Invoke(); + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs new file mode 100644 index 0000000000..29a2686b3f --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs @@ -0,0 +1,87 @@ +using System; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +namespace Avalonia.Web.Blazor.Interop +{ + internal class DpiWatcherInterop : JSModuleInterop + { + private const string JsFilename = "./_content/Avalonia.Web.Blazor/DpiWatcher.js"; + private const string StartSymbol = "DpiWatcher.start"; + private const string StopSymbol = "DpiWatcher.stop"; + private const string GetDpiSymbol = "DpiWatcher.getDpi"; + + private static DpiWatcherInterop? instance; + + private event Action? callbacksEvent; + private readonly FloatFloatActionHelper callbackHelper; + + private DotNetObjectReference? callbackReference; + + public static async Task ImportAsync(IJSRuntime js, Action? callback = null) + { + var interop = Get(js); + await interop.ImportAsync(); + if (callback != null) + interop.Subscribe(callback); + return interop; + } + + public static DpiWatcherInterop Get(IJSRuntime js) => + instance ??= new DpiWatcherInterop(js); + + private DpiWatcherInterop(IJSRuntime js) + : base(js, JsFilename) + { + callbackHelper = new FloatFloatActionHelper((o, n) => callbacksEvent?.Invoke(n)); + } + + protected override void OnDisposingModule() => + Stop(); + + public void Subscribe(Action callback) + { + var shouldStart = callbacksEvent == null; + + callbacksEvent += callback; + + var dpi = shouldStart + ? Start() + : GetDpi(); + + callback(dpi); + } + + public void Unsubscribe(Action callback) + { + callbacksEvent -= callback; + + if (callbacksEvent == null) + Stop(); + } + + private double Start() + { + if (callbackReference != null) + return GetDpi(); + + callbackReference = DotNetObjectReference.Create(callbackHelper); + + return Invoke(StartSymbol, callbackReference); + } + + private void Stop() + { + if (callbackReference == null) + return; + + Invoke(StopSymbol); + + callbackReference?.Dispose(); + callbackReference = null; + } + + public double GetDpi() => + Invoke(GetDpiSymbol); + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/FloatFloatActionHelper.cs b/src/Web/Avalonia.Web.Blazor/Interop/FloatFloatActionHelper.cs new file mode 100644 index 0000000000..80b9e5689a --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/FloatFloatActionHelper.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel; +using Microsoft.JSInterop; + +namespace Avalonia.Web.Blazor.Interop +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public class FloatFloatActionHelper + { + private readonly Action action; + + public FloatFloatActionHelper(Action action) + { + this.action = action; + } + + [JSInvokable] + public void Invoke(float width, float height) => action?.Invoke(width, height); + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs new file mode 100644 index 0000000000..524326f7c0 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs @@ -0,0 +1,42 @@ +using System; +using System.Threading.Tasks; +using Microsoft.JSInterop; + +namespace Avalonia.Web.Blazor.Interop +{ + internal class JSModuleInterop : IDisposable + { + private readonly Task moduleTask; + private IJSUnmarshalledObjectReference? module; + + public JSModuleInterop(IJSRuntime js, string filename) + { + if (js is not IJSInProcessRuntime) + throw new NotSupportedException("SkiaSharp currently only works on Web Assembly."); + + moduleTask = js.InvokeAsync("import", filename).AsTask(); + } + + public async Task ImportAsync() + { + module = await moduleTask; + } + + public void Dispose() + { + OnDisposingModule(); + Module.Dispose(); + } + + protected IJSUnmarshalledObjectReference Module => + module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first."); + + protected void Invoke(string identifier, params object?[]? args) => + Module.InvokeVoid(identifier, args); + + protected TValue Invoke(string identifier, params object?[]? args) => + Module.Invoke(identifier, args); + + protected virtual void OnDisposingModule() { } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs new file mode 100644 index 0000000000..4f5d4cdf70 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using SkiaSharp; + +namespace Avalonia.Web.Blazor.Interop +{ + internal class SKHtmlCanvasInterop : JSModuleInterop + { + private const string JsFilename = "./_content/Avalonia.Web.Blazor/SKHtmlCanvas.js"; + private const string InitGLSymbol = "SKHtmlCanvas.initGL"; + private const string InitRasterSymbol = "SKHtmlCanvas.initRaster"; + private const string DeinitSymbol = "SKHtmlCanvas.deinit"; + private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame"; + private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData"; + + private readonly ElementReference htmlCanvas; + private readonly string htmlElementId; + private readonly ActionHelper callbackHelper; + + private DotNetObjectReference? callbackReference; + + public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) + { + var interop = new SKHtmlCanvasInterop(js, element, callback); + await interop.ImportAsync(); + return interop; + } + + public SKHtmlCanvasInterop(IJSRuntime js, ElementReference element, Action renderFrameCallback) + : base(js, JsFilename) + { + htmlCanvas = element; + htmlElementId = element.Id; + + callbackHelper = new ActionHelper(renderFrameCallback); + } + + protected override void OnDisposingModule() => + Deinit(); + + public GLInfo InitGL() + { + if (callbackReference != null) + throw new InvalidOperationException("Unable to initialize the same canvas more than once."); + + callbackReference = DotNetObjectReference.Create(callbackHelper); + + return Invoke(InitGLSymbol, htmlCanvas, htmlElementId, callbackReference); + } + + public bool InitRaster() + { + if (callbackReference != null) + throw new InvalidOperationException("Unable to initialize the same canvas more than once."); + + callbackReference = DotNetObjectReference.Create(callbackHelper); + + return Invoke(InitRasterSymbol, htmlCanvas, htmlElementId, callbackReference); + } + + public void Deinit() + { + if (callbackReference == null) + return; + + Invoke(DeinitSymbol, htmlElementId); + + callbackReference?.Dispose(); + } + + public void RequestAnimationFrame(bool enableRenderLoop, int rawWidth, int rawHeight) => + Invoke(RequestAnimationFrameSymbol, htmlCanvas, enableRenderLoop, rawWidth, rawHeight); + + public void PutImageData(IntPtr intPtr, SKSizeI rawSize) => + Invoke(PutImageDataSymbol, htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height); + + public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs b/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs new file mode 100644 index 0000000000..8904137b8b --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs @@ -0,0 +1,61 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using SkiaSharp; + +namespace Avalonia.Web.Blazor.Interop +{ + internal class SizeWatcherInterop : JSModuleInterop + { + private const string JsFilename = "./_content/Avalonia.Web.Blazor/SizeWatcher.js"; + private const string ObserveSymbol = "SizeWatcher.observe"; + private const string UnobserveSymbol = "SizeWatcher.unobserve"; + + private readonly ElementReference htmlElement; + private readonly string htmlElementId; + private readonly FloatFloatActionHelper callbackHelper; + + private DotNetObjectReference? callbackReference; + + public static async Task ImportAsync(IJSRuntime js, ElementReference element, Action callback) + { + var interop = new SizeWatcherInterop(js, element, callback); + await interop.ImportAsync(); + interop.Start(); + return interop; + } + + public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action callback) + : base(js, JsFilename) + { + htmlElement = element; + htmlElementId = element.Id; + callbackHelper = new FloatFloatActionHelper((x, y) => callback(new SKSize(x, y))); + } + + protected override void OnDisposingModule() => + Stop(); + + public void Start() + { + if (callbackReference != null) + return; + + callbackReference = DotNetObjectReference.Create(callbackHelper); + + Invoke(ObserveSymbol, htmlElement, htmlElementId, callbackReference); + } + + public void Stop() + { + if (callbackReference == null) + return; + + Invoke(UnobserveSymbol, htmlElementId); + + callbackReference?.Dispose(); + callbackReference = null; + } + } +} diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.js b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.js new file mode 100644 index 0000000000..4eaeb7c921 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.js @@ -0,0 +1,28 @@ +export class DpiWatcher { + static getDpi() { + return window.devicePixelRatio; + } + static start(callback) { + //console.info(`Starting DPI watcher with callback ${callback._id}...`); + DpiWatcher.lastDpi = window.devicePixelRatio; + DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000); + DpiWatcher.callback = callback; + return DpiWatcher.lastDpi; + } + static stop() { + //console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`); + window.clearInterval(DpiWatcher.timerId); + DpiWatcher.callback = undefined; + } + static update() { + if (!DpiWatcher.callback) + return; + const currentDpi = window.devicePixelRatio; + const lastDpi = DpiWatcher.lastDpi; + DpiWatcher.lastDpi = currentDpi; + if (Math.abs(lastDpi - currentDpi) > 0.001) { + DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi); + } + } +} +//# sourceMappingURL=DpiWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Blazor/wwwroot/DpiWatcher.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/DpiWatcher.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.js b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.js new file mode 100644 index 0000000000..ddd53d09cc --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.js @@ -0,0 +1,163 @@ +export class SKHtmlCanvas { + constructor(useGL, element, callback) { + this.renderLoopEnabled = false; + this.renderLoopRequest = 0; + this.htmlCanvas = element; + this.renderFrameCallback = callback; + if (useGL) { + const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas); + if (!ctx) { + console.error(`Failed to create WebGL context: err ${ctx}`); + return null; + } + // make current + GL.makeContextCurrent(ctx); + // read values + const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING); + this.glInfo = { + context: ctx, + fboId: fbo ? fbo.id : 0, + stencil: GLctx.getParameter(GLctx.STENCIL_BITS), + sample: 0, + depth: GLctx.getParameter(GLctx.DEPTH_BITS), + }; + } + } + static initGL(element, elementId, callback) { + var view = SKHtmlCanvas.init(true, element, elementId, callback); + if (!view || !view.glInfo) + return null; + return view.glInfo; + } + static initRaster(element, elementId, callback) { + var view = SKHtmlCanvas.init(false, element, elementId, callback); + if (!view) + return false; + return true; + } + static init(useGL, element, elementId, callback) { + var htmlCanvas = element; + if (!htmlCanvas) { + console.error(`No canvas element was provided.`); + return null; + } + if (!SKHtmlCanvas.elements) + SKHtmlCanvas.elements = new Map(); + SKHtmlCanvas.elements[elementId] = element; + const view = new SKHtmlCanvas(useGL, element, callback); + htmlCanvas.SKHtmlCanvas = view; + return view; + } + static deinit(elementId) { + if (!elementId) + return; + const element = SKHtmlCanvas.elements[elementId]; + SKHtmlCanvas.elements.delete(elementId); + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.deinit(); + htmlCanvas.SKHtmlCanvas = undefined; + } + static requestAnimationFrame(element, renderLoop, width, height) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop, width, height); + } + static setEnableRenderLoop(element, enable) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); + } + static putImageData(element, pData, width, height) { + const htmlCanvas = element; + if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) + return; + htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); + } + deinit() { + this.setEnableRenderLoop(false); + } + requestAnimationFrame(renderLoop, width, height) { + // optionally update the render loop + if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) + this.setEnableRenderLoop(renderLoop); + // make sure the canvas is scaled correctly for the drawing + if (width && height) { + this.htmlCanvas.width = width; + this.htmlCanvas.height = height; + } + // skip because we have a render loop + if (this.renderLoopRequest !== 0) + return; + // add the draw to the next frame + this.renderLoopRequest = window.requestAnimationFrame(() => { + if (this.glInfo) { + // make current + GL.makeContextCurrent(this.glInfo.context); + } + this.renderFrameCallback.invokeMethod('Invoke'); + this.renderLoopRequest = 0; + // we may want to draw the next frame + if (this.renderLoopEnabled) + this.requestAnimationFrame(); + }); + } + setEnableRenderLoop(enable) { + this.renderLoopEnabled = enable; + // either start the new frame or cancel the existing one + if (enable) { + //console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`); + this.requestAnimationFrame(); + } + else if (this.renderLoopRequest !== 0) { + window.cancelAnimationFrame(this.renderLoopRequest); + this.renderLoopRequest = 0; + } + } + putImageData(pData, width, height) { + if (this.glInfo || !pData || width <= 0 || width <= 0) + return false; + var ctx = this.htmlCanvas.getContext('2d'); + if (!ctx) { + console.error(`Failed to obtain 2D canvas context.`); + return false; + } + // make sure the canvas is scaled correctly for the drawing + this.htmlCanvas.width = width; + this.htmlCanvas.height = height; + // set the canvas to be the bytes + var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4); + var imageData = new ImageData(buffer, width, height); + ctx.putImageData(imageData, 0, 0); + return true; + } + static createWebGLContext(htmlCanvas) { + const contextAttributes = { + alpha: 1, + depth: 1, + stencil: 8, + antialias: 1, + premultipliedAlpha: 1, + preserveDrawingBuffer: 0, + preferLowPowerToHighPerformance: 0, + failIfMajorPerformanceCaveat: 0, + majorVersion: 2, + minorVersion: 0, + enableExtensionsByDefault: 1, + explicitSwapControl: 0, + renderViaOffscreenBackBuffer: 0, + }; + let ctx = GL.createContext(htmlCanvas, contextAttributes); + if (!ctx && contextAttributes.majorVersion > 1) { + console.warn('Falling back to WebGL 1.0'); + contextAttributes.majorVersion = 1; + contextAttributes.minorVersion = 0; + ctx = GL.createContext(htmlCanvas, contextAttributes); + } + return ctx; + } +} +//# sourceMappingURL=SKHtmlCanvas.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Blazor/wwwroot/SKHtmlCanvas.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/SKHtmlCanvas.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts diff --git a/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.js b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.js new file mode 100644 index 0000000000..eabf07c122 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.js @@ -0,0 +1,42 @@ +export class SizeWatcher { + static observe(element, elementId, callback) { + if (!element || !callback) + return; + //console.info(`Adding size watcher observation with callback ${callback._id}...`); + SizeWatcher.init(); + const watcherElement = element; + watcherElement.SizeWatcher = { + callback: callback + }; + SizeWatcher.elements[elementId] = element; + SizeWatcher.observer.observe(element); + SizeWatcher.invoke(element); + } + static unobserve(elementId) { + if (!elementId || !SizeWatcher.observer) + return; + //console.info('Removing size watcher observation...'); + const element = SizeWatcher.elements[elementId]; + SizeWatcher.elements.delete(elementId); + SizeWatcher.observer.unobserve(element); + } + static init() { + if (SizeWatcher.observer) + return; + //console.info('Starting size watcher...'); + SizeWatcher.elements = new Map(); + SizeWatcher.observer = new ResizeObserver((entries) => { + for (let entry of entries) { + SizeWatcher.invoke(entry.target); + } + }); + } + static invoke(element) { + const watcherElement = element; + const instance = watcherElement.SizeWatcher; + if (!instance || !instance.callback) + return; + return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight); + } +} +//# sourceMappingURL=SizeWatcher.js.map \ No newline at end of file diff --git a/src/Web/Avalonia.Blazor/wwwroot/SizeWatcher.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/SizeWatcher.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts diff --git a/src/Web/Avalonia.Blazor/wwwroot/types/dotnet/extras.d.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/types/dotnet/extras.d.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts diff --git a/src/Web/Avalonia.Blazor/wwwroot/types/dotnet/index.d.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/types/dotnet/index.d.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts diff --git a/src/Web/Avalonia.Blazor/wwwroot/types/emscripten/index.d.ts b/src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/types/emscripten/index.d.ts rename to src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts diff --git a/src/Web/Avalonia.Blazor/Keycodes.cs b/src/Web/Avalonia.Web.Blazor/Keycodes.cs similarity index 99% rename from src/Web/Avalonia.Blazor/Keycodes.cs rename to src/Web/Avalonia.Web.Blazor/Keycodes.cs index 9d343d4f5d..00e7a4b7fa 100644 --- a/src/Web/Avalonia.Blazor/Keycodes.cs +++ b/src/Web/Avalonia.Web.Blazor/Keycodes.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using Avalonia.Input; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { internal static class Keycodes { diff --git a/src/Web/Avalonia.Blazor/ManualTriggerRenderTimer.cs b/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs similarity index 88% rename from src/Web/Avalonia.Blazor/ManualTriggerRenderTimer.cs rename to src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs index 8201af872c..03e79d6423 100644 --- a/src/Web/Avalonia.Blazor/ManualTriggerRenderTimer.cs +++ b/src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs @@ -2,16 +2,16 @@ using System; using System.Diagnostics; using Avalonia.Rendering; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class ManualTriggerRenderTimer : IRenderTimer { private static readonly Stopwatch _sw = Stopwatch.StartNew(); - + public static ManualTriggerRenderTimer Instance { get; } = new ManualTriggerRenderTimer(); - + public void RaiseTick() => Tick?.Invoke(_sw.Elapsed); - + public event Action Tick; } } \ No newline at end of file diff --git a/src/Web/Avalonia.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs similarity index 96% rename from src/Web/Avalonia.Blazor/RazorViewTopLevelImpl.cs rename to src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index 4ad5c011be..9a264c0698 100644 --- a/src/Web/Avalonia.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -1,5 +1,4 @@ using System.Diagnostics; -using Avalonia.Blazor.Interop; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -7,9 +6,10 @@ using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Web.Blazor.Interop; using SkiaSharp; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod { @@ -50,7 +50,7 @@ namespace Avalonia.Blazor if (_currentSurface is { }) { - _currentSurface.Size = new PixelSize((int)(size.Width), (int)(size.Height)); + _currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height); } Resized?.Invoke(newSize, PlatformResizeReason.User); diff --git a/src/Web/Avalonia.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs similarity index 93% rename from src/Web/Avalonia.Blazor/WinStubs.cs rename to src/Web/Avalonia.Web.Blazor/WinStubs.cs index 60b1ce8ad1..5b38484d48 100644 --- a/src/Web/Avalonia.Blazor/WinStubs.cs +++ b/src/Web/Avalonia.Web.Blazor/WinStubs.cs @@ -9,7 +9,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { class ClipboardStub : IClipboard { @@ -30,7 +30,7 @@ namespace Avalonia.Blazor { public void Dispose() { - + } } @@ -53,7 +53,7 @@ namespace Avalonia.Blazor { public void Save(Stream outputStream) { - + } } @@ -67,10 +67,10 @@ namespace Avalonia.Blazor class SystemDialogsStub : ISystemDialogImpl { public Task ShowFileDialogAsync(FileDialog dialog, Window parent) => - Task.FromResult((string[]) null); + Task.FromResult((string[])null); public Task ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) => - Task.FromResult((string) null); + Task.FromResult((string)null); } class ScreenStub : IScreenImpl diff --git a/src/Web/Avalonia.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs similarity index 94% rename from src/Web/Avalonia.Blazor/WindowingPlatform.cs rename to src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs index 4fa317f3b7..c6a64360a1 100644 --- a/src/Web/Avalonia.Blazor/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs @@ -1,5 +1,3 @@ -using System; -using System.Threading; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; @@ -7,7 +5,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Threading; -namespace Avalonia.Blazor +namespace Avalonia.Web.Blazor { public class BlazorWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface { @@ -17,7 +15,7 @@ namespace Avalonia.Blazor private static int _uiThreadId = -1; private static int _lockNesting = 0; - public IWindowImpl CreateWindow() => throw new System.NotSupportedException(); + public IWindowImpl CreateWindow() => throw new NotSupportedException(); IWindowImpl IWindowingPlatform.CreateEmbeddableWindow() { @@ -28,9 +26,9 @@ namespace Avalonia.Blazor { return null; } - + public static KeyboardDevice Keyboard { get; private set; } - + public static void Register() { var instance = new BlazorWindowingPlatform(); @@ -75,7 +73,7 @@ namespace Avalonia.Blazor { lock (_syncRootLock) { - if(_signaled) + if (_signaled) return; _signaled = true; IDisposable disp = null; @@ -88,7 +86,7 @@ namespace Avalonia.Blazor _signaled = false; disp.Dispose(); } - + using (Lock()) Signaled?.Invoke(null); }); @@ -102,7 +100,7 @@ namespace Avalonia.Blazor return true; // Blazor is single threaded. } } - + public event Action Signaled; class LockDisposable : IDisposable @@ -119,7 +117,7 @@ namespace Avalonia.Blazor Monitor.Exit(_uiLock); } } - + public static IDisposable Lock() { Monitor.Enter(_uiLock); @@ -132,4 +130,4 @@ namespace Avalonia.Blazor return new LockDisposable(); } } -} \ No newline at end of file +} diff --git a/src/Web/Avalonia.Blazor/_Imports.razor b/src/Web/Avalonia.Web.Blazor/_Imports.razor similarity index 100% rename from src/Web/Avalonia.Blazor/_Imports.razor rename to src/Web/Avalonia.Web.Blazor/_Imports.razor diff --git a/src/Web/Avalonia.Blazor/tsconfig.json b/src/Web/Avalonia.Web.Blazor/tsconfig.json similarity index 100% rename from src/Web/Avalonia.Blazor/tsconfig.json rename to src/Web/Avalonia.Web.Blazor/tsconfig.json diff --git a/src/Web/Avalonia.Blazor/wwwroot/.gitignore b/src/Web/Avalonia.Web.Blazor/wwwroot/.gitignore similarity index 100% rename from src/Web/Avalonia.Blazor/wwwroot/.gitignore rename to src/Web/Avalonia.Web.Blazor/wwwroot/.gitignore