558 lines
25 KiB
JavaScript
558 lines
25 KiB
JavaScript
/*
|
|
Crafter®.Graphics
|
|
Copyright (C) 2026 Catcrafts®
|
|
catcrafts.net
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License version 3.0 as published by the Free Software Foundation;
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with this library; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
// JS bridge for the CRAFTER_GRAPHICS_WINDOW_DOM build of Crafter.Graphics.
|
|
// Populates `window.crafter_webbuild_env` (same global as Crafter.CppDOM
|
|
// used) with the env imports the .wasm declares. Crafter.Build's
|
|
// runtime.js merges this object into the WebAssembly import object as
|
|
// the `env` module before instantiation.
|
|
//
|
|
// Three groups of imports:
|
|
// 1. DOM ops + 23 element-scoped listener kinds (port of CppDOM env.js,
|
|
// plus createElement / getBody for the new element-creation API).
|
|
// 2. Window-level event hookup — DOM events delivered to a global
|
|
// Window object on the C++ side. Set up when the C++ Window ctor
|
|
// calls domAttachWindow().
|
|
// 3. requestAnimationFrame driver — domStartFrameLoop / domStopFrameLoop.
|
|
|
|
const __decoder = new TextDecoder();
|
|
const __encoder = new TextEncoder();
|
|
|
|
// jsmemory: integer cookie → live DOM element. Counter is monotone;
|
|
// 32-bit wrap is unlikely over a single page's lifetime but if it
|
|
// ever matters we'd switch to a free-list.
|
|
let __cookieCounter = 0;
|
|
const __jsmemory = new Map();
|
|
const __listenerHandlers = new Map(); // `${ptr}-${id}-${kind}` → JS handler
|
|
|
|
// Window-level (document / window) listeners installed by domAttachWindow.
|
|
// Indexed by event kind; each entry is the JS handler we'd remove if the
|
|
// user ever destroyed the Window. V1 only ever has one Window so we don't
|
|
// support tear-down; the listeners live for the page's lifetime.
|
|
const __windowListeners = {};
|
|
let __windowAttachedHandle = 0; // cookie of the C++ Window for export dispatch
|
|
let __frameLoopActive = false;
|
|
|
|
function __wasm() { return window.crafter_webbuild_wasi.instance.exports; }
|
|
function __memBuf() { return window.crafter_webbuild_wasi.instance.exports.memory.buffer; }
|
|
|
|
function __readUtf8(ptr, len) {
|
|
return __decoder.decode(new Uint8Array(__memBuf(), ptr, len));
|
|
}
|
|
function __writeUtf8(str) {
|
|
const encoded = __encoder.encode(str + '\0');
|
|
const ptr = __wasm().WasmAlloc(encoded.length);
|
|
new Uint8Array(__memBuf(), ptr, encoded.length).set(encoded);
|
|
return ptr;
|
|
}
|
|
function __storeElement(el) {
|
|
__jsmemory.set(++__cookieCounter, el);
|
|
return __cookieCounter;
|
|
}
|
|
|
|
// ─── DOM lookup / creation ────────────────────────────────────────────
|
|
|
|
function getElementById(idPtr, idLen) {
|
|
try {
|
|
const id = __readUtf8(idPtr, idLen);
|
|
const el = document.getElementById(id);
|
|
if (!el) {
|
|
console.error(`Crafter.Dom: getElementById("${id}") → null`);
|
|
return 0;
|
|
}
|
|
return __storeElement(el);
|
|
} catch (err) {
|
|
console.error(err);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function createElement(parentCookie, tagPtr, tagLen, idPtr, idLen) {
|
|
try {
|
|
const tag = __readUtf8(tagPtr, tagLen);
|
|
const el = document.createElement(tag);
|
|
if (idLen > 0) {
|
|
el.id = __readUtf8(idPtr, idLen);
|
|
}
|
|
const parent = __jsmemory.get(parentCookie);
|
|
if (parent) {
|
|
parent.appendChild(el);
|
|
} else {
|
|
// parent cookie 0 / not-found → append to body so the element
|
|
// still ends up in the DOM (matches CreateInBody semantics
|
|
// when the user passes a moved-from / default-constructed
|
|
// HtmlElementPtr).
|
|
document.body.appendChild(el);
|
|
}
|
|
return __storeElement(el);
|
|
} catch (err) {
|
|
console.error(err);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
function getBody() {
|
|
return __storeElement(document.body);
|
|
}
|
|
|
|
function freeJs(cookie) {
|
|
__jsmemory.delete(cookie);
|
|
}
|
|
|
|
// ─── DOM mutations ────────────────────────────────────────────────────
|
|
|
|
function setInnerHTML(cookie, htmlPtr, htmlLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.innerHTML = __readUtf8(htmlPtr, htmlLen);
|
|
}
|
|
function setStyle(cookie, stylePtr, styleLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.style.cssText = __readUtf8(stylePtr, styleLen);
|
|
}
|
|
function setProperty(cookie, propPtr, propLen, valPtr, valLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.style.setProperty(__readUtf8(propPtr, propLen), __readUtf8(valPtr, valLen));
|
|
}
|
|
function addClass(cookie, namePtr, nameLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.classList.add(__readUtf8(namePtr, nameLen));
|
|
}
|
|
function removeClass(cookie, namePtr, nameLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.classList.remove(__readUtf8(namePtr, nameLen));
|
|
}
|
|
function toggleClass(cookie, namePtr, nameLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.classList.toggle(__readUtf8(namePtr, nameLen));
|
|
}
|
|
function hasClass(cookie, namePtr, nameLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (!el) return false;
|
|
return el.classList.contains(__readUtf8(namePtr, nameLen));
|
|
}
|
|
function deleteElement(cookie) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el && el.parentNode) el.parentNode.removeChild(el);
|
|
}
|
|
function getValue(cookie) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (!el || el.value === undefined) return 0;
|
|
return __writeUtf8(el.value || "");
|
|
}
|
|
function setValue(cookie, valPtr, valLen) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (el) el.value = __readUtf8(valPtr, valLen);
|
|
}
|
|
|
|
// ─── Element-scoped event listeners ───────────────────────────────────
|
|
//
|
|
// Each Add*Listener factory generates an addEventListener call whose
|
|
// handler marshals the event fields through to a specific C++ export.
|
|
// We key the handler in __listenerHandlers by `${cookie}-${id}-${kind}`
|
|
// so removeEventListener can re-find it. C++-side handler id counters
|
|
// are per-kind, so a per-kind suffix is what makes the keys unique.
|
|
|
|
function __makeMouseListenerPair(kind, eventName, exportName) {
|
|
return {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (!el) return;
|
|
const handler = (event) => {
|
|
__wasm()[exportName](id,
|
|
event.clientX, event.clientY,
|
|
event.screenX, event.screenY,
|
|
event.button, event.buttons,
|
|
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-${kind}`, handler);
|
|
el.addEventListener(eventName, handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-${kind}`;
|
|
const handler = __listenerHandlers.get(key);
|
|
if (el && handler) el.removeEventListener(eventName, handler);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
}
|
|
function __makeKeyListenerPair(kind, eventName, exportName) {
|
|
return {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (!el) return;
|
|
const handler = (event) => {
|
|
const keyPtr = __writeUtf8(event.key || "");
|
|
__wasm()[exportName](id, keyPtr, event.keyCode,
|
|
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
|
__wasm().WasmFree(keyPtr);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-${kind}`, handler);
|
|
el.addEventListener(eventName, handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-${kind}`;
|
|
const handler = __listenerHandlers.get(key);
|
|
if (el && handler) el.removeEventListener(eventName, handler);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
}
|
|
|
|
const __mouse_Click = __makeMouseListenerPair("click", "click", "ExecuteClickHandler");
|
|
const __mouse_MouseOver = __makeMouseListenerPair("mouseover", "mouseover", "ExecuteMouseOverHandler");
|
|
const __mouse_MouseOut = __makeMouseListenerPair("mouseout", "mouseout", "ExecuteMouseOutHandler");
|
|
const __mouse_MouseMove = __makeMouseListenerPair("mousemove", "mousemove", "ExecuteMouseMoveHandler");
|
|
const __mouse_MouseDown = __makeMouseListenerPair("mousedown", "mousedown", "ExecuteMouseDownHandler");
|
|
const __mouse_MouseUp = __makeMouseListenerPair("mouseup", "mouseup", "ExecuteMouseUpHandler");
|
|
const __mouse_ContextMenu = __makeMouseListenerPair("contextmenu", "contextmenu", "ExecuteContextMenuHandler");
|
|
const __mouse_DragStart = __makeMouseListenerPair("dragstart", "dragstart", "ExecuteDragStartHandler");
|
|
const __mouse_DragEnd = __makeMouseListenerPair("dragend", "dragend", "ExecuteDragEndHandler");
|
|
const __mouse_Drop = __makeMouseListenerPair("drop", "drop", "ExecuteDropHandler");
|
|
const __mouse_DragOver = __makeMouseListenerPair("dragover", "dragover", "ExecuteDragOverHandler");
|
|
const __mouse_DragEnter = __makeMouseListenerPair("dragenter", "dragenter", "ExecuteDragEnterHandler");
|
|
const __mouse_DragLeave = __makeMouseListenerPair("dragleave", "dragleave", "ExecuteDragLeaveHandler");
|
|
const __key_KeyDown = __makeKeyListenerPair("keydown", "keydown", "ExecuteKeyDownHandler");
|
|
const __key_KeyUp = __makeKeyListenerPair("keyup", "keyup", "ExecuteKeyUpHandler");
|
|
const __key_KeyPress = __makeKeyListenerPair("keypress", "keypress", "ExecuteKeyPressHandler");
|
|
|
|
function __makeFocusListenerPair(kind, eventName, exportName) {
|
|
return {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
if (!el) return;
|
|
const handler = (event) => {
|
|
const tCookie = event.target ? __storeElement(event.target) : 0;
|
|
const rCookie = event.relatedTarget ? __storeElement(event.relatedTarget) : 0;
|
|
__wasm()[exportName](id, tCookie, rCookie);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-${kind}`, handler);
|
|
el.addEventListener(eventName, handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-${kind}`;
|
|
const handler = __listenerHandlers.get(key);
|
|
if (el && handler) el.removeEventListener(eventName, handler);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
}
|
|
const __focusPair = __makeFocusListenerPair("focus", "focus", "ExecuteFocusHandler");
|
|
const __blurPair = __makeFocusListenerPair("blur", "blur", "ExecuteBlurHandler");
|
|
|
|
// Change / Input / Submit / Resize / Scroll have bespoke marshaling.
|
|
const __changePair = {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie); if (!el) return;
|
|
const handler = (event) => {
|
|
const p = __writeUtf8(event.target.value || "");
|
|
__wasm().ExecuteChangeHandler(id, p);
|
|
__wasm().WasmFree(p);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-change`, handler);
|
|
el.addEventListener("change", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-change`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (el && h) el.removeEventListener("change", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
const __inputPair = {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie); if (!el) return;
|
|
const handler = (event) => {
|
|
const text = event.data || (event.target && event.target.value) || "";
|
|
const p = __writeUtf8(text);
|
|
__wasm().ExecuteInputHandler(id, p, event.inputType === 'insertCompositionText');
|
|
__wasm().WasmFree(p);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-input`, handler);
|
|
el.addEventListener("input", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-input`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (el && h) el.removeEventListener("input", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
const __submitPair = {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie); if (!el) return;
|
|
const handler = (event) => { event.preventDefault(); __wasm().ExecuteSubmitHandler(id); };
|
|
__listenerHandlers.set(`${cookie}-${id}-submit`, handler);
|
|
el.addEventListener("submit", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-submit`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (el && h) el.removeEventListener("submit", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
const __resizePair = {
|
|
// Resize is window-global in CppDOM. Mirror that: attach to `window`
|
|
// regardless of which element the C++ caller passed.
|
|
add(cookie, id) {
|
|
const handler = () => __wasm().ExecuteResizeHandler(id, window.innerWidth, window.innerHeight);
|
|
__listenerHandlers.set(`${cookie}-${id}-resize`, handler);
|
|
window.addEventListener("resize", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const key = `${cookie}-${id}-resize`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (h) window.removeEventListener("resize", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
const __scrollPair = {
|
|
add(cookie, id) {
|
|
const handler = () => __wasm().ExecuteScrollHandler(id, window.scrollX, window.scrollY);
|
|
__listenerHandlers.set(`${cookie}-${id}-scroll`, handler);
|
|
window.addEventListener("scroll", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const key = `${cookie}-${id}-scroll`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (h) window.removeEventListener("scroll", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
const __wheelPair = {
|
|
add(cookie, id) {
|
|
const el = __jsmemory.get(cookie); if (!el) return;
|
|
const handler = (event) => {
|
|
__wasm().ExecuteWheelHandler(id,
|
|
event.deltaX, event.deltaY, event.deltaZ, event.deltaMode,
|
|
event.clientX, event.clientY, event.screenX, event.screenY,
|
|
event.button, event.buttons,
|
|
event.altKey, event.ctrlKey, event.shiftKey, event.metaKey);
|
|
};
|
|
__listenerHandlers.set(`${cookie}-${id}-wheel`, handler);
|
|
el.addEventListener("wheel", handler);
|
|
},
|
|
remove(cookie, id) {
|
|
const el = __jsmemory.get(cookie);
|
|
const key = `${cookie}-${id}-wheel`;
|
|
const h = __listenerHandlers.get(key);
|
|
if (el && h) el.removeEventListener("wheel", h);
|
|
__listenerHandlers.delete(key);
|
|
}
|
|
};
|
|
|
|
// ─── Window-level event hookup ────────────────────────────────────────
|
|
//
|
|
// Called by the C++ Window ctor (`domAttachWindow`). Installs document /
|
|
// window listeners that route into Window exports (`__crafterDom_*`).
|
|
// Only one Window per page in V1 — multiple Attach calls overwrite the
|
|
// previous attachment.
|
|
|
|
function domAttachWindow(windowHandle) {
|
|
__windowAttachedHandle = windowHandle;
|
|
|
|
const fire = (name, args) => {
|
|
const fn = __wasm()[name];
|
|
if (fn) fn(__windowAttachedHandle, ...args);
|
|
};
|
|
|
|
__windowListeners.mousemove = (e) => fire("__crafterDom_mouseMove", [e.clientX, e.clientY]);
|
|
__windowListeners.mousedown = (e) => fire("__crafterDom_mouseDown", [e.button]);
|
|
__windowListeners.mouseup = (e) => fire("__crafterDom_mouseUp", [e.button]);
|
|
__windowListeners.wheel = (e) => fire("__crafterDom_wheel", [e.deltaY]);
|
|
__windowListeners.contextmenu = (e) => { e.preventDefault(); };
|
|
|
|
// Keyboard events go through the document so they fire even when no
|
|
// input element is focused. event.code is the layout-independent
|
|
// physical key identifier — matches what the C++ Keys table hashes.
|
|
__windowListeners.keydown = (e) => {
|
|
const codePtr = __writeUtf8(e.code || "");
|
|
const keyPtr = __writeUtf8(e.key || "");
|
|
fire("__crafterDom_keyDown", [codePtr, e.code.length, keyPtr, e.key.length, e.repeat]);
|
|
__wasm().WasmFree(codePtr);
|
|
__wasm().WasmFree(keyPtr);
|
|
};
|
|
__windowListeners.keyup = (e) => {
|
|
const codePtr = __writeUtf8(e.code || "");
|
|
fire("__crafterDom_keyUp", [codePtr, e.code.length]);
|
|
__wasm().WasmFree(codePtr);
|
|
};
|
|
|
|
__windowListeners.resize = () => fire("__crafterDom_resize", [window.innerWidth, window.innerHeight]);
|
|
__windowListeners.beforeunload = () => fire("__crafterDom_close", []);
|
|
|
|
document.addEventListener("mousemove", __windowListeners.mousemove);
|
|
document.addEventListener("mousedown", __windowListeners.mousedown);
|
|
document.addEventListener("mouseup", __windowListeners.mouseup);
|
|
document.addEventListener("wheel", __windowListeners.wheel);
|
|
document.addEventListener("contextmenu", __windowListeners.contextmenu);
|
|
document.addEventListener("keydown", __windowListeners.keydown);
|
|
document.addEventListener("keyup", __windowListeners.keyup);
|
|
window .addEventListener("resize", __windowListeners.resize);
|
|
window .addEventListener("beforeunload",__windowListeners.beforeunload);
|
|
}
|
|
|
|
function domSetTitle(titlePtr, titleLen) {
|
|
document.title = __readUtf8(titlePtr, titleLen);
|
|
}
|
|
|
|
function domGetInnerWidth() { return window.innerWidth; }
|
|
function domGetInnerHeight() { return window.innerHeight; }
|
|
|
|
// ─── requestAnimationFrame loop ───────────────────────────────────────
|
|
|
|
function __frameTick() {
|
|
if (!__frameLoopActive) return;
|
|
const fn = __wasm().__crafterDom_frame;
|
|
if (fn) fn(__windowAttachedHandle);
|
|
window.requestAnimationFrame(__frameTick);
|
|
}
|
|
|
|
function domStartFrameLoop() {
|
|
if (__frameLoopActive) return;
|
|
__frameLoopActive = true;
|
|
window.requestAnimationFrame(__frameTick);
|
|
}
|
|
|
|
function domStopFrameLoop() {
|
|
__frameLoopActive = false;
|
|
}
|
|
|
|
// ─── Clipboard ────────────────────────────────────────────────────────
|
|
|
|
function clipboardSetText(strPtr, strLen) {
|
|
try {
|
|
navigator.clipboard.writeText(__readUtf8(strPtr, strLen));
|
|
return true;
|
|
} catch (err) {
|
|
console.error("Crafter.Clipboard.SetText failed:", err);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ─── History / routing ────────────────────────────────────────────────
|
|
|
|
function pushState(dataPtr, dataLen, titlePtr, titleLen, urlPtr, urlLen) {
|
|
const dataStr = __readUtf8(dataPtr, dataLen);
|
|
const titleStr = __readUtf8(titlePtr, titleLen);
|
|
const urlStr = __readUtf8(urlPtr, urlLen);
|
|
let parsed;
|
|
try { parsed = JSON.parse(dataStr); } catch { parsed = null; }
|
|
window.history.pushState(parsed, titleStr, urlStr);
|
|
}
|
|
function addPopStateListener(id) {
|
|
const handler = () => __wasm().ExecutePopStateHandler(id);
|
|
__listenerHandlers.set(`popstate-${id}`, handler);
|
|
window.addEventListener("popstate", handler);
|
|
}
|
|
function removePopStateListener(id) {
|
|
const h = __listenerHandlers.get(`popstate-${id}`);
|
|
if (h) window.removeEventListener("popstate", h);
|
|
__listenerHandlers.delete(`popstate-${id}`);
|
|
}
|
|
function getPathName() {
|
|
return __writeUtf8(window.location.pathname);
|
|
}
|
|
|
|
// ─── Gamepad polling helper ───────────────────────────────────────────
|
|
//
|
|
// V1: a minimal polling shim used by the C++ Gamepad backend. Returns
|
|
// the navigator.getGamepads() result reshaped into a flat layout the
|
|
// C++ side can read out via getter calls. Hot-plug events are exposed
|
|
// as a flag the C++ side polls during Tick.
|
|
|
|
let __gamepadConnectedFlag = false;
|
|
let __gamepadDisconnectedFlag = false;
|
|
window.addEventListener("gamepadconnected", () => { __gamepadConnectedFlag = true; });
|
|
window.addEventListener("gamepaddisconnected", () => { __gamepadDisconnectedFlag = true; });
|
|
function gamepadPollConnected() { const v = __gamepadConnectedFlag; __gamepadConnectedFlag = false; return v; }
|
|
function gamepadPollDisconnected() { const v = __gamepadDisconnectedFlag; __gamepadDisconnectedFlag = false; return v; }
|
|
function gamepadCount() {
|
|
return (navigator.getGamepads ? navigator.getGamepads() : []).filter(g => g).length;
|
|
}
|
|
function gamepadGetButton(idx, buttonIdx) {
|
|
const pads = navigator.getGamepads ? navigator.getGamepads() : [];
|
|
const p = pads.filter(g => g)[idx];
|
|
return p && p.buttons[buttonIdx] ? (p.buttons[buttonIdx].pressed ? 1 : 0) : 0;
|
|
}
|
|
function gamepadGetAxis(idx, axisIdx) {
|
|
const pads = navigator.getGamepads ? navigator.getGamepads() : [];
|
|
const p = pads.filter(g => g)[idx];
|
|
return p && typeof p.axes[axisIdx] === "number" ? p.axes[axisIdx] : 0;
|
|
}
|
|
|
|
// ─── Export env object ────────────────────────────────────────────────
|
|
|
|
if (!window.crafter_webbuild_env) {
|
|
window.crafter_webbuild_env = {};
|
|
}
|
|
Object.assign(window.crafter_webbuild_env, {
|
|
// DOM lookup / creation / mutation
|
|
freeJs, getElementById, createElement, getBody,
|
|
setInnerHTML, setStyle, setProperty,
|
|
addClass, removeClass, toggleClass, hasClass,
|
|
deleteElement, getValue, setValue,
|
|
|
|
// Element listeners — pair add/remove
|
|
addClickListener: __mouse_Click.add, removeClickListener: __mouse_Click.remove,
|
|
addMouseOverListener: __mouse_MouseOver.add, removeMouseOverListener: __mouse_MouseOver.remove,
|
|
addMouseOutListener: __mouse_MouseOut.add, removeMouseOutListener: __mouse_MouseOut.remove,
|
|
addMouseMoveListener: __mouse_MouseMove.add, removeMouseMoveListener: __mouse_MouseMove.remove,
|
|
addMouseDownListener: __mouse_MouseDown.add, removeMouseDownListener: __mouse_MouseDown.remove,
|
|
addMouseUpListener: __mouse_MouseUp.add, removeMouseUpListener: __mouse_MouseUp.remove,
|
|
addContextMenuListener: __mouse_ContextMenu.add, removeContextMenuListener: __mouse_ContextMenu.remove,
|
|
addDragStartListener: __mouse_DragStart.add, removeDragStartListener: __mouse_DragStart.remove,
|
|
addDragEndListener: __mouse_DragEnd.add, removeDragEndListener: __mouse_DragEnd.remove,
|
|
addDropListener: __mouse_Drop.add, removeDropListener: __mouse_Drop.remove,
|
|
addDragOverListener: __mouse_DragOver.add, removeDragOverListener: __mouse_DragOver.remove,
|
|
addDragEnterListener: __mouse_DragEnter.add, removeDragEnterListener: __mouse_DragEnter.remove,
|
|
addDragLeaveListener: __mouse_DragLeave.add, removeDragLeaveListener: __mouse_DragLeave.remove,
|
|
addKeyDownListener: __key_KeyDown.add, removeKeyDownListener: __key_KeyDown.remove,
|
|
addKeyUpListener: __key_KeyUp.add, removeKeyUpListener: __key_KeyUp.remove,
|
|
addKeyPressListener: __key_KeyPress.add, removeKeyPressListener: __key_KeyPress.remove,
|
|
addFocusListener: __focusPair.add, removeFocusListener: __focusPair.remove,
|
|
addBlurListener: __blurPair.add, removeBlurListener: __blurPair.remove,
|
|
addChangeListener: __changePair.add, removeChangeListener: __changePair.remove,
|
|
addInputListener: __inputPair.add, removeInputListener: __inputPair.remove,
|
|
addSubmitListener: __submitPair.add, removeSubmitListener: __submitPair.remove,
|
|
addResizeListener: __resizePair.add, removeResizeListener: __resizePair.remove,
|
|
addScrollListener: __scrollPair.add, removeScrollListener: __scrollPair.remove,
|
|
addWheelListener: __wheelPair.add, removeWheelListener: __wheelPair.remove,
|
|
|
|
// Window / lifecycle
|
|
domAttachWindow, domSetTitle,
|
|
domGetInnerWidth, domGetInnerHeight,
|
|
domStartFrameLoop, domStopFrameLoop,
|
|
|
|
// Clipboard
|
|
clipboardSetText,
|
|
|
|
// History
|
|
pushState, addPopStateListener, removePopStateListener, getPathName,
|
|
|
|
// Gamepad
|
|
gamepadPollConnected, gamepadPollDisconnected,
|
|
gamepadCount, gamepadGetButton, gamepadGetAxis,
|
|
});
|