Browse Source

Fix namspaces

Fix native asset reference
Move js interop classes
repros/web-null-signature
Benedikt Stebner 5 years ago
parent
commit
a212489efb
  1. 2
      Avalonia.sln
  2. 2
      samples/ControlCatalog.Web/App.razor.cs
  3. 8
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  4. 2
      samples/ControlCatalog.Web/Pages/Index.razor
  5. 2
      samples/ControlCatalog.Web/Program.cs
  6. BIN
      samples/ControlCatalog.Web/libHarfBuzzSharp.a
  7. 1
      samples/ControlCatalog.Web/wwwroot/index.html
  8. 20
      src/Web/Avalonia.Blazor/Interop/ActionHelper.cs
  9. 87
      src/Web/Avalonia.Blazor/Interop/DpiWatcherInterop.cs
  10. 20
      src/Web/Avalonia.Blazor/Interop/FloatFloatActionHelper.cs
  11. 42
      src/Web/Avalonia.Blazor/Interop/JSModuleInterop.cs
  12. 79
      src/Web/Avalonia.Blazor/Interop/SKHtmlCanvasInterop.cs
  13. 61
      src/Web/Avalonia.Blazor/Interop/SizeWatcherInterop.cs
  14. 63
      src/Web/Avalonia.Blazor/SKTypefaceCollection.cs
  15. 68
      src/Web/Avalonia.Blazor/SKTypefaceCollectionCache.cs
  16. 6
      src/Web/Avalonia.Blazor/package-lock.json
  17. BIN
      src/Web/Avalonia.Blazor/wwwroot/background.png
  18. 0
      src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf
  19. 0
      src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf
  20. 0
      src/Web/Avalonia.Web.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf
  21. 21
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
  22. 4
      src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs
  23. 0
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor
  24. 48
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  25. 6
      src/Web/Avalonia.Web.Blazor/BlazorRuntimePlatform.cs
  26. 7
      src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
  27. 3
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs
  28. 2
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs
  29. 2
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs
  30. 5
      src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
  31. 8
      src/Web/Avalonia.Web.Blazor/CustomFontManagerImpl.cs
  32. 2
      src/Web/Avalonia.Web.Blazor/ExampleJsInterop.cs
  33. 20
      src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs
  34. 87
      src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs
  35. 20
      src/Web/Avalonia.Web.Blazor/Interop/FloatFloatActionHelper.cs
  36. 42
      src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs
  37. 79
      src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs
  38. 61
      src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs
  39. 28
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.js
  40. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts
  41. 163
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.js
  42. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts
  43. 42
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.js
  44. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts
  45. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts
  46. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts
  47. 0
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts
  48. 2
      src/Web/Avalonia.Web.Blazor/Keycodes.cs
  49. 8
      src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs
  50. 6
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
  51. 10
      src/Web/Avalonia.Web.Blazor/WinStubs.cs
  52. 20
      src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
  53. 0
      src/Web/Avalonia.Web.Blazor/_Imports.razor
  54. 0
      src/Web/Avalonia.Web.Blazor/tsconfig.json
  55. 0
      src/Web/Avalonia.Web.Blazor/wwwroot/.gitignore

2
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

2
samples/ControlCatalog.Web/App.razor.cs

@ -1,4 +1,4 @@
using Avalonia.Blazor;
using Avalonia.Web.Blazor;
namespace ControlCatalog.Web;

8
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@ -19,19 +19,19 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="2.8.2-preview.171" />
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.171" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.155" />
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="2.88.0-preview.155" />
</ItemGroup>
<ItemGroup>
<NativeFileReference Include="libHarfBuzzSharp.a" />
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\libHarfBuzzSharp.a" />
<NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\2.0.23\libSkiaSharp.a" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Web\Avalonia.Blazor\Avalonia.Blazor.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>

2
samples/ControlCatalog.Web/Pages/Index.razor

@ -1,5 +1,5 @@
@page "/"
@using Avalonia.Blazor
@using Avalonia.Web.Blazor
<AvaloniaView />

