Browse Source

implement dpi and size tracking.

pull/9028/head
Dan Walmsley 3 years ago
parent
commit
8d6050bf97
  1. 1
      src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
  2. 10
      src/Web/Avalonia.Web.Sample/css/style.css
  3. 28
      src/Web/Avalonia.Web.Sample/index.html
  4. 12
      src/Web/Avalonia.Web/AvaloniaRuntime.cs
  5. 50
      src/Web/Avalonia.Web/AvaloniaView.cs
  6. 15
      src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs
  7. 110
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  8. 6
      src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts

1
src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj

@ -19,6 +19,7 @@
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="main.js" />
<WasmExtraFilesToDeploy Include="css/style.css" />
<NativeFileReference Include="C:\Users\User\.nuget\packages\harfbuzzsharp.nativeassets.webassembly\2.8.2.1-preview.108\build\netstandard1.0\libHarfBuzzSharp.a\3.1.7\libHarfBuzzSharp.a" />
<NativeFileReference Include="C:\Users\User\.nuget\packages\skiasharp.nativeassets.webassembly\2.88.1\build\netstandard1.0\libSkiaSharp.a\3.1.7\libSkiaSharp.a" />

10
src/Web/Avalonia.Web.Sample/css/style.css

@ -0,0 +1,10 @@
.avalonia-canvas {
opacity: 1;
background-color: red;
position: fixed;
width: 100vw;
height: 100vh;
top: 0px;
left: 0px;
z-index: 500;
}

28
src/Web/Avalonia.Web.Sample/index.html

@ -4,17 +4,29 @@
<html>
<head>
<title>Avalonia.Web.Sample</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./dotnet.js" />
<link rel="modulepreload" href="./avalonia.js" />
<title>Avalonia.Web.Sample</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="modulepreload" href="./main.js" />
<link rel="modulepreload" href="./dotnet.js" />
<link rel="modulepreload" href="./avalonia.js" />
</head>
<body>
<div id="out" tabindex="0"></div>
<script type='module' src="./main.js"></script>
<style>
canvas.avalonia-canvas {
opacity: 1;
background-color: lawngreen;
position: fixed;
width: 100vw;
height: 100vh;
top: 0px;
left: 0px;
z-index: 500;
}
</style>
<div id="out" tabindex="0"></div>
<script type='module' src="./main.js"></script>
</body>
</html>

12
src/Web/Avalonia.Web/AvaloniaRuntime.cs

