Crafter.Graphics/implementations/Crafter.Graphics-Dom.cpp

571 lines
31 KiB
C++
Raw Permalink Normal View History

2026-05-18 02:07:48 +02:00
/*
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
*/
// Implementation of the :Dom partition. Only ever built when
// CRAFTER_GRAPHICS_WINDOW_DOM is defined (project.cpp's DOM branch
// is what brings this file into the source list). All WASM imports
// resolve against the `env` module produced by additional/dom-env.js;
// all exports become C entry points the JS bridge calls back through.
module;
module Crafter.Graphics;
import std;
using namespace Crafter::Dom;
// ─── WASM env imports + per-event-kind handler maps ───────────────────
//
// External linkage required for the import_module/import_name attributes
// to actually wire up to a WASM env symbol — anonymous namespace would
// hide them. The namespace also gates access from the extern "C"
// dispatcher block below.
namespace Crafter::DomBindings {
// DOM ops (string args are pointer/length pairs — the JS side decodes
// UTF-8 directly out of WASM linear memory; no copying on the C++
// side).
__attribute__((import_module("env"), import_name("freeJs")))
void FreeJs(std::int32_t ptr);
__attribute__((import_module("env"), import_name("getElementById")))
std::int32_t GetElementById(const char* id, std::int32_t idLength);
__attribute__((import_module("env"), import_name("createElement")))
std::int32_t CreateElement(std::int32_t parentPtr,
const char* tagName, std::int32_t tagNameLength,
const char* id, std::int32_t idLength);
__attribute__((import_module("env"), import_name("getBody")))
std::int32_t GetBody();
__attribute__((import_module("env"), import_name("setInnerHTML")))
void SetInnerHTML(std::int32_t ptr, const char* html, std::int32_t htmlLength);
__attribute__((import_module("env"), import_name("setStyle")))
void SetStyle(std::int32_t ptr, const char* style, std::int32_t styleLength);
__attribute__((import_module("env"), import_name("setProperty")))
void SetProperty(std::int32_t ptr,
const char* property, std::int32_t propertyLength,
const char* value, std::int32_t valueLength);
__attribute__((import_module("env"), import_name("addClass")))
void AddClass(std::int32_t ptr, const char* className, std::int32_t classNameLength);
__attribute__((import_module("env"), import_name("removeClass")))
void RemoveClass(std::int32_t ptr, const char* className, std::int32_t classNameLength);
__attribute__((import_module("env"), import_name("toggleClass")))
void ToggleClass(std::int32_t ptr, const char* className, std::int32_t classNameLength);
__attribute__((import_module("env"), import_name("hasClass")))
bool HasClass(std::int32_t ptr, const char* className, std::int32_t classNameLength);
__attribute__((import_module("env"), import_name("deleteElement")))
void DeleteElement(std::int32_t ptr);
__attribute__((import_module("env"), import_name("getValue")))
const char* GetValue(std::int32_t ptr);
__attribute__((import_module("env"), import_name("setValue")))
void SetValue(std::int32_t ptr, const char* value, std::int32_t valueLength);
// Per-event-kind listener register / unregister imports.
#define CG_DOM_LISTENER_IMPORT(addName, removeName) \
__attribute__((import_module("env"), import_name(#addName))) \
void addName(std::int32_t ptr, std::int32_t id); \
__attribute__((import_module("env"), import_name(#removeName))) \
void removeName(std::int32_t ptr, std::int32_t id);
CG_DOM_LISTENER_IMPORT(addClickListener, removeClickListener)
CG_DOM_LISTENER_IMPORT(addMouseOverListener, removeMouseOverListener)
CG_DOM_LISTENER_IMPORT(addMouseOutListener, removeMouseOutListener)
CG_DOM_LISTENER_IMPORT(addMouseMoveListener, removeMouseMoveListener)
CG_DOM_LISTENER_IMPORT(addMouseDownListener, removeMouseDownListener)
CG_DOM_LISTENER_IMPORT(addMouseUpListener, removeMouseUpListener)
CG_DOM_LISTENER_IMPORT(addFocusListener, removeFocusListener)
CG_DOM_LISTENER_IMPORT(addBlurListener, removeBlurListener)
CG_DOM_LISTENER_IMPORT(addKeyDownListener, removeKeyDownListener)
CG_DOM_LISTENER_IMPORT(addKeyUpListener, removeKeyUpListener)
CG_DOM_LISTENER_IMPORT(addKeyPressListener, removeKeyPressListener)
CG_DOM_LISTENER_IMPORT(addChangeListener, removeChangeListener)
CG_DOM_LISTENER_IMPORT(addSubmitListener, removeSubmitListener)
CG_DOM_LISTENER_IMPORT(addInputListener, removeInputListener)
CG_DOM_LISTENER_IMPORT(addResizeListener, removeResizeListener)
CG_DOM_LISTENER_IMPORT(addScrollListener, removeScrollListener)
CG_DOM_LISTENER_IMPORT(addContextMenuListener, removeContextMenuListener)
CG_DOM_LISTENER_IMPORT(addDragStartListener, removeDragStartListener)
CG_DOM_LISTENER_IMPORT(addDragEndListener, removeDragEndListener)
CG_DOM_LISTENER_IMPORT(addDropListener, removeDropListener)
CG_DOM_LISTENER_IMPORT(addDragOverListener, removeDragOverListener)
CG_DOM_LISTENER_IMPORT(addDragEnterListener, removeDragEnterListener)
CG_DOM_LISTENER_IMPORT(addDragLeaveListener, removeDragLeaveListener)
CG_DOM_LISTENER_IMPORT(addWheelListener, removeWheelListener)
#undef CG_DOM_LISTENER_IMPORT
// Per-event-kind callback maps. Counters are per-kind so two
// different event kinds can share an id without aliasing — the JS
// bridge keys its own handler table by (ptr, id, kind) for the same
// reason. `maxId` is bumped without wrap-around handling: 2 billion
// listeners over the lifetime of a single page is enough.
template <typename EvT>
struct HandlerTable {
std::int32_t maxId = 0;
std::unordered_map<std::int32_t, std::function<void(EvT)>> map;
};
template <>
struct HandlerTable<void> {
std::int32_t maxId = 0;
std::unordered_map<std::int32_t, std::function<void()>> map;
};
inline HandlerTable<MouseEvent> clickT, mouseOverT, mouseOutT, mouseMoveT,
mouseDownT, mouseUpT, contextMenuT,
dragStartT, dragEndT, dropT,
dragOverT, dragEnterT, dragLeaveT;
inline HandlerTable<WheelEvent> wheelT;
inline HandlerTable<FocusEvent> focusT, blurT;
inline HandlerTable<KeyboardEvent> keyDownT, keyUpT, keyPressT;
inline HandlerTable<ChangeEvent> changeT;
inline HandlerTable<InputEvent> inputT;
inline HandlerTable<ResizeEvent> resizeT;
inline HandlerTable<ScrollEvent> scrollT;
inline HandlerTable<void> submitT;
inline HandlerTable<void> popStateT;
inline HandlerTable<std::string> fetchT; // Reserved for future use.
// Opaque cross-TU helpers for the popState table. Router.cpp lives
// in a different translation unit and can't touch HandlerTable
// (re-defining the specialization there would be an ODR violation);
// it goes through these instead. Defined below the namespace.
std::int32_t PopStateRegister(std::function<void()> cb);
void PopStateUnregister(std::int32_t id);
// The 24 stable indices used by HtmlElementPtr::handlerIds_[] to
// map the per-kind id list back to the right `remove*Listener`
// import on destruction. Order MUST match the cppm comment.
enum class Kind : std::uint8_t {
Click = 0, MouseOver, MouseOut, MouseMove, MouseDown, MouseUp,
Focus, Blur, KeyDown, KeyUp, KeyPress, Change, Submit, Input,
Resize, Scroll, ContextMenu, DragStart, DragEnd, Drop,
DragOver, DragEnter, DragLeave, Wheel,
};
}
// ─── WASM exports the JS bridge calls back through ────────────────────
//
// One per event kind plus the popstate / fetch dispatchers. Each takes
// the handler id + the marshaled event fields, looks up the table, and
// invokes the std::function. find()->second is intentional: a callback
// firing for an id we never registered is a bridge bug worth crashing
// on (the env.js side stores its own handler table keyed by id and
// only calls Execute*Handler for ids it minted).
extern "C" {
__attribute__((export_name("WasmAlloc")))
void* WasmAlloc(std::int32_t size) { return std::malloc(size); }
__attribute__((export_name("WasmFree")))
void WasmFree(void* ptr) { std::free(ptr); }
#define CG_DOM_EXEC_MOUSE(exportName, table) \
__attribute__((export_name(#exportName))) \
void exportName(std::int32_t handlerID, \
double clientX, double clientY, \
double screenX, double screenY, \
std::int32_t button, std::int32_t buttons, \
bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) { \
Crafter::DomBindings::table.map.find(handlerID)->second( \
MouseEvent(clientX, clientY, screenX, screenY, \
button, buttons, altKey, ctrlKey, shiftKey, metaKey)); \
}
CG_DOM_EXEC_MOUSE(ExecuteClickHandler, clickT)
CG_DOM_EXEC_MOUSE(ExecuteMouseOverHandler, mouseOverT)
CG_DOM_EXEC_MOUSE(ExecuteMouseOutHandler, mouseOutT)
CG_DOM_EXEC_MOUSE(ExecuteMouseMoveHandler, mouseMoveT)
CG_DOM_EXEC_MOUSE(ExecuteMouseDownHandler, mouseDownT)
CG_DOM_EXEC_MOUSE(ExecuteMouseUpHandler, mouseUpT)
CG_DOM_EXEC_MOUSE(ExecuteContextMenuHandler, contextMenuT)
CG_DOM_EXEC_MOUSE(ExecuteDragStartHandler, dragStartT)
CG_DOM_EXEC_MOUSE(ExecuteDragEndHandler, dragEndT)
CG_DOM_EXEC_MOUSE(ExecuteDropHandler, dropT)
CG_DOM_EXEC_MOUSE(ExecuteDragOverHandler, dragOverT)
CG_DOM_EXEC_MOUSE(ExecuteDragEnterHandler, dragEnterT)
CG_DOM_EXEC_MOUSE(ExecuteDragLeaveHandler, dragLeaveT)
#undef CG_DOM_EXEC_MOUSE
__attribute__((export_name("ExecuteWheelHandler")))
void ExecuteWheelHandler(std::int32_t handlerID,
double deltaX, double deltaY, double deltaZ, std::int32_t /*deltaMode*/,
double clientX, double clientY, double screenX, double screenY,
std::int32_t button, std::int32_t buttons,
bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) {
Crafter::DomBindings::wheelT.map.find(handlerID)->second(
WheelEvent(clientX, clientY, screenX, screenY,
button, buttons, altKey, ctrlKey, shiftKey, metaKey,
deltaX, deltaY, deltaZ));
}
__attribute__((export_name("ExecuteFocusHandler")))
void ExecuteFocusHandler(std::int32_t handlerID, void* target, void* relatedTarget) {
Crafter::DomBindings::focusT.map.find(handlerID)->second(
FocusEvent(target, relatedTarget));
}
__attribute__((export_name("ExecuteBlurHandler")))
void ExecuteBlurHandler(std::int32_t handlerID, void* target, void* relatedTarget) {
Crafter::DomBindings::blurT.map.find(handlerID)->second(
FocusEvent(target, relatedTarget));
}
#define CG_DOM_EXEC_KEY(exportName, table) \
__attribute__((export_name(#exportName))) \
void exportName(std::int32_t handlerID, \
const char* key, std::int32_t keyCode, \
bool altKey, bool ctrlKey, bool shiftKey, bool metaKey) { \
Crafter::DomBindings::table.map.find(handlerID)->second( \
KeyboardEvent(key, keyCode, altKey, ctrlKey, shiftKey, metaKey)); \
}
CG_DOM_EXEC_KEY(ExecuteKeyDownHandler, keyDownT)
CG_DOM_EXEC_KEY(ExecuteKeyUpHandler, keyUpT)
CG_DOM_EXEC_KEY(ExecuteKeyPressHandler, keyPressT)
#undef CG_DOM_EXEC_KEY
__attribute__((export_name("ExecuteChangeHandler")))
void ExecuteChangeHandler(std::int32_t handlerID, const char* value) {
Crafter::DomBindings::changeT.map.find(handlerID)->second(ChangeEvent(value));
}
__attribute__((export_name("ExecuteSubmitHandler")))
void ExecuteSubmitHandler(std::int32_t handlerID) {
Crafter::DomBindings::submitT.map.find(handlerID)->second();
}
__attribute__((export_name("ExecuteInputHandler")))
void ExecuteInputHandler(std::int32_t handlerID, const char* data, bool isComposing) {
Crafter::DomBindings::inputT.map.find(handlerID)->second(InputEvent(data, isComposing));
}
__attribute__((export_name("ExecuteResizeHandler")))
void ExecuteResizeHandler(std::int32_t handlerID, std::int32_t width, std::int32_t height) {
Crafter::DomBindings::resizeT.map.find(handlerID)->second(ResizeEvent(width, height));
}
__attribute__((export_name("ExecuteScrollHandler")))
void ExecuteScrollHandler(std::int32_t handlerID, double scrollX, double scrollY) {
Crafter::DomBindings::scrollT.map.find(handlerID)->second(ScrollEvent(scrollX, scrollY));
}
__attribute__((export_name("ExecutePopStateHandler")))
void ExecutePopStateHandler(std::int32_t handlerID) {
Crafter::DomBindings::popStateT.map.find(handlerID)->second();
}
}
// ─── Listener add/remove helpers ──────────────────────────────────────
//
// Each Add*Listener follows the same five-step pattern; capturing it in
// a single function template removes ~120 lines of near-duplicate body
// code. AddImpl returns the freshly minted id (already pushed onto the
// per-element tracking list). RemoveImpl performs the inverse for an
// id known to belong to the right table.
namespace {
template <typename EvT, typename JsAdd>
std::int32_t AddImpl(Crafter::DomBindings::HandlerTable<EvT>& table,
std::int32_t ptr,
std::vector<std::int32_t>& trackingList,
JsAdd jsAdd,
std::function<void(EvT)> callback) {
if (ptr == 0) return 0;
std::int32_t id = table.maxId++;
table.map.insert({id, std::move(callback)});
jsAdd(ptr, id);
trackingList.push_back(id);
return id;
}
template <typename JsAdd>
std::int32_t AddVoidImpl(Crafter::DomBindings::HandlerTable<void>& table,
std::int32_t ptr,
std::vector<std::int32_t>& trackingList,
JsAdd jsAdd,
std::function<void()> callback) {
if (ptr == 0) return 0;
std::int32_t id = table.maxId++;
table.map.insert({id, std::move(callback)});
jsAdd(ptr, id);
trackingList.push_back(id);
return id;
}
template <typename Table, typename JsRemove>
void RemoveImpl(Table& table,
std::int32_t ptr,
std::vector<std::int32_t>& trackingList,
JsRemove jsRemove,
std::int32_t id) {
if (ptr == 0) return;
table.map.erase(id);
trackingList.erase(std::remove(trackingList.begin(), trackingList.end(), id),
trackingList.end());
jsRemove(ptr, id);
}
}
// ─── HtmlElementPtr ───────────────────────────────────────────────────
namespace Crafter::Dom {
HtmlElementPtr::HtmlElementPtr(const std::string_view id)
: ptr(Crafter::DomBindings::GetElementById(id.data(), id.size())) {}
HtmlElementPtr::HtmlElementPtr(const std::string_view id, const std::string_view html)
: ptr(Crafter::DomBindings::GetElementById(id.data(), id.size())) {
if (ptr != 0) {
Crafter::DomBindings::SetInnerHTML(ptr, html.data(), html.size());
}
}
HtmlElementPtr::HtmlElementPtr(HtmlElementPtr&& other) noexcept
: ptr(other.ptr) {
other.ptr = 0;
for (std::size_t i = 0; i < 24; ++i) handlerIds_[i] = std::move(other.handlerIds_[i]);
}
HtmlElementPtr& HtmlElementPtr::operator=(HtmlElementPtr&& other) noexcept {
if (this == &other) return *this;
RemoveAllHandlersAndFree();
ptr = other.ptr;
other.ptr = 0;
for (std::size_t i = 0; i < 24; ++i) handlerIds_[i] = std::move(other.handlerIds_[i]);
return *this;
}
HtmlElementPtr::~HtmlElementPtr() {
RemoveAllHandlersAndFree();
}
void HtmlElementPtr::RemoveAllHandlersAndFree() {
if (ptr == 0) return;
// Unregister every still-live handler the user forgot about.
// The per-kind index matches the cppm comment ordering and the
// Kind enum above; keep them in sync.
namespace D = Crafter::DomBindings;
auto sweep = [this](auto& table, auto jsRemove, std::size_t kind) {
for (std::int32_t id : handlerIds_[kind]) {
table.map.erase(id);
jsRemove(ptr, id);
}
handlerIds_[kind].clear();
};
sweep(D::clickT, D::removeClickListener, (std::size_t)D::Kind::Click);
sweep(D::mouseOverT, D::removeMouseOverListener, (std::size_t)D::Kind::MouseOver);
sweep(D::mouseOutT, D::removeMouseOutListener, (std::size_t)D::Kind::MouseOut);
sweep(D::mouseMoveT, D::removeMouseMoveListener, (std::size_t)D::Kind::MouseMove);
sweep(D::mouseDownT, D::removeMouseDownListener, (std::size_t)D::Kind::MouseDown);
sweep(D::mouseUpT, D::removeMouseUpListener, (std::size_t)D::Kind::MouseUp);
sweep(D::focusT, D::removeFocusListener, (std::size_t)D::Kind::Focus);
sweep(D::blurT, D::removeBlurListener, (std::size_t)D::Kind::Blur);
sweep(D::keyDownT, D::removeKeyDownListener, (std::size_t)D::Kind::KeyDown);
sweep(D::keyUpT, D::removeKeyUpListener, (std::size_t)D::Kind::KeyUp);
sweep(D::keyPressT, D::removeKeyPressListener, (std::size_t)D::Kind::KeyPress);
sweep(D::changeT, D::removeChangeListener, (std::size_t)D::Kind::Change);
sweep(D::submitT, D::removeSubmitListener, (std::size_t)D::Kind::Submit);
sweep(D::inputT, D::removeInputListener, (std::size_t)D::Kind::Input);
sweep(D::resizeT, D::removeResizeListener, (std::size_t)D::Kind::Resize);
sweep(D::scrollT, D::removeScrollListener, (std::size_t)D::Kind::Scroll);
sweep(D::contextMenuT, D::removeContextMenuListener, (std::size_t)D::Kind::ContextMenu);
sweep(D::dragStartT, D::removeDragStartListener, (std::size_t)D::Kind::DragStart);
sweep(D::dragEndT, D::removeDragEndListener, (std::size_t)D::Kind::DragEnd);
sweep(D::dropT, D::removeDropListener, (std::size_t)D::Kind::Drop);
sweep(D::dragOverT, D::removeDragOverListener, (std::size_t)D::Kind::DragOver);
sweep(D::dragEnterT, D::removeDragEnterListener, (std::size_t)D::Kind::DragEnter);
sweep(D::dragLeaveT, D::removeDragLeaveListener, (std::size_t)D::Kind::DragLeave);
sweep(D::wheelT, D::removeWheelListener, (std::size_t)D::Kind::Wheel);
D::FreeJs(ptr);
ptr = 0;
}
// DOM ops are thin pass-throughs; the JS side does the work.
void HtmlElementPtr::SetInnerHTML(const std::string_view html) {
if (ptr) Crafter::DomBindings::SetInnerHTML(ptr, html.data(), html.size());
}
void HtmlElementPtr::SetStyle(const std::string_view style) {
if (ptr) Crafter::DomBindings::SetStyle(ptr, style.data(), style.size());
}
void HtmlElementPtr::SetProperty(const std::string_view property, const std::string_view value) {
if (ptr) Crafter::DomBindings::SetProperty(ptr,
property.data(), property.size(),
value.data(), value.size());
}
void HtmlElementPtr::AddClass(const std::string_view className) {
if (ptr) Crafter::DomBindings::AddClass(ptr, className.data(), className.size());
}
void HtmlElementPtr::RemoveClass(const std::string_view className) {
if (ptr) Crafter::DomBindings::RemoveClass(ptr, className.data(), className.size());
}
void HtmlElementPtr::ToggleClass(const std::string_view className) {
if (ptr) Crafter::DomBindings::ToggleClass(ptr, className.data(), className.size());
}
bool HtmlElementPtr::HasClass(const std::string_view className) {
return ptr && Crafter::DomBindings::HasClass(ptr, className.data(), className.size());
}
std::string HtmlElementPtr::GetValue() {
if (!ptr) return {};
// The JS side WasmAlloc's a NUL-terminated buffer. We own it
// after the call — copy into std::string and free.
const char* raw = Crafter::DomBindings::GetValue(ptr);
if (!raw) return {};
std::string out(raw);
std::free(const_cast<char*>(raw));
return out;
}
void HtmlElementPtr::SetValue(const std::string_view value) {
if (ptr) Crafter::DomBindings::SetValue(ptr, value.data(), value.size());
}
// Listener wrappers. Each Add/Remove pair just plugs the right
// table, kind index, and js import into the helper templates. The
// 23 (+1 for popstate, lived in :Router) listener kinds previously
// expanded to ~250 lines of body; here they're ~60.
#define CG_DOM_ADD(MethodName, JsAdd, TableName, EvType, KindEnum) \
std::int32_t HtmlElementPtr::MethodName(std::function<void(EvType)> cb) { \
return AddImpl<EvType>(Crafter::DomBindings::TableName, ptr, \
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \
Crafter::DomBindings::JsAdd, std::move(cb)); \
}
#define CG_DOM_REMOVE(MethodName, JsRemove, TableName, KindEnum) \
void HtmlElementPtr::MethodName(std::int32_t id) { \
RemoveImpl(Crafter::DomBindings::TableName, ptr, \
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::KindEnum], \
Crafter::DomBindings::JsRemove, id); \
}
CG_DOM_ADD (AddClickListener, addClickListener, clickT, MouseEvent, Click)
CG_DOM_REMOVE(RemoveClickListener, removeClickListener, clickT, Click)
CG_DOM_ADD (AddMouseOverListener, addMouseOverListener, mouseOverT, MouseEvent, MouseOver)
CG_DOM_REMOVE(RemoveMouseOverListener, removeMouseOverListener, mouseOverT, MouseOver)
CG_DOM_ADD (AddMouseOutListener, addMouseOutListener, mouseOutT, MouseEvent, MouseOut)
CG_DOM_REMOVE(RemoveMouseOutListener, removeMouseOutListener, mouseOutT, MouseOut)
CG_DOM_ADD (AddMouseMoveListener, addMouseMoveListener, mouseMoveT, MouseEvent, MouseMove)
CG_DOM_REMOVE(RemoveMouseMoveListener, removeMouseMoveListener, mouseMoveT, MouseMove)
CG_DOM_ADD (AddMouseDownListener, addMouseDownListener, mouseDownT, MouseEvent, MouseDown)
CG_DOM_REMOVE(RemoveMouseDownListener, removeMouseDownListener, mouseDownT, MouseDown)
CG_DOM_ADD (AddMouseUpListener, addMouseUpListener, mouseUpT, MouseEvent, MouseUp)
CG_DOM_REMOVE(RemoveMouseUpListener, removeMouseUpListener, mouseUpT, MouseUp)
CG_DOM_ADD (AddFocusListener, addFocusListener, focusT, FocusEvent, Focus)
CG_DOM_REMOVE(RemoveFocusListener, removeFocusListener, focusT, Focus)
CG_DOM_ADD (AddBlurListener, addBlurListener, blurT, FocusEvent, Blur)
CG_DOM_REMOVE(RemoveBlurListener, removeBlurListener, blurT, Blur)
CG_DOM_ADD (AddKeyDownListener, addKeyDownListener, keyDownT, KeyboardEvent, KeyDown)
CG_DOM_REMOVE(RemoveKeyDownListener, removeKeyDownListener, keyDownT, KeyDown)
CG_DOM_ADD (AddKeyUpListener, addKeyUpListener, keyUpT, KeyboardEvent, KeyUp)
CG_DOM_REMOVE(RemoveKeyUpListener, removeKeyUpListener, keyUpT, KeyUp)
CG_DOM_ADD (AddKeyPressListener, addKeyPressListener, keyPressT, KeyboardEvent, KeyPress)
CG_DOM_REMOVE(RemoveKeyPressListener, removeKeyPressListener, keyPressT, KeyPress)
CG_DOM_ADD (AddChangeListener, addChangeListener, changeT, ChangeEvent, Change)
CG_DOM_REMOVE(RemoveChangeListener, removeChangeListener, changeT, Change)
CG_DOM_ADD (AddInputListener, addInputListener, inputT, InputEvent, Input)
CG_DOM_REMOVE(RemoveInputListener, removeInputListener, inputT, Input)
CG_DOM_ADD (AddResizeListener, addResizeListener, resizeT, ResizeEvent, Resize)
CG_DOM_REMOVE(RemoveResizeListener, removeResizeListener, resizeT, Resize)
CG_DOM_ADD (AddScrollListener, addScrollListener, scrollT, ScrollEvent, Scroll)
CG_DOM_REMOVE(RemoveScrollListener, removeScrollListener, scrollT, Scroll)
CG_DOM_ADD (AddContextMenuListener, addContextMenuListener, contextMenuT, MouseEvent, ContextMenu)
CG_DOM_REMOVE(RemoveContextMenuListener,removeContextMenuListener,contextMenuT, ContextMenu)
CG_DOM_ADD (AddDragStartListener, addDragStartListener, dragStartT, MouseEvent, DragStart)
CG_DOM_REMOVE(RemoveDragStartListener, removeDragStartListener, dragStartT, DragStart)
CG_DOM_ADD (AddDragEndListener, addDragEndListener, dragEndT, MouseEvent, DragEnd)
CG_DOM_REMOVE(RemoveDragEndListener, removeDragEndListener, dragEndT, DragEnd)
CG_DOM_ADD (AddDropListener, addDropListener, dropT, MouseEvent, Drop)
CG_DOM_REMOVE(RemoveDropListener, removeDropListener, dropT, Drop)
CG_DOM_ADD (AddDragOverListener, addDragOverListener, dragOverT, MouseEvent, DragOver)
CG_DOM_REMOVE(RemoveDragOverListener, removeDragOverListener, dragOverT, DragOver)
CG_DOM_ADD (AddDragEnterListener, addDragEnterListener, dragEnterT, MouseEvent, DragEnter)
CG_DOM_REMOVE(RemoveDragEnterListener, removeDragEnterListener, dragEnterT, DragEnter)
CG_DOM_ADD (AddDragLeaveListener, addDragLeaveListener, dragLeaveT, MouseEvent, DragLeave)
CG_DOM_REMOVE(RemoveDragLeaveListener, removeDragLeaveListener, dragLeaveT, DragLeave)
CG_DOM_ADD (AddWheelListener, addWheelListener, wheelT, WheelEvent, Wheel)
CG_DOM_REMOVE(RemoveWheelListener, removeWheelListener, wheelT, Wheel)
#undef CG_DOM_ADD
#undef CG_DOM_REMOVE
// Submit takes std::function<void()> — needs the void specialization.
std::int32_t HtmlElementPtr::AddSubmitListener(std::function<void()> cb) {
return AddVoidImpl(Crafter::DomBindings::submitT, ptr,
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::Submit],
Crafter::DomBindings::addSubmitListener, std::move(cb));
}
void HtmlElementPtr::RemoveSubmitListener(std::int32_t id) {
RemoveImpl(Crafter::DomBindings::submitT, ptr,
handlerIds_[(std::size_t)Crafter::DomBindings::Kind::Submit],
Crafter::DomBindings::removeSubmitListener, id);
}
// ─── HtmlElement ──────────────────────────────────────────────────
HtmlElement::HtmlElement(const std::string_view id)
: HtmlElementPtr(id) {}
HtmlElement::HtmlElement(const std::string_view id, const std::string_view html)
: HtmlElementPtr(id, html) {}
HtmlElement::HtmlElement(HtmlElement&& other) noexcept
: HtmlElementPtr(std::move(other)) {}
HtmlElement& HtmlElement::operator=(HtmlElement&& other) noexcept {
if (this == &other) return *this;
HtmlElementPtr::operator=(std::move(other));
return *this;
}
HtmlElement::~HtmlElement() {
// Remove the element from the DOM first. The base destructor
// runs next (implicit) and will sweep handler maps + call
// FreeJs on the still-valid JS cookie. DeleteElement only
// removes the node from its parent; the cookie itself stays
// alive in jsmemory until FreeJs.
if (ptr != 0) {
Crafter::DomBindings::DeleteElement(ptr);
}
}
HtmlElement HtmlElement::Create(const HtmlElementPtr& parent,
std::string_view tagName,
std::string_view id) {
std::int32_t handle = Crafter::DomBindings::CreateElement(
parent.ptr,
tagName.data(), static_cast<std::int32_t>(tagName.size()),
id.data(), static_cast<std::int32_t>(id.size()));
return HtmlElement(HtmlElementPtr::FromHandle{handle});
}
HtmlElement HtmlElement::CreateInBody(std::string_view tagName,
std::string_view id) {
std::int32_t body = Crafter::DomBindings::GetBody();
std::int32_t handle = Crafter::DomBindings::CreateElement(
body,
tagName.data(), static_cast<std::int32_t>(tagName.size()),
id.data(), static_cast<std::int32_t>(id.size()));
Crafter::DomBindings::FreeJs(body);
return HtmlElement(HtmlElementPtr::FromHandle{handle});
}
}
// Sibling-TU helpers exposed for Router.cpp. Keep the popStateT table
// private to this TU and let Router go through these.
namespace Crafter::DomBindings {
std::int32_t PopStateRegister(std::function<void()> cb) {
std::int32_t id = popStateT.maxId++;
popStateT.map.insert({id, std::move(cb)});
return id;
}
void PopStateUnregister(std::int32_t id) {
popStateT.map.erase(id);
}
}