browser DOM support
This commit is contained in:
parent
3859c43ce3
commit
5352ef69a2
37 changed files with 2637 additions and 59 deletions
558
additional/dom-env.js
Normal file
558
additional/dom-env.js
Normal file
|
|
@ -0,0 +1,558 @@
|
|||
/*
|
||||
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,
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue