Skip to content

Commit

Permalink
feat(wasm-api-dom): extend API & types, add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Oct 3, 2022
1 parent cb51bf6 commit 27fc6d6
Show file tree
Hide file tree
Showing 7 changed files with 588 additions and 109 deletions.
54 changes: 49 additions & 5 deletions packages/wasm-api-dom/include/api.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Generated by @thi.ng/wasm-api at 2022-10-03T00:05:37.977Z - DO NOT EDIT!
//! Generated by @thi.ng/wasm-api at 2022-10-03T15:17:25.199Z - DO NOT EDIT!

const std = @import("std");

pub const EventType = enum(i32) {
UNKOWN = -1,
Expand All @@ -12,6 +14,13 @@ pub const EventType = enum(i32) {
RESIZE,
};

pub const MouseButton = enum(u8) {
NONE,
PRIMARY = 1,
SECONDARY = 2,
MIDDLE = 4,
};

pub const KeyModifier = enum(u8) {
SHIFT = 1,
CTRL = 2,
Expand All @@ -26,18 +35,45 @@ pub const WindowInfo = struct {
};

pub const Event = struct {
target: i32,
clientX: u16 = 0,
clientY: u16 = 0,
type: EventType,
/// Target element ID, positive if a known element, otherwise:
///
/// - 0: document.body
/// - -1: window
/// - -2: = unknown
target: i32,
/// Mouse/touch X position in the local space of the element's bounding rect
clientX: i16 = 0,
/// Mouse/touch Y position in the local space of the element's bounding rect
clientY: i16 = 0,
/// WHEEL event only, scroll X delta
deltaX: i16 = 0,
/// WHEEL event only, scroll Y delta
deltaY: i16 = 0,
/// Encoded bitmask of currently pressed mouse buttons, see `MouseButton` enum
buttons: u8 = 0,
/// Encoded bitmask of currently pressed modifier keys, see `KeyModifier` enum
modifiers: u8 = 0,
key: [8]u8,
/// Value/name of the key pressed
key: [8:0]u8,

pub fn getKey(self: *const Event) []const u8 {
return self.key[0..std.mem.indexOfSentinel(u8, 0, &self.key)];
}

};

pub const CreateElementOpts = struct {
/// DOM element name
tag: []const u8,
/// ID attrib
id: []const u8 = "",
/// Element class attrib
class: []const u8 = "",
/// Element inner text body
text: []const u8 = "",
/// Element inner HTML body
html: []const u8 = "",
/// Parent element ID. If >=0 the new element will be attached to that parent
/// element. Set to -1 to leave new element unattached
parent: i32,
Expand All @@ -46,11 +82,19 @@ pub const CreateElementOpts = struct {
};

pub const CreateCanvasOpts = struct {
/// Canvas width (in CSS pixels)
width: u16,
/// Canvas height (in CSS pixels)
height: u16,
/// Element ID attrib
id: []const u8 = "",
/// Element class attrib
class: []const u8 = "",
/// Same as CreateElementOpts.parent
parent: i32,
/// Same as CreateElementOpts.index
index: i32 = -1,
/// Device pixel ratio for computing physical pixel dimensions, see
/// `getWindowInfo()`
dpr: u8 = 1,
};
67 changes: 53 additions & 14 deletions packages/wasm-api-dom/include/dom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@ const dom = @import("api.zig");

pub usingnamespace dom;

/// DOM event listener function
pub const EventListener = *const fn (event: *const dom.Event) void;

/// Reserved reference handle for the browser window itself (e.g. used for event targets)
pub const WINDOW: i32 = -1;
/// Reserved reference handle for `document.body`
pub const DOC_BODY: i32 = 0;

pub const DOMError = error{
InvalidID,
};

var listeners: std.ArrayList(?EventListener) = undefined;
var nextListenerID: i32 = -1;

pub fn init(allocator: std.mem.Allocator) anyerror!void {
listeners = std.ArrayList(?EventListener).init(allocator);
}

pub extern "dom" fn getWindowInfo(desc: *dom.WindowInfo) void;

pub extern "dom" fn createElement(opts: *const dom.CreateElementOpts) i32;

pub extern "dom" fn removeElement(id: i32) bool;

pub extern "dom" fn createCanvas(opts: *const dom.CreateCanvasOpts) i32;

pub extern "dom" fn setCanvasSize(id: i32, width: u16, height: u16, dpr: u8) void;
Expand All @@ -19,12 +38,24 @@ pub fn setStringAttrib(id: i32, name: []const u8, val: []const u8) void {
_setStringAttrib(id, name.ptr, val.ptr);
}

pub extern "dom" fn _setNumericAttrib(id: i32, name: [*]const u8, val: f32) void;
pub extern "dom" fn _getStringAttrib(id: i32, name: [*]const u8, val: [*]u8, maxBytes: usize) usize;

pub fn getStringAttrib(id: i32, name: []const u8, val: []u8) []u8 {
return val[0.._getStringAttrib(id, name.ptr, val.ptr, val.len)];
}

pub extern "dom" fn _setNumericAttrib(id: i32, name: [*]const u8, val: f64) void;

pub fn setNumericAttrib(id: i32, name: []const u8, val: f32) void {
pub fn setNumericAttrib(id: i32, name: []const u8, val: f64) void {
_setNumericAttrib(id, name.ptr, val);
}

pub extern "dom" fn _getNumericAttrib(id: i32, name: [*]const u8) f64;

pub fn getNumericAttrib(id: i32, name: []const u8) f64 {
return _getNumericAttrib(id, name.ptr);
}

pub extern "dom" fn _setInnerHtml(id: i32, ptr: [*]const u8) void;

pub fn setInnerHtml(id: i32, tag: []const u8) void {
Expand All @@ -37,24 +68,32 @@ pub fn setInnerText(id: i32, tag: []const u8) void {
_setInnerText(id, tag.ptr);
}

var listeners: std.ArrayList(EventListener) = undefined;
var nextListenerID: i32 = -1;

pub fn init(allocator: std.mem.Allocator) anyerror!void {
listeners = std.ArrayList(EventListener).init(allocator);
export fn dom_callListener(listenerID: u32, event: *const dom.Event) void {
if (listeners.items[@as(u32, listenerID)]) |listener| listener(event);
}

export fn dom_callListener(id: u32, event: *const dom.Event) void {
listeners.items[@as(u32, id)](event);
}
pub extern "dom" fn _addListener(id: i32, name: [*]const u8, listenerID: i32) void;

pub extern "dom" fn _addListener(ctx: i32, name: [*]const u8, listenerID: i32) void;

pub fn addListener(ctx: i32, name: []const u8, listener: EventListener) i32 {
pub fn addListener(id: i32, name: []const u8, listener: EventListener) i32 {
nextListenerID += 1;
listeners.append(listener) catch return -1;
_addListener(ctx, name.ptr, nextListenerID);
_addListener(id, name.ptr, nextListenerID);
return nextListenerID;
}

pub extern "dom" fn _removeListener(listenerID: i32) void;

pub fn removeListener(listenerID: i32) void {
if (listeners.items[listenerID]) {
_removeListener(listenerID);
listeners.items[listenerID] = null;
}
}

/// calls .preventDefault() on currently processed event
/// (only to be called from an EventListener!)
pub extern "dom" fn preventDefault() void;

/// calls .stopImmediatePropagation() on currently processed event
/// (only to be called from an EventListener!)
pub extern "dom" fn stopImmediatePropagation() void;
2 changes: 1 addition & 1 deletion packages/wasm-api-dom/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"license": "Apache-2.0",
"scripts": {
"build": "yarn clean && tsc --declaration",
"build:types": "npx wasm-api --lang ts -o src/generated/api.ts --lang zig -o include/api.zig typedefs.json",
"build:types": "npx wasm-api -a analytics.json --lang ts -o src/generated/api.ts --lang zig -o include/api.zig typedefs.json",
"clean": "rimraf '*.js' '*.d.ts' '*.map' doc",
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
Expand Down
140 changes: 125 additions & 15 deletions packages/wasm-api-dom/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,147 @@ import type { WasmExports } from "@thi.ng/wasm-api";
export * from "./generated/api.js";

export interface DOMExports extends WasmExports {
dom_callListener: (listenerID: number, event: number) => void;
dom_callListener(listenerID: number, event: number): void;
}

export interface DOMImports extends WebAssembly.ModuleImports {
getWindowInfo: (ptr: number) => void;
/**
* Queries the browser's current `window` object and writes results to given
* {@link WindowInfo} pointer.
*
* @param infoAddr
*/
getWindowInfo(infoAddr: number): void;

createElement: (optsAddr: number) => number;
/**
* Takes a {@link CreateElementOpts} pointer and creates a new DOM element
* according to the given opts. The created element is indexed and a unique
* ID handle returned.
*
* @remarks
* The DOM element created will NOT be garbage collected unless explicitly
* (or implicitly via ancestors) removed via
* {@link DOMImports.removeElement}.
*
* @param optsAddr
*/
createElement(optsAddr: number): number;

createCanvas: (optsAddr: number) => number;
/**
* Removes DOM element for given ID and all of its known children.
*
* @remarks
* IMPORTANT: Currently there's no way to automatically garbage collect
* WASM-side event listeners (i.e. those registered via
* {@link DOMImports._addListener}) attached to the element or anywhere in
* its sub-tree. These must be manually removed via
* {@link DOMImports._removeListener} prior to calling this function.
*
* @param elementID
*/
removeElement(elementID: number): void;

setCanvasSize: (
/**
* Takes a {@link CreateCanvasOpts} pointer and creates a new canvas element
* according to the given opts. Returns unique ID handle for new element,
* see {@link DOMImports.createElement} for details.
*
* @param optsAddr
*/
createCanvas(optsAddr: number): number;

/**
* Resizes the canvas element for given `elementID` to provided size (in CSS
* pixels). The given device pixel ratio `dpr` will be used to adjust the
* actual number of physical pixels. See
* [thi.ng/adapt-dpi](https://thi.ng/adapt-dpi) for reference.
*
* @param elementID
* @param width
* @param height
* @param dpr
*/
setCanvasSize(
elementID: number,
width: number,
height: number,
dpr: number
) => void;
): void;

_setStringAttrib: (
/**
* Sets attribute for given element to new string value. Both `nameAddr` and
* `valAddr` are zero-terminated char pointers (or standard Zig `[]u8` /
* `[]const u8` slices).
*
* @param elementID
* @param nameAddr
* @param valAddr
*/
_setStringAttrib(
elementID: number,
nameAddr: number,
valAddr: number
) => void;
_setNumericAttrib: (
): void;

/**
* Reads a string attribute value from DOM element and writes it
* zero-terminated to char pointer `valAddr`. Only `maxBytes` are written.
* Returns actual number of bytes written (excluding the sentinel).
*
* @param elementID
* @param nameAddr
* @param valAddr
* @param maxBytes
*/
_getStringAttrib(
elementID: number,
nameAddr: number,
val: number
) => void;
valAddr: number,
maxBytes: number
): number;

/**
* Sets attribute for given element to new f64 value. `nameAddr` is a
* zero-terminated char pointer (or standard Zig `[]u8` / `[]const u8`
* slices).
*
* @param elementID
* @param nameAddr
* @param val
*/
_setNumericAttrib(elementID: number, nameAddr: number, val: number): void;

/**
* Reads a numeric attribute value from DOM element and returns it as f64.
*
* @param elementID
* @param nameAddr
*/
_getNumericAttrib(elementID: number, nameAddr: number): number;

_addListener: (ctxID: number, name: number, listenerID: number) => void;
_removeListener: (listenerID: number) => void;
/**
* Attaches a new DOM event listener for given event name to element (or
* window) given by `ctxID`. The `listenerID` is managed by the Zig
* counterpart of this API.
*
* @remarks
* The following context IDs are special:
*
* - -1: `window`
* - 0: `document.body`
*
* @param ctxID
* @param nameAddr
* @param listenerID
*/
_addListener(ctxID: number, nameAddr: number, listenerID: number): void;
/**
* Removes DOM event listener for given listener ID.
*
* @param listenerID
*/
_removeListener(listenerID: number): void;

_setInnerHtml: (elementID: number, body: number) => void;
_setInnerText: (elementID: number, body: number) => void;
_setInnerHtml(elementID: number, body: number): void;
_setInnerText(elementID: number, body: number): void;
}
Loading

0 comments on commit 27fc6d6

Please sign in to comment.