2
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>("#app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

BIN
samples/ControlCatalog.Web/libHarfBuzzSharp.a

Binary file not shown.

1
samples/ControlCatalog.Web/wwwroot/index.html

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Avalonia Sample</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />
</head>

20
src/Web/Avalonia.Blazor/Interop/ActionHelper.cs

@ -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();
}
}

87
src/Web/Avalonia.Blazor/Interop/DpiWatcherInterop.cs

@ -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<double>? callbacksEvent;
private readonly FloatFloatActionHelper callbackHelper;
private DotNetObjectReference<FloatFloatActionHelper>? callbackReference;
public static async Task<DpiWatcherInterop> ImportAsync(IJSRuntime js, Action<double>? 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<double> callback)
{
var shouldStart = callbacksEvent == null;
callbacksEvent += callback;
var dpi = shouldStart
? Start()
: GetDpi();
callback(dpi);
}
public void Unsubscribe(Action<double> callback)
{
callbacksEvent -= callback;
if (callbacksEvent == null)
Stop();
}
private double Start()
{
if (callbackReference != null)
return GetDpi();
callbackReference = DotNetObjectReference.Create(callbackHelper);
return Invoke<double>(StartSymbol, callbackReference);
}
private void Stop()
{
if (callbackReference == null)
return;
Invoke(StopSymbol);
callbackReference?.Dispose();
callbackReference = null;
}
public double GetDpi() =>
Invoke<double>(GetDpiSymbol);
}
}

20
src/Web/Avalonia.Blazor/Interop/FloatFloatActionHelper.cs

@ -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<float, float> action;
public FloatFloatActionHelper(Action<float, float> action)
{
this.action = action;
}
[JSInvokable]
public void Invoke(float width, float height) => action?.Invoke(width, height);
}
}

42
src/Web/Avalonia.Blazor/Interop/JSModuleInterop.cs

@ -1,42 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.JSInterop;
namespace Avalonia.Blazor.Interop
{
internal class JSModuleInterop : IDisposable
{
private readonly Task<IJSUnmarshalledObjectReference> 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<IJSUnmarshalledObjectReference>("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<TValue>(string identifier, params object?[]? args) =>
Module.Invoke<TValue>(identifier, args);
protected virtual void OnDisposingModule() { }
}
}

79
src/Web/Avalonia.Blazor/Interop/SKHtmlCanvasInterop.cs

@ -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<ActionHelper>? callbackReference;
public static async Task<SKHtmlCanvasInterop> 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<GLInfo>(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<bool>(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);
}
}

61
src/Web/Avalonia.Blazor/Interop/SizeWatcherInterop.cs

@ -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<FloatFloatActionHelper>? callbackReference;
public static async Task<SizeWatcherInterop> ImportAsync(IJSRuntime js, ElementReference element, Action<SKSize> callback)
{
var interop = new SizeWatcherInterop(js, element, callback);
await interop.ImportAsync();
interop.Start();
return interop;
}
public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action<SKSize> 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;
}
}
}

63
src/Web/Avalonia.Blazor/SKTypefaceCollection.cs

@ -1,63 +0,0 @@
using System.Collections.Concurrent;
using Avalonia.Media;
using SkiaSharp;
namespace Avalonia.Skia
{
internal class SKTypefaceCollection
{
private readonly ConcurrentDictionary<Typeface, SKTypeface> _typefaces =
new ConcurrentDictionary<Typeface, SKTypeface>();
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<Typeface, SKTypeface> 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;
}
}
}

68
src/Web/Avalonia.Blazor/SKTypefaceCollectionCache.cs

