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