@ -64,6 +64,18 @@ public partial class AvaloniaRuntime
[JSMarshalAs<JSType.Function>]
Action renderFrameCallback);
[JSImport("SizeWatcher.observe", "avalonia.ts")]
public static partial JSObject ObserveSize(
JSObject canvas,
string canvasId,
[JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
Action<int, int> onSizeChanged);
[JSImport("DpiWatcher.start", "avalonia.ts")]
public static partial double ObserveDpi(
[JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
Action<double, double> onDpiChanged);
[JSImport("StorageProvider.isFileApiSupported", "storage.ts")]
public static partial bool IsFileApiSupported();

50
src/Web/Avalonia.Web/AvaloniaView.cs

@ -36,8 +36,9 @@ namespace Avalonia.Web
private ElementReference _inputElement;
private ElementReference _containerElement;
private ElementReference _nativeControlsContainer;*/
private JSObject _canvas;
private double _dpi = 1;
private SKSize _canvasSize = new(100, 100);
private Size _canvasSize = new(100.0, 100.0);
private GRContext? _context;
private GRGlInterface? _glInterface;
@ -50,8 +51,8 @@ namespace Avalonia.Web
{
var div = GetElementById("out");
var canvas = CreateCanvas(div);
canvas.SetProperty("id", "mycanvas");
_canvas = CreateCanvas(div);
_canvas.SetProperty("id", "mycanvas");
_topLevelImpl = new RazorViewTopLevelImpl(this);
@ -66,26 +67,17 @@ namespace Avalonia.Web
(code, key, modifier) => _topLevelImpl.RawKeyboardEvent(Input.Raw.RawKeyEventType.KeyDown, code, key, (Input.RawInputModifiers)modifier),
(code, key, modifier) => _topLevelImpl.RawKeyboardEvent(Input.Raw.RawKeyEventType.KeyUp, code, key, (Input.RawInputModifiers)modifier));
//_interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame);
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
_dpi = ObserveDpi(OnDpiChanged);
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
_useGL = skiaOptions?.CustomGpuFactory != null;
Console.WriteLine($"Started observing dpi: {_dpi}");
if (_useGL)
{
_jsGlInfo = AvaloniaRuntime.InitialiseGL(canvas, OnRenderFrame);
Console.WriteLine("jsglinfo created - init gl");
}
else
{
throw new NotImplementedException();
//var rasterInitialized = _interop.InitRaster();
//Console.WriteLine("raster initialized: {0}", rasterInitialized);
}
_useGL = skiaOptions?.CustomGpuFactory != null;
if (_useGL)
{
_jsGlInfo = AvaloniaRuntime.InitialiseGL(_canvas, OnRenderFrame);
// create the SkiaSharp context
if (_context == null)
{
@ -93,25 +85,29 @@ namespace Avalonia.Web
_glInterface = GRGlInterface.Create();
_context = GRContext.CreateGl(_glInterface);
// bump the default resource cache limit
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
Console.WriteLine("glcontext created and resource limit set");
}
_topLevelImpl.Surfaces = new[] { new BlazorSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize(100, 100), 1, GRSurfaceOrigin.BottomLeft) };
_topLevelImpl.Surfaces = new[] { new BlazorSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) };
}
else
{
//var rasterInitialized = _interop.InitRaster();
//Console.WriteLine("raster initialized: {0}", rasterInitialized);
//_topLevelImpl.SetSurface(ColorType,
// new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
// new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
}
AvaloniaRuntime.SetCanvasSize(canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
AvaloniaRuntime.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
_topLevelImpl.SetClientSize(_canvasSize, _dpi);
RequestAnimationFrame(canvas, true);
ObserveSize(_canvas, "mycanvas", OnSizeChanged);
RequestAnimationFrame(_canvas, true);
}
private void OnRenderFrame()
{
@ -164,13 +160,13 @@ namespace Avalonia.Web
}
}
private void OnDpiChanged(double newDpi)
private void OnDpiChanged(double oldDpi, double newDpi)
{
if (Math.Abs(_dpi - newDpi) > 0.0001)
{
_dpi = newDpi;
//_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
_topLevelImpl.SetClientSize(_canvasSize, _dpi);
@ -178,13 +174,15 @@ namespace Avalonia.Web
}
}
private void OnSizeChanged(SKSize newSize)
private void OnSizeChanged(int height, int width)
{
var newSize = new Size(height, width);
if (_canvasSize != newSize)
{
_canvasSize = newSize;
//_interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
_topLevelImpl.SetClientSize(_canvasSize, _dpi);

15
src/Web/Avalonia.Web/RazorViewTopLevelImpl.cs

@ -21,7 +21,6 @@ namespace Avalonia.Web.Blazor
internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private Size _clientSize;
private IBlazorSkiaSurface? _currentSurface;
private IInputRoot? _inputRoot;
private readonly Stopwatch _sw = Stopwatch.StartNew();
private readonly AvaloniaView _avaloniaView;
@ -44,15 +43,13 @@ namespace Avalonia.Web.Blazor
public void SetClientSize(SKSize size, double dpi)
public void SetClientSize(Size newSize, double dpi)
{
var newSize = new Size(size.Width, size.Height);
if (Math.Abs(RenderScaling - dpi) > 0.0001)
{
if (_currentSurface is { })
if (Surfaces.FirstOrDefault() is BlazorSkiaSurface surface)
{
_currentSurface.Scaling = dpi;
surface.Scaling = dpi;
}
ScalingChanged?.Invoke(dpi);
@ -62,9 +59,9 @@ namespace Avalonia.Web.Blazor
{
_clientSize = newSize;
if (_currentSurface is { })
if (Surfaces.FirstOrDefault() is BlazorSkiaSurface surface)
{
_currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height);
surface.Size = new PixelSize((int)newSize.Width, (int)newSize.Height);
}
Resized?.Invoke(newSize, PlatformResizeReason.User);
@ -192,7 +189,7 @@ namespace Avalonia.Web.Blazor
public Size ClientSize => _clientSize;
public Size? FrameSize => null;
public double RenderScaling => _currentSurface?.Scaling ?? 1;
public double RenderScaling => (Surfaces.FirstOrDefault() as BlazorSkiaSurface)?.Scaling ?? 1;
public IEnumerable<object> Surfaces { get; set; }

110
src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts

@ -27,6 +27,7 @@ export class Canvas {
var canvas = document.createElement("canvas");
element.appendChild(canvas);
canvas.classList.add('avalonia-canvas');
return canvas;
}
@ -202,3 +203,112 @@ export class Canvas {
return ctx;
}
}
type SizeWatcherElement = {
SizeWatcher: SizeWatcherInstance;
} & HTMLElement
type SizeWatcherInstance = {
callback: (width: number, height: number) => void;
}
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map<string, HTMLElement>;
public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void) {
if (!element || !callback)
return;
//console.info(`Adding size watcher observation with callback ${callback._id}...`);
SizeWatcher.init();
const watcherElement = element as SizeWatcherElement;
watcherElement.SizeWatcher = {
callback: callback
};
SizeWatcher.elements.set(elementId, element);
SizeWatcher.observer.observe(element);
SizeWatcher.invoke(element);
}
public static unobserve(elementId: string) {
if (!elementId || !SizeWatcher.observer)
return;
//console.info('Removing size watcher observation...');
const element = SizeWatcher.elements.get(elementId)!;
SizeWatcher.elements.delete(elementId);
SizeWatcher.observer.unobserve(element);
}
static init() {
if (SizeWatcher.observer)
return;
//console.info('Starting size watcher...');
SizeWatcher.elements = new Map<string, HTMLElement>();
SizeWatcher.observer = new ResizeObserver((entries) => {
for (let entry of entries) {
SizeWatcher.invoke(entry.target);
}
});
}
static invoke(element: Element) {
const watcherElement = element as SizeWatcherElement;
const instance = watcherElement.SizeWatcher;
if (!instance || !instance.callback)
return;
return instance.callback(element.clientWidth, element.clientHeight);
}
}
export class DpiWatcher {
static lastDpi: number;
static timerId: number;
static callback: (old: number, newdpi: number) => void;
public static getDpi() {
return window.devicePixelRatio;
}
public static start(callback: (old: number, newdpi: number) => void) : number {
//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;
}
public 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(lastDpi, currentDpi);
}
}
}

6
src/Web/Avalonia.Web/webapp/modules/avalonia/runtime.ts

@ -1,5 +1,7 @@
import { RuntimeAPI } from "../../types/dotnet";
import { SizeWatcher } from "./canvas";
import { DpiWatcher } from "./canvas";
import { Canvas } from "./canvas";
import { InputHelper } from "./input";
@ -10,7 +12,9 @@ export class AvaloniaRuntime {
) {
api.setModuleImports("avalonia.ts", {
Canvas,
InputHelper
InputHelper,
SizeWatcher,
DpiWatcher
});
}
}

Loading…
Cancel
Save