@ -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<FontFamily, SKTypefaceCollection> s_cachedCollections;
static SKTypefaceCollectionCache()
{
s_cachedCollections = new ConcurrentDictionary<FontFamily, SKTypefaceCollection>();
}
/// <summary>
/// Gets the or add typeface collection.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <returns></returns>
public static SKTypefaceCollection GetOrAddTypefaceCollection(FontFamily fontFamily)
{
return s_cachedCollections.GetOrAdd(fontFamily, x => CreateCustomFontCollection(fontFamily));
}
/// <summary>
/// Creates the custom font collection.
/// </summary>
/// <param name="fontFamily">The font family.</param>
/// <returns></returns>
private static SKTypefaceCollection CreateCustomFontCollection(FontFamily fontFamily)
{
var fontAssets = FontFamilyLoader.LoadFontAssets(fontFamily.Key);
var typeFaceCollection = new SKTypefaceCollection();
var assetLoader = AvaloniaLocator.Current.GetService<IAssetLoader>();
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;
}
}
}

6
src/Web/Avalonia.Blazor/package-lock.json

@ -1,6 +0,0 @@
{
"name": "Avalonia.Blazor",
"lockfileVersion": 2,
"requires": true,
"packages": {}
}

BIN
src/Web/Avalonia.Blazor/wwwroot/background.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 B

0
src/Web/Avalonia.Blazor/Assets/NotoMono-Regular.ttf → src/Web/Avalonia.Web.Blazor/Assets/NotoMono-Regular.ttf

0
src/Web/Avalonia.Blazor/Assets/NotoSans-Italic.ttf → src/Web/Avalonia.Web.Blazor/Assets/NotoSans-Italic.ttf

0
src/Web/Avalonia.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf → src/Web/Avalonia.Web.Blazor/Assets/TwitterColorEmoji-SVGinOT.ttf

21
src/Web/Avalonia.Blazor/Avalonia.Blazor.csproj → src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj

@ -7,13 +7,30 @@
<PackageId>Avalonia.Web.Blazor</PackageId>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
<Compile Include="..\..\Shared\PlatformSupport\AssetLoader.cs" />
</ItemGroup>
<PropertyGroup>
<TypescriptOutDir>wwwroot</TypescriptOutDir>
<TypeScriptNoEmitOnError>true</TypeScriptNoEmitOnError>
<TypeScriptNoImplicitReturns>true</TypeScriptNoImplicitReturns>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptRemoveComments>false</TypeScriptRemoveComments>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptRemoveComments>true</TypeScriptRemoveComments>
<TypeScriptSourceMap>false</TypeScriptSourceMap>
</PropertyGroup>
<Import Project="..\..\..\build\SkiaSharp.props" />
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.0" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="4.5.2" PrivateAssets="all" />

4
src/Web/Avalonia.Blazor/AvaloniaBlazorAppBuilder.cs → 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<AvaloniaBlazorAppBuilder>
{
public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action<AvaloniaBlazorAppBuilder> platformServices)
public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action<AvaloniaBlazorAppBuilder> platformServices)
: base(platform, platformServices)
{
}

0
src/Web/Avalonia.Blazor/AvaloniaView.razor → src/Web/Avalonia.Web.Blazor/AvaloniaView.razor

48
src/Web/Avalonia.Blazor/AvaloniaView.razor.cs → 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");

6
src/Web/Avalonia.Blazor/BlazorRuntimePlatform.cs → 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);

7
src/Web/Avalonia.Blazor/BlazorSingleViewLifetime.cs → 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<TApp>()
where TApp : Application, new()
@ -20,6 +21,8 @@ namespace Avalonia.Blazor
.With(new SkiaOptions() { CustomGpuFactory = () => new BlazorSkiaGpu() })
.SetupWithLifetime(new BlazorSingleViewLifetime());
AvaloniaLocator.CurrentMutable.Bind<IFontManagerImpl>().ToConstant(new CustomFontManagerImpl());
return builder;
}
}

