21 changed files with 566 additions and 904 deletions
@ -1,41 +0,0 @@ |
|||||
|
|
||||
export class DpiWatcher { |
|
||||
static lastDpi: number; |
|
||||
static timerId: number; |
|
||||
static callback: DotNet.DotNetObjectReference; |
|
||||
|
|
||||
public static getDpi() { |
|
||||
return window.devicePixelRatio; |
|
||||
} |
|
||||
|
|
||||
public static start(callback: DotNet.DotNetObjectReference): 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.invokeMethod('Invoke', lastDpi, currentDpi); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,23 +0,0 @@ |
|||||
|
|
||||
export class InputHelper { |
|
||||
public static clear (inputElement: HTMLInputElement){ |
|
||||
inputElement.value = ""; |
|
||||
} |
|
||||
|
|
||||
public static focus (inputElement: HTMLInputElement){ |
|
||||
inputElement.focus(); |
|
||||
inputElement.setSelectionRange(0, 0); |
|
||||
} |
|
||||
|
|
||||
public static setCursor (inputElement: HTMLInputElement, kind: string) { |
|
||||
inputElement.style.cursor = kind; |
|
||||
} |
|
||||
|
|
||||
public static hide (inputElement: HTMLInputElement){ |
|
||||
inputElement.style.display = 'none'; |
|
||||
} |
|
||||
|
|
||||
public static show (inputElement: HTMLInputElement){ |
|
||||
inputElement.style.display = 'block'; |
|
||||
} |
|
||||
} |
|
||||
@ -1,261 +0,0 @@ |
|||||
// aliases for emscripten
|
|
||||
declare let GL: any; |
|
||||
declare let GLctx: WebGLRenderingContext; |
|
||||
declare let Module: EmscriptenModule; |
|
||||
|
|
||||
// container for gl info
|
|
||||
type SKGLViewInfo = { |
|
||||
context: WebGLRenderingContext | WebGL2RenderingContext | undefined; |
|
||||
fboId: number; |
|
||||
stencil: number; |
|
||||
sample: number; |
|
||||
depth: number; |
|
||||
} |
|
||||
|
|
||||
// alias for a potential skia html canvas
|
|
||||
type SKHtmlCanvasElement = { |
|
||||
SKHtmlCanvas: SKHtmlCanvas |
|
||||
} & HTMLCanvasElement |
|
||||
|
|
||||
export class SKHtmlCanvas { |
|
||||
static elements: Map<string, HTMLCanvasElement>; |
|
||||
|
|
||||
htmlCanvas: HTMLCanvasElement; |
|
||||
glInfo: SKGLViewInfo; |
|
||||
renderFrameCallback: DotNet.DotNetObjectReference; |
|
||||
renderLoopEnabled: boolean = false; |
|
||||
renderLoopRequest: number = 0; |
|
||||
newWidth: number; |
|
||||
newHeight: number; |
|
||||
|
|
||||
public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKGLViewInfo { |
|
||||
var view = SKHtmlCanvas.init(true, element, elementId, callback); |
|
||||
if (!view || !view.glInfo) |
|
||||
return null; |
|
||||
|
|
||||
return view.glInfo; |
|
||||
} |
|
||||
|
|
||||
public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): boolean { |
|
||||
var view = SKHtmlCanvas.init(false, element, elementId, callback); |
|
||||
if (!view) |
|
||||
return false; |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObjectReference): SKHtmlCanvas { |
|
||||
var htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas) { |
|
||||
console.error(`No canvas element was provided.`); |
|
||||
return null; |
|
||||
} |
|
||||
|
|
||||
if (!SKHtmlCanvas.elements) |
|
||||
SKHtmlCanvas.elements = new Map<string, HTMLCanvasElement>(); |
|
||||
SKHtmlCanvas.elements[elementId] = element; |
|
||||
|
|
||||
const view = new SKHtmlCanvas(useGL, element, callback); |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas = view; |
|
||||
|
|
||||
return view; |
|
||||
} |
|
||||
|
|
||||
public static deinit(elementId: string) { |
|
||||
if (!elementId) |
|
||||
return; |
|
||||
|
|
||||
const element = SKHtmlCanvas.elements[elementId]; |
|
||||
SKHtmlCanvas.elements.delete(elementId); |
|
||||
|
|
||||
const htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
|
||||
return; |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas.deinit(); |
|
||||
htmlCanvas.SKHtmlCanvas = undefined; |
|
||||
} |
|
||||
|
|
||||
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) { |
|
||||
const htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
|
||||
return; |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); |
|
||||
} |
|
||||
|
|
||||
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) |
|
||||
{ |
|
||||
const htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
|
||||
return; |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); |
|
||||
} |
|
||||
|
|
||||
public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { |
|
||||
const htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
|
||||
return; |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); |
|
||||
} |
|
||||
|
|
||||
public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) { |
|
||||
const htmlCanvas = element as SKHtmlCanvasElement; |
|
||||
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
|
||||
return; |
|
||||
|
|
||||
htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); |
|
||||
} |
|
||||
|
|
||||
public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObjectReference) { |
|
||||
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, // TODO: GLctx.getParameter(GLctx.SAMPLES)
|
|
||||
depth: GLctx.getParameter(GLctx.DEPTH_BITS), |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public deinit() { |
|
||||
this.setEnableRenderLoop(false); |
|
||||
} |
|
||||
|
|
||||
public setCanvasSize(width: number, height: number) |
|
||||
{ |
|
||||
this.newWidth = width; |
|
||||
this.newHeight = height; |
|
||||
|
|
||||
if(this.htmlCanvas.width != this.newWidth) |
|
||||
{ |
|
||||
this.htmlCanvas.width = this.newWidth; |
|
||||
} |
|
||||
|
|
||||
if(this.htmlCanvas.height != this.newHeight) |
|
||||
{ |
|
||||
this.htmlCanvas.height = this.newHeight; |
|
||||
} |
|
||||
|
|
||||
if (this.glInfo) { |
|
||||
// make current
|
|
||||
GL.makeContextCurrent(this.glInfo.context); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public requestAnimationFrame(renderLoop?: boolean) { |
|
||||
// optionally update the render loop
|
|
||||
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) |
|
||||
this.setEnableRenderLoop(renderLoop); |
|
||||
|
|
||||
// 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); |
|
||||
} |
|
||||
|
|
||||
if(this.htmlCanvas.width != this.newWidth) |
|
||||
{ |
|
||||
this.htmlCanvas.width = this.newWidth; |
|
||||
} |
|
||||
|
|
||||
if(this.htmlCanvas.height != this.newHeight) |
|
||||
{ |
|
||||
this.htmlCanvas.height = this.newHeight; |
|
||||
} |
|
||||
|
|
||||
this.renderFrameCallback.invokeMethod('Invoke'); |
|
||||
this.renderLoopRequest = 0; |
|
||||
|
|
||||
// we may want to draw the next frame
|
|
||||
if (this.renderLoopEnabled) |
|
||||
this.requestAnimationFrame(); |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
public setEnableRenderLoop(enable: boolean) { |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public putImageData(pData: number, width: number, height: number): boolean { |
|
||||
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: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext { |
|
||||
const contextAttributes = { |
|
||||
alpha: 1, |
|
||||
depth: 1, |
|
||||
stencil: 8, |
|
||||
antialias: 0, |
|
||||
premultipliedAlpha: 1, |
|
||||
preserveDrawingBuffer: 0, |
|
||||
preferLowPowerToHighPerformance: 0, |
|
||||
failIfMajorPerformanceCaveat: 0, |
|
||||
majorVersion: 2, |
|
||||
minorVersion: 0, |
|
||||
enableExtensionsByDefault: 1, |
|
||||
explicitSwapControl: 0, |
|
||||
renderViaOffscreenBackBuffer: 1, |
|
||||
}; |
|
||||
|
|
||||
let ctx: WebGLRenderingContext = 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; |
|
||||
} |
|
||||
} |
|
||||
@ -1,68 +0,0 @@ |
|||||
|
|
||||
type SizeWatcherElement = { |
|
||||
SizeWatcher: SizeWatcherInstance; |
|
||||
} & HTMLElement |
|
||||
|
|
||||
type SizeWatcherInstance = { |
|
||||
callback: DotNet.DotNetObjectReference; |
|
||||
} |
|
||||
|
|
||||
export class SizeWatcher { |
|
||||
static observer: ResizeObserver; |
|
||||
static elements: Map<string, HTMLElement>; |
|
||||
|
|
||||
public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObjectReference) { |
|
||||
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[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[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.invokeMethod('Invoke', element.clientWidth, element.clientHeight); |
|
||||
} |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
|
|
||||
declare namespace DotNet { |
|
||||
interface DotNetObjectReference extends DotNet.DotNetObject { |
|
||||
_id: number; |
|
||||
dispose(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,326 +0,0 @@ |
|||||
// Type definitions for Emscripten 1.39.16
|
|
||||
// Project: https://emscripten.org
|
|
||||
// Definitions by: Kensuke Matsuzaki <https://github.com/zakki>
|
|
||||
// Periklis Tsirakidis <https://github.com/periklis>
|
|
||||
// Bumsik Kim <https://github.com/kbumsik>
|
|
||||
// Louis DeScioli <https://github.com/lourd>
|
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
||||
// TypeScript Version: 2.2
|
|
||||
|
|
||||
/** Other WebAssembly declarations, for compatibility with older versions of Typescript */ |
|
||||
declare namespace WebAssembly { |
|
||||
interface Module {} |
|
||||
} |
|
||||
|
|
||||
declare namespace Emscripten { |
|
||||
interface FileSystemType {} |
|
||||
type EnvironmentType = 'WEB' | 'NODE' | 'SHELL' | 'WORKER'; |
|
||||
|
|
||||
type JSType = 'number' | 'string' | 'array' | 'boolean'; |
|
||||
type TypeCompatibleWithC = number | string | any[] | boolean; |
|
||||
|
|
||||
type CIntType = 'i8' | 'i16' | 'i32' | 'i64'; |
|
||||
type CFloatType = 'float' | 'double'; |
|
||||
type CPointerType = 'i8*' | 'i16*' | 'i32*' | 'i64*' | 'float*' | 'double*' | '*'; |
|
||||
type CType = CIntType | CFloatType | CPointerType; |
|
||||
|
|
||||
type WebAssemblyImports = Array<{ |
|
||||
name: string; |
|
||||
kind: string; |
|
||||
}>; |
|
||||
|
|
||||
type WebAssemblyExports = Array<{ |
|
||||
module: string; |
|
||||
name: string; |
|
||||
kind: string; |
|
||||
}>; |
|
||||
|
|
||||
interface CCallOpts { |
|
||||
async?: boolean | undefined; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
interface EmscriptenModule { |
|
||||
print(str: string): void; |
|
||||
printErr(str: string): void; |
|
||||
arguments: string[]; |
|
||||
environment: Emscripten.EnvironmentType; |
|
||||
preInit: Array<{ (): void }>; |
|
||||
preRun: Array<{ (): void }>; |
|
||||
postRun: Array<{ (): void }>; |
|
||||
onAbort: { (what: any): void }; |
|
||||
onRuntimeInitialized: { (): void }; |
|
||||
preinitializedWebGLContext: WebGLRenderingContext; |
|
||||
noInitialRun: boolean; |
|
||||
noExitRuntime: boolean; |
|
||||
logReadFiles: boolean; |
|
||||
filePackagePrefixURL: string; |
|
||||
wasmBinary: ArrayBuffer; |
|
||||
|
|
||||
destroy(object: object): void; |
|
||||
getPreloadedPackage(remotePackageName: string, remotePackageSize: number): ArrayBuffer; |
|
||||
instantiateWasm( |
|
||||
imports: Emscripten.WebAssemblyImports, |
|
||||
successCallback: (module: WebAssembly.Module) => void, |
|
||||
): Emscripten.WebAssemblyExports; |
|
||||
locateFile(url: string, scriptDirectory: string): string; |
|
||||
onCustomMessage(event: MessageEvent): void; |
|
||||
|
|
||||
// USE_TYPED_ARRAYS == 1
|
|
||||
HEAP: Int32Array; |
|
||||
IHEAP: Int32Array; |
|
||||
FHEAP: Float64Array; |
|
||||
|
|
||||
// USE_TYPED_ARRAYS == 2
|
|
||||
HEAP8: Int8Array; |
|
||||
HEAP16: Int16Array; |
|
||||
HEAP32: Int32Array; |
|
||||
HEAPU8: Uint8Array; |
|
||||
HEAPU16: Uint16Array; |
|
||||
HEAPU32: Uint32Array; |
|
||||
HEAPF32: Float32Array; |
|
||||
HEAPF64: Float64Array; |
|
||||
|
|
||||
TOTAL_STACK: number; |
|
||||
TOTAL_MEMORY: number; |
|
||||
FAST_MEMORY: number; |
|
||||
|
|
||||
addOnPreRun(cb: () => any): void; |
|
||||
addOnInit(cb: () => any): void; |
|
||||
addOnPreMain(cb: () => any): void; |
|
||||
addOnExit(cb: () => any): void; |
|
||||
addOnPostRun(cb: () => any): void; |
|
||||
|
|
||||
preloadedImages: any; |
|
||||
preloadedAudios: any; |
|
||||
|
|
||||
_malloc(size: number): number; |
|
||||
_free(ptr: number): void; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* A factory function is generated when setting the `MODULARIZE` build option |
|
||||
* to `1` in your Emscripten build. It return a Promise that resolves to an |
|
||||
* initialized, ready-to-call `EmscriptenModule` instance. |
|
||||
* |
|
||||
* By default, the factory function will be named `Module`. It's recommended to |
|
||||
* use the `EXPORT_ES6` option, in which the factory function will be the |
|
||||
* default export. If used without `EXPORT_ES6`, the factory function will be a |
|
||||
* global variable. You can rename the variable using the `EXPORT_NAME` build |
|
||||
* option. It's left to you to declare any global variables as needed in your |
|
||||
* application's types. |
|
||||
* @param moduleOverrides Default properties for the initialized module. |
|
||||
*/ |
|
||||
type EmscriptenModuleFactory<T extends EmscriptenModule = EmscriptenModule> = ( |
|
||||
moduleOverrides?: Partial<T>, |
|
||||
) => Promise<T>; |
|
||||
|
|
||||
declare namespace FS { |
|
||||
interface Lookup { |
|
||||
path: string; |
|
||||
node: FSNode; |
|
||||
} |
|
||||
|
|
||||
interface FSStream {} |
|
||||
interface FSNode {} |
|
||||
interface ErrnoError {} |
|
||||
|
|
||||
let ignorePermissions: boolean; |
|
||||
let trackingDelegate: any; |
|
||||
let tracking: any; |
|
||||
let genericErrors: any; |
|
||||
|
|
||||
//
|
|
||||
// paths
|
|
||||
//
|
|
||||
function lookupPath(path: string, opts: any): Lookup; |
|
||||
function getPath(node: FSNode): string; |
|
||||
|
|
||||
//
|
|
||||
// nodes
|
|
||||
//
|
|
||||
function isFile(mode: number): boolean; |
|
||||
function isDir(mode: number): boolean; |
|
||||
function isLink(mode: number): boolean; |
|
||||
function isChrdev(mode: number): boolean; |
|
||||
function isBlkdev(mode: number): boolean; |
|
||||
function isFIFO(mode: number): boolean; |
|
||||
function isSocket(mode: number): boolean; |
|
||||
|
|
||||
//
|
|
||||
// devices
|
|
||||
//
|
|
||||
function major(dev: number): number; |
|
||||
function minor(dev: number): number; |
|
||||
function makedev(ma: number, mi: number): number; |
|
||||
function registerDevice(dev: number, ops: any): void; |
|
||||
|
|
||||
//
|
|
||||
// core
|
|
||||
//
|
|
||||
function syncfs(populate: boolean, callback: (e: any) => any): void; |
|
||||
function syncfs(callback: (e: any) => any, populate?: boolean): void; |
|
||||
function mount(type: Emscripten.FileSystemType, opts: any, mountpoint: string): any; |
|
||||
function unmount(mountpoint: string): void; |
|
||||
|
|
||||
function mkdir(path: string, mode?: number): any; |
|
||||
function mkdev(path: string, mode?: number, dev?: number): any; |
|
||||
function symlink(oldpath: string, newpath: string): any; |
|
||||
function rename(old_path: string, new_path: string): void; |
|
||||
function rmdir(path: string): void; |
|
||||
function readdir(path: string): any; |
|
||||
function unlink(path: string): void; |
|
||||
function readlink(path: string): string; |
|
||||
function stat(path: string, dontFollow?: boolean): any; |
|
||||
function lstat(path: string): any; |
|
||||
function chmod(path: string, mode: number, dontFollow?: boolean): void; |
|
||||
function lchmod(path: string, mode: number): void; |
|
||||
function fchmod(fd: number, mode: number): void; |
|
||||
function chown(path: string, uid: number, gid: number, dontFollow?: boolean): void; |
|
||||
function lchown(path: string, uid: number, gid: number): void; |
|
||||
function fchown(fd: number, uid: number, gid: number): void; |
|
||||
function truncate(path: string, len: number): void; |
|
||||
function ftruncate(fd: number, len: number): void; |
|
||||
function utime(path: string, atime: number, mtime: number): void; |
|
||||
function open(path: string, flags: string, mode?: number, fd_start?: number, fd_end?: number): FSStream; |
|
||||
function close(stream: FSStream): void; |
|
||||
function llseek(stream: FSStream, offset: number, whence: number): any; |
|
||||
function read(stream: FSStream, buffer: ArrayBufferView, offset: number, length: number, position?: number): number; |
|
||||
function write( |
|
||||
stream: FSStream, |
|
||||
buffer: ArrayBufferView, |
|
||||
offset: number, |
|
||||
length: number, |
|
||||
position?: number, |
|
||||
canOwn?: boolean, |
|
||||
): number; |
|
||||
function allocate(stream: FSStream, offset: number, length: number): void; |
|
||||
function mmap( |
|
||||
stream: FSStream, |
|
||||
buffer: ArrayBufferView, |
|
||||
offset: number, |
|
||||
length: number, |
|
||||
position: number, |
|
||||
prot: number, |
|
||||
flags: number, |
|
||||
): any; |
|
||||
function ioctl(stream: FSStream, cmd: any, arg: any): any; |
|
||||
function readFile(path: string, opts: { encoding: 'binary'; flags?: string | undefined }): Uint8Array; |
|
||||
function readFile(path: string, opts: { encoding: 'utf8'; flags?: string | undefined }): string; |
|
||||
function readFile(path: string, opts?: { flags?: string | undefined }): Uint8Array; |
|
||||
function writeFile(path: string, data: string | ArrayBufferView, opts?: { flags?: string | undefined }): void; |
|
||||
|
|
||||
//
|
|
||||
// module-level FS code
|
|
||||
//
|
|
||||
function cwd(): string; |
|
||||
function chdir(path: string): void; |
|
||||
function init( |
|
||||
input: null | (() => number | null), |
|
||||
output: null | ((c: number) => any), |
|
||||
error: null | ((c: number) => any), |
|
||||
): void; |
|
||||
|
|
||||
function createLazyFile( |
|
||||
parent: string | FSNode, |
|
||||
name: string, |
|
||||
url: string, |
|
||||
canRead: boolean, |
|
||||
canWrite: boolean, |
|
||||
): FSNode; |
|
||||
function createPreloadedFile( |
|
||||
parent: string | FSNode, |
|
||||
name: string, |
|
||||
url: string, |
|
||||
canRead: boolean, |
|
||||
canWrite: boolean, |
|
||||
onload?: () => void, |
|
||||
onerror?: () => void, |
|
||||
dontCreateFile?: boolean, |
|
||||
canOwn?: boolean, |
|
||||
): void; |
|
||||
function createDataFile( |
|
||||
parent: string | FSNode, |
|
||||
name: string, |
|
||||
data: ArrayBufferView, |
|
||||
canRead: boolean, |
|
||||
canWrite: boolean, |
|
||||
canOwn: boolean, |
|
||||
): FSNode; |
|
||||
} |
|
||||
|
|
||||
declare var MEMFS: Emscripten.FileSystemType; |
|
||||
declare var NODEFS: Emscripten.FileSystemType; |
|
||||
declare var IDBFS: Emscripten.FileSystemType; |
|
||||
|
|
||||
// Below runtime function/variable declarations are exportable by
|
|
||||
// -s EXTRA_EXPORTED_RUNTIME_METHODS. You can extend or merge
|
|
||||
// EmscriptenModule interface to add runtime functions.
|
|
||||
//
|
|
||||
// For example, by using -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']"
|
|
||||
// You can access ccall() via Module["ccall"]. In this case, you should
|
|
||||
// extend EmscriptenModule to pass the compiler check like the following:
|
|
||||
//
|
|
||||
// interface YourOwnEmscriptenModule extends EmscriptenModule {
|
|
||||
// ccall: typeof ccall;
|
|
||||
// }
|
|
||||
//
|
|
||||
// See: https://emscripten.org/docs/getting_started/FAQ.html#why-do-i-get-typeerror-module-something-is-not-a-function
|
|
||||
|
|
||||
declare function ccall( |
|
||||
ident: string, |
|
||||
returnType: Emscripten.JSType | null, |
|
||||
argTypes: Emscripten.JSType[], |
|
||||
args: Emscripten.TypeCompatibleWithC[], |
|
||||
opts?: Emscripten.CCallOpts, |
|
||||
): any; |
|
||||
declare function cwrap( |
|
||||
ident: string, |
|
||||
returnType: Emscripten.JSType | null, |
|
||||
argTypes: Emscripten.JSType[], |
|
||||
opts?: Emscripten.CCallOpts, |
|
||||
): (...args: any[]) => any; |
|
||||
|
|
||||
declare function setValue(ptr: number, value: any, type: Emscripten.CType, noSafe?: boolean): void; |
|
||||
declare function getValue(ptr: number, type: Emscripten.CType, noSafe?: boolean): number; |
|
||||
|
|
||||
declare function allocate( |
|
||||
slab: number[] | ArrayBufferView | number, |
|
||||
types: Emscripten.CType | Emscripten.CType[], |
|
||||
allocator: number, |
|
||||
ptr?: number, |
|
||||
): number; |
|
||||
|
|
||||
declare function stackAlloc(size: number): number; |
|
||||
declare function stackSave(): number; |
|
||||
declare function stackRestore(ptr: number): void; |
|
||||
|
|
||||
declare function UTF8ToString(ptr: number, maxBytesToRead?: number): string; |
|
||||
declare function stringToUTF8(str: string, outPtr: number, maxBytesToRead?: number): void; |
|
||||
declare function lengthBytesUTF8(str: string): number; |
|
||||
declare function allocateUTF8(str: string): number; |
|
||||
declare function allocateUTF8OnStack(str: string): number; |
|
||||
declare function UTF16ToString(ptr: number): string; |
|
||||
declare function stringToUTF16(str: string, outPtr: number, maxBytesToRead?: number): void; |
|
||||
declare function lengthBytesUTF16(str: string): number; |
|
||||
declare function UTF32ToString(ptr: number): string; |
|
||||
declare function stringToUTF32(str: string, outPtr: number, maxBytesToRead?: number): void; |
|
||||
declare function lengthBytesUTF32(str: string): number; |
|
||||
|
|
||||
declare function intArrayFromString(stringy: string, dontAddNull?: boolean, length?: number): number[]; |
|
||||
declare function intArrayToString(array: number[]): string; |
|
||||
declare function writeStringToMemory(str: string, buffer: number, dontAddNull: boolean): void; |
|
||||
declare function writeArrayToMemory(array: number[], buffer: number): void; |
|
||||
declare function writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void; |
|
||||
|
|
||||
declare function addRunDependency(id: any): void; |
|
||||
declare function removeRunDependency(id: any): void; |
|
||||
|
|
||||
declare function addFunction(func: (...args: any[]) => any, signature?: string): number; |
|
||||
declare function removeFunction(funcPtr: number): void; |
|
||||
|
|
||||
declare var ALLOC_NORMAL: number; |
|
||||
declare var ALLOC_STACK: number; |
|
||||
declare var ALLOC_STATIC: number; |
|
||||
declare var ALLOC_DYNAMIC: number; |
|
||||
declare var ALLOC_NONE: number; |
|
||||
@ -1,14 +0,0 @@ |
|||||
{ |
|
||||
"compilerOptions": { |
|
||||
"noImplicitAny": false, |
|
||||
"noEmitOnError": true, |
|
||||
"removeComments": false, |
|
||||
"sourceMap": true, |
|
||||
"target": "ES2020", |
|
||||
"module": "ES2020", |
|
||||
"outDir": "wwwroot" |
|
||||
}, |
|
||||
"exclude": [ |
|
||||
"node_modules" |
|
||||
] |
|
||||
} |
|
||||
@ -0,0 +1 @@ |
|||||
|
|
||||
@ -0,0 +1,40 @@ |
|||||
|
export class DpiWatcher { |
||||
|
static lastDpi: number; |
||||
|
static timerId: number; |
||||
|
static callback?: DotNet.DotNetObject; |
||||
|
|
||||
|
public static getDpi() { |
||||
|
return window.devicePixelRatio; |
||||
|
} |
||||
|
|
||||
|
public static start(callback: DotNet.DotNetObject): 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.invokeMethod('Invoke', lastDpi, currentDpi); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,79 @@ |
|||||
|
class InnerDbConnection { |
||||
|
constructor(private database: IDBDatabase) { } |
||||
|
|
||||
|
private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore { |
||||
|
const tx = this.database.transaction(store, mode); |
||||
|
return tx.objectStore(store); |
||||
|
} |
||||
|
|
||||
|
public put(store: string, obj: any, key?: IDBValidKey): Promise<IDBValidKey> { |
||||
|
const os = this.openStore(store, "readwrite"); |
||||
|
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
const response = os.put(obj, key); |
||||
|
response.onsuccess = () => { |
||||
|
resolve(response.result); |
||||
|
}; |
||||
|
response.onerror = () => { |
||||
|
reject(response.error); |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public get(store: string, key: IDBValidKey): any { |
||||
|
const os = this.openStore(store, "readonly"); |
||||
|
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
const response = os.get(key); |
||||
|
response.onsuccess = () => { |
||||
|
resolve(response.result); |
||||
|
}; |
||||
|
response.onerror = () => { |
||||
|
reject(response.error); |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public delete(store: string, key: IDBValidKey): Promise<void> { |
||||
|
const os = this.openStore(store, "readwrite"); |
||||
|
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
const response = os.delete(key); |
||||
|
response.onsuccess = () => { |
||||
|
resolve(); |
||||
|
}; |
||||
|
response.onerror = () => { |
||||
|
reject(response.error); |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public close() { |
||||
|
this.database.close(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export class IndexedDbWrapper { |
||||
|
constructor(private databaseName: string, private objectStores: [string]) { |
||||
|
} |
||||
|
|
||||
|
public connect(): Promise<InnerDbConnection> { |
||||
|
const conn = window.indexedDB.open(this.databaseName, 1); |
||||
|
|
||||
|
conn.onupgradeneeded = event => { |
||||
|
const db = (<IDBRequest<IDBDatabase>>event.target).result; |
||||
|
this.objectStores.forEach(store => { |
||||
|
db.createObjectStore(store); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return new Promise((resolve, reject) => { |
||||
|
conn.onsuccess = event => { |
||||
|
resolve(new InnerDbConnection((<IDBRequest<IDBDatabase>>event.target).result)); |
||||
|
}; |
||||
|
conn.onerror = event => { |
||||
|
reject((<IDBRequest<IDBDatabase>>event.target).error); |
||||
|
}; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
export class InputHelper { |
||||
|
public static clear(inputElement: HTMLInputElement) { |
||||
|
inputElement.value = ""; |
||||
|
} |
||||
|
|
||||
|
public static focus(inputElement: HTMLInputElement) { |
||||
|
inputElement.focus(); |
||||
|
inputElement.setSelectionRange(0, 0); |
||||
|
} |
||||
|
|
||||
|
public static setCursor(inputElement: HTMLInputElement, kind: string) { |
||||
|
inputElement.style.cursor = kind; |
||||
|
} |
||||
|
|
||||
|
public static hide(inputElement: HTMLInputElement) { |
||||
|
inputElement.style.display = 'none'; |
||||
|
} |
||||
|
|
||||
|
public static show(inputElement: HTMLInputElement) { |
||||
|
inputElement.style.display = 'block'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,255 @@ |
|||||
|
// aliases for emscripten
|
||||
|
declare let GL: any; |
||||
|
declare let GLctx: WebGLRenderingContext; |
||||
|
declare let Module: EmscriptenModule; |
||||
|
|
||||
|
// container for gl info
|
||||
|
type SKGLViewInfo = { |
||||
|
context: WebGLRenderingContext | WebGL2RenderingContext | undefined; |
||||
|
fboId: number; |
||||
|
stencil: number; |
||||
|
sample: number; |
||||
|
depth: number; |
||||
|
} |
||||
|
|
||||
|
// alias for a potential skia html canvas
|
||||
|
type SKHtmlCanvasElement = { |
||||
|
SKHtmlCanvas: SKHtmlCanvas | undefined |
||||
|
} & HTMLCanvasElement |
||||
|
|
||||
|
export class SKHtmlCanvas { |
||||
|
static elements: Map<string, HTMLCanvasElement>; |
||||
|
|
||||
|
htmlCanvas: HTMLCanvasElement; |
||||
|
glInfo?: SKGLViewInfo; |
||||
|
renderFrameCallback: DotNet.DotNetObject; |
||||
|
renderLoopEnabled: boolean = false; |
||||
|
renderLoopRequest: number = 0; |
||||
|
newWidth?: number; |
||||
|
newHeight?: number; |
||||
|
|
||||
|
public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKGLViewInfo | null { |
||||
|
var view = SKHtmlCanvas.init(true, element, elementId, callback); |
||||
|
if (!view || !view.glInfo) |
||||
|
return null; |
||||
|
|
||||
|
return view.glInfo; |
||||
|
} |
||||
|
|
||||
|
public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): boolean { |
||||
|
var view = SKHtmlCanvas.init(false, element, elementId, callback); |
||||
|
if (!view) |
||||
|
return false; |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKHtmlCanvas | null { |
||||
|
var htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas) { |
||||
|
console.error(`No canvas element was provided.`); |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
if (!SKHtmlCanvas.elements) |
||||
|
SKHtmlCanvas.elements = new Map<string, HTMLCanvasElement>(); |
||||
|
SKHtmlCanvas.elements.set(elementId, element); |
||||
|
|
||||
|
const view = new SKHtmlCanvas(useGL, element, callback); |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas = view; |
||||
|
|
||||
|
return view; |
||||
|
} |
||||
|
|
||||
|
public static deinit(elementId: string) { |
||||
|
if (!elementId) |
||||
|
return; |
||||
|
|
||||
|
const element = SKHtmlCanvas.elements.get(elementId); |
||||
|
SKHtmlCanvas.elements.delete(elementId); |
||||
|
|
||||
|
const htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
||||
|
return; |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas.deinit(); |
||||
|
htmlCanvas.SKHtmlCanvas = undefined; |
||||
|
} |
||||
|
|
||||
|
public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) { |
||||
|
const htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
||||
|
return; |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop); |
||||
|
} |
||||
|
|
||||
|
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) { |
||||
|
const htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
||||
|
return; |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height); |
||||
|
} |
||||
|
|
||||
|
public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) { |
||||
|
const htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
||||
|
return; |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable); |
||||
|
} |
||||
|
|
||||
|
public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) { |
||||
|
const htmlCanvas = element as SKHtmlCanvasElement; |
||||
|
if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas) |
||||
|
return; |
||||
|
|
||||
|
htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height); |
||||
|
} |
||||
|
|
||||
|
public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObject) { |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
// 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, // TODO: GLctx.getParameter(GLctx.SAMPLES)
|
||||
|
depth: GLctx.getParameter(GLctx.DEPTH_BITS), |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public deinit() { |
||||
|
this.setEnableRenderLoop(false); |
||||
|
} |
||||
|
|
||||
|
public setCanvasSize(width: number, height: number) { |
||||
|
this.newWidth = width; |
||||
|
this.newHeight = height; |
||||
|
|
||||
|
if (this.htmlCanvas.width != this.newWidth) { |
||||
|
this.htmlCanvas.width = this.newWidth; |
||||
|
} |
||||
|
|
||||
|
if (this.htmlCanvas.height != this.newHeight) { |
||||
|
this.htmlCanvas.height = this.newHeight; |
||||
|
} |
||||
|
|
||||
|
if (this.glInfo) { |
||||
|
// make current
|
||||
|
GL.makeContextCurrent(this.glInfo.context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public requestAnimationFrame(renderLoop?: boolean) { |
||||
|
// optionally update the render loop
|
||||
|
if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop) |
||||
|
this.setEnableRenderLoop(renderLoop); |
||||
|
|
||||
|
// 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); |
||||
|
} |
||||
|
|
||||
|
if (this.htmlCanvas.width != this.newWidth) { |
||||
|
this.htmlCanvas.width = this.newWidth || 0; |
||||
|
} |
||||
|
|
||||
|
if (this.htmlCanvas.height != this.newHeight) { |
||||
|
this.htmlCanvas.height = this.newHeight || 0; |
||||
|
} |
||||
|
|
||||
|
this.renderFrameCallback.invokeMethod('Invoke'); |
||||
|
this.renderLoopRequest = 0; |
||||
|
|
||||
|
// we may want to draw the next frame
|
||||
|
if (this.renderLoopEnabled) |
||||
|
this.requestAnimationFrame(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public setEnableRenderLoop(enable: boolean) { |
||||
|
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; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public putImageData(pData: number, width: number, height: number): boolean { |
||||
|
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: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext { |
||||
|
const contextAttributes = { |
||||
|
alpha: 1, |
||||
|
depth: 1, |
||||
|
stencil: 8, |
||||
|
antialias: 0, |
||||
|
premultipliedAlpha: 1, |
||||
|
preserveDrawingBuffer: 0, |
||||
|
preferLowPowerToHighPerformance: 0, |
||||
|
failIfMajorPerformanceCaveat: 0, |
||||
|
majorVersion: 2, |
||||
|
minorVersion: 0, |
||||
|
enableExtensionsByDefault: 1, |
||||
|
explicitSwapControl: 0, |
||||
|
renderViaOffscreenBackBuffer: 1, |
||||
|
}; |
||||
|
|
||||
|
let ctx: WebGLRenderingContext = 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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
type SizeWatcherElement = { |
||||
|
SizeWatcher: SizeWatcherInstance; |
||||
|
} & HTMLElement |
||||
|
|
||||
|
type SizeWatcherInstance = { |
||||
|
callback: DotNet.DotNetObject; |
||||
|
} |
||||
|
|
||||
|
export class SizeWatcher { |
||||
|
static observer: ResizeObserver; |
||||
|
static elements: Map<string, HTMLElement>; |
||||
|
|
||||
|
public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObject) { |
||||
|
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.invokeMethod('Invoke', element.clientWidth, element.clientHeight); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
{ |
||||
|
"name": "avalonia.web", |
||||
|
"scripts": { |
||||
|
"build": "tsc --build", |
||||
|
"clean": "tsc --build --clean" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/emscripten": "^1.39.6", |
||||
|
"@types/wicg-file-system-access": "^2020.9.5", |
||||
|
"typescript": "^4.7.4", |
||||
|
"webpack": "^5.73.0", |
||||
|
"webpack-cli": "^4.10.0" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "ES6", |
||||
|
"strict": true, |
||||
|
"sourceMap": true, |
||||
|
"outDir": "../wwwroot", |
||||
|
"removeComments": true, |
||||
|
"noEmitOnError": true, |
||||
|
"lib": [ |
||||
|
"dom", |
||||
|
"ES6", |
||||
|
"esnext.asynciterable" |
||||
|
] |
||||
|
}, |
||||
|
"exclude": [ |
||||
|
"node_modules" |
||||
|
] |
||||
|
} |
||||
Loading…
Reference in new issue