3
src/Web/Avalonia.Blazor/BlazorSkiaGpu.cs → 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
{

2
src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderSession.cs → 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
{

2
src/Web/Avalonia.Blazor/BlazorSkiaGpuRenderTarget.cs → 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
{

5
src/Web/Avalonia.Blazor/BlazorSkiaSurface.cs → 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
{

8
src/Web/Avalonia.Blazor/CustomFontManagerImpl.cs → 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()
{

2
src/Web/Avalonia.Blazor/ExampleJsInterop.cs → 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

20
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();
}
}

87
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<double>? callbacksEvent;
private readonly FloatFloatActionHelper callbackHelper;
private DotNetObjectReference<FloatFloatActionHelper>? callbackReference;
public static async Task<DpiWatcherInterop> ImportAsync(IJSRuntime js, Action<double>? 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<double> callback)
{
var shouldStart = callbacksEvent == null;
callbacksEvent += callback;
var dpi = shouldStart
? Start()
: GetDpi();
callback(dpi);
}
public void Unsubscribe(Action<double> callback)
{
callbacksEvent -= callback;
if (callbacksEvent == null)
Stop();
}
private double Start()
{
if (callbackReference != null)
return GetDpi();
callbackReference = DotNetObjectReference.Create(callbackHelper);
return Invoke<double>(StartSymbol, callbackReference);
}
private void Stop()
{
if (callbackReference == null)
return;
Invoke(StopSymbol);
callbackReference?.Dispose();
callbackReference = null;
}
public double GetDpi() =>
Invoke<double>(GetDpiSymbol);
}
}

20
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<float, float> action;
public FloatFloatActionHelper(Action<float, float> action)
{
this.action = action;
}
[JSInvokable]
public void Invoke(float width, float height) => action?.Invoke(width, height);
}
}

42
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<IJSUnmarshalledObjectReference> 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<IJSUnmarshalledObjectReference>("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<TValue>(string identifier, params object?[]? args) =>
Module.Invoke<TValue>(identifier, args);
protected virtual void OnDisposingModule() { }
}
}

79
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<ActionHelper>? callbackReference;
public static async Task<SKHtmlCanvasInterop> 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<GLInfo>(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<bool>(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);
}
}

61
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<FloatFloatActionHelper>? callbackReference;
public static async Task<SizeWatcherInterop> ImportAsync(IJSRuntime js, ElementReference element, Action<SKSize> callback)
{
var interop = new SizeWatcherInterop(js, element, callback);
await interop.ImportAsync();
interop.Start();
return interop;
}
public SizeWatcherInterop(IJSRuntime js, ElementReference element, Action<SKSize> 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;
}
}
}

28
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

0
src/Web/Avalonia.Blazor/wwwroot/DpiWatcher.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/DpiWatcher.ts

163
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

0
src/Web/Avalonia.Blazor/wwwroot/SKHtmlCanvas.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/SKHtmlCanvas.ts

42
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

0
src/Web/Avalonia.Blazor/wwwroot/SizeWatcher.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/SizeWatcher.ts

0
src/Web/Avalonia.Blazor/wwwroot/types/dotnet/extras.d.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/extras.d.ts

0
src/Web/Avalonia.Blazor/wwwroot/types/dotnet/index.d.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts

0
src/Web/Avalonia.Blazor/wwwroot/types/emscripten/index.d.ts → src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/emscripten/index.d.ts

2
src/Web/Avalonia.Blazor/Keycodes.cs → 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
{

8
src/Web/Avalonia.Blazor/ManualTriggerRenderTimer.cs → 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<TimeSpan> Tick;
}
}

6
src/Web/Avalonia.Blazor/RazorViewTopLevelImpl.cs → 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);

10
src/Web/Avalonia.Blazor/WinStubs.cs → 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<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent) =>
Task.FromResult((string[]) null);
Task.FromResult((string[])null);
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent) =>
Task.FromResult((string) null);
Task.FromResult((string)null);
}
class ScreenStub : IScreenImpl

20
src/Web/Avalonia.Blazor/WindowingPlatform.cs → 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<DispatcherPriority?> 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();
}
}
}
}

0
src/Web/Avalonia.Blazor/_Imports.razor → src/Web/Avalonia.Web.Blazor/_Imports.razor

0
src/Web/Avalonia.Blazor/tsconfig.json → src/Web/Avalonia.Web.Blazor/tsconfig.json

0
src/Web/Avalonia.Blazor/wwwroot/.gitignore → src/Web/Avalonia.Web.Blazor/wwwroot/.gitignore

Loading…
Cancel
Save