browser DOM support
This commit is contained in:
parent
3859c43ce3
commit
5352ef69a2
37 changed files with 2637 additions and 59 deletions
217
interfaces/Crafter.Graphics-Dom.cppm
Normal file
217
interfaces/Crafter.Graphics-Dom.cppm
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
// Browser DOM bindings — absorbed from Crafter.CppDOM. Only meaningful
|
||||
// when the build defined CRAFTER_GRAPHICS_WINDOW_DOM (i.e. a wasm32-*
|
||||
// target paired with additional/dom-env.js); on native platforms the
|
||||
// partition still parses but every method links to nothing because the
|
||||
// build excludes the impl. project.cpp's per-target module set is what
|
||||
// makes the partition disappear from the native libs.
|
||||
//
|
||||
// Two-class hierarchy (simplified from CppDOM's three-class design):
|
||||
// HtmlElementPtr — non-owning element reference. Tracks every handler
|
||||
// it registered and clears them on destruction, so
|
||||
// the silent-leak bug from CppDOM's bare-pointer
|
||||
// flavour can't happen. Releases the JS handle on
|
||||
// destruction.
|
||||
// HtmlElement — `HtmlElementPtr` plus element ownership: removes
|
||||
// the element from the DOM on destruction. The new
|
||||
// `Create` factory builds a fresh element under a
|
||||
// parent and returns one of these.
|
||||
//
|
||||
// Move-only throughout. The JS handle (`ptr`) of a moved-from instance
|
||||
// is zeroed so the destructor of the moved-from carcass is a no-op.
|
||||
|
||||
export module Crafter.Graphics:Dom;
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
import std;
|
||||
import :DomEvents;
|
||||
|
||||
export namespace Crafter::Dom {
|
||||
|
||||
class HtmlElementPtr {
|
||||
public:
|
||||
// Opaque JS-side element cookie. 0 = "no element"; the destructor
|
||||
// treats 0 as "moved-from, nothing to release".
|
||||
std::int32_t ptr;
|
||||
|
||||
// Look up an existing element by its DOM id.
|
||||
HtmlElementPtr(const std::string_view id);
|
||||
// Look up + replace innerHTML in one step (matches CppDOM).
|
||||
HtmlElementPtr(const std::string_view id, const std::string_view html);
|
||||
|
||||
protected:
|
||||
// Adopt a JS handle directly (used by HtmlElement::Create — the
|
||||
// JS side has already minted the handle, no second lookup needed).
|
||||
// Tagged-type ctor so the public id-based overloads stay
|
||||
// unambiguous from the call site.
|
||||
struct FromHandle { std::int32_t handle; };
|
||||
explicit HtmlElementPtr(FromHandle h) noexcept : ptr(h.handle) {}
|
||||
|
||||
public:
|
||||
|
||||
// Move-only — copying would silently double-free the JS handle.
|
||||
HtmlElementPtr(HtmlElementPtr&&) noexcept;
|
||||
HtmlElementPtr& operator=(HtmlElementPtr&&) noexcept;
|
||||
HtmlElementPtr(const HtmlElementPtr&) = delete;
|
||||
HtmlElementPtr& operator=(const HtmlElementPtr&) = delete;
|
||||
|
||||
~HtmlElementPtr();
|
||||
|
||||
// DOM ops ─────────────────────────────────────────────────────
|
||||
void SetInnerHTML(const std::string_view html);
|
||||
void SetStyle(const std::string_view style);
|
||||
void SetProperty(const std::string_view property, const std::string_view value);
|
||||
void AddClass(const std::string_view className);
|
||||
void RemoveClass(const std::string_view className);
|
||||
void ToggleClass(const std::string_view className);
|
||||
bool HasClass(const std::string_view className);
|
||||
std::string GetValue();
|
||||
void SetValue(const std::string_view value);
|
||||
|
||||
// Listener API — each Add* returns an opaque id that can be
|
||||
// passed to the matching Remove*. The destructor automatically
|
||||
// removes every handler still registered, so manual removal is
|
||||
// optional. Returns 0 only if registration failed at the JS
|
||||
// boundary (the element was already collected). The 23 event
|
||||
// types are 1:1 with CppDOM's surface.
|
||||
std::int32_t AddClickListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveClickListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddMouseOverListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveMouseOverListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddMouseOutListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveMouseOutListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddMouseMoveListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveMouseMoveListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddMouseDownListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveMouseDownListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddMouseUpListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveMouseUpListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddFocusListener(std::function<void(Crafter::Dom::FocusEvent)> callback);
|
||||
void RemoveFocusListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddBlurListener(std::function<void(Crafter::Dom::FocusEvent)> callback);
|
||||
void RemoveBlurListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddKeyDownListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
|
||||
void RemoveKeyDownListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddKeyUpListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
|
||||
void RemoveKeyUpListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddKeyPressListener(std::function<void(Crafter::Dom::KeyboardEvent)> callback);
|
||||
void RemoveKeyPressListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddChangeListener(std::function<void(Crafter::Dom::ChangeEvent)> callback);
|
||||
void RemoveChangeListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddSubmitListener(std::function<void()> callback);
|
||||
void RemoveSubmitListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddInputListener(std::function<void(Crafter::Dom::InputEvent)> callback);
|
||||
void RemoveInputListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddResizeListener(std::function<void(Crafter::Dom::ResizeEvent)> callback);
|
||||
void RemoveResizeListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddScrollListener(std::function<void(Crafter::Dom::ScrollEvent)> callback);
|
||||
void RemoveScrollListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddContextMenuListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveContextMenuListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDragStartListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDragStartListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDragEndListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDragEndListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDropListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDropListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDragOverListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDragOverListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDragEnterListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDragEnterListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddDragLeaveListener(std::function<void(Crafter::Dom::MouseEvent)> callback);
|
||||
void RemoveDragLeaveListener(std::int32_t id);
|
||||
|
||||
std::int32_t AddWheelListener(std::function<void(Crafter::Dom::WheelEvent)> callback);
|
||||
void RemoveWheelListener(std::int32_t id);
|
||||
|
||||
protected:
|
||||
// Per-event-kind handler-id lists. Each `Add*Listener` push_backs
|
||||
// the id it returns; `RemoveAllHandlers` walks every list and
|
||||
// calls the matching JS Remove on each id. The lists are indexed
|
||||
// by event kind so a single handler-id collision across kinds
|
||||
// (allowed by the JS bridge — counters are per-event-kind)
|
||||
// doesn't cause a wrong-kind remove.
|
||||
std::vector<std::int32_t> handlerIds_[24];
|
||||
|
||||
// Shared cleanup used by the destructor AND by move-assignment.
|
||||
// Removes every registered handler from both the C++ map and
|
||||
// the JS side, then frees the JS handle. Leaves `ptr == 0` on
|
||||
// return so a second call is a no-op.
|
||||
void RemoveAllHandlersAndFree();
|
||||
};
|
||||
|
||||
class HtmlElement : public HtmlElementPtr {
|
||||
public:
|
||||
// Adopt an existing element by id; the dtor will remove it from
|
||||
// the DOM. Useful when the caller created the element in markup
|
||||
// and wants C++ to own its lifetime from now on.
|
||||
HtmlElement(const std::string_view id);
|
||||
HtmlElement(const std::string_view id, const std::string_view html);
|
||||
|
||||
HtmlElement(HtmlElement&&) noexcept;
|
||||
HtmlElement& operator=(HtmlElement&&) noexcept;
|
||||
|
||||
~HtmlElement();
|
||||
|
||||
// Create a new element and append it to `parent`. Optional `id`
|
||||
// sets the new element's DOM id (use it when you want to look the
|
||||
// element up from non-C++ code later); empty leaves the element
|
||||
// unnamed. Returns an owning handle — the element is removed from
|
||||
// the DOM when this `HtmlElement` is destroyed.
|
||||
static HtmlElement Create(const HtmlElementPtr& parent,
|
||||
std::string_view tagName,
|
||||
std::string_view id = {});
|
||||
|
||||
// Convenience: create a top-level element under document.body.
|
||||
static HtmlElement CreateInBody(std::string_view tagName,
|
||||
std::string_view id = {});
|
||||
|
||||
private:
|
||||
// Hidden ctor used by Create/CreateInBody — takes an already
|
||||
// allocated JS handle via the base class FromHandle tag. Kept
|
||||
// private so user code doesn't construct an HtmlElement from a
|
||||
// raw handle by accident.
|
||||
explicit HtmlElement(HtmlElementPtr::FromHandle h) noexcept
|
||||
: HtmlElementPtr(h) {}
|
||||
};
|
||||
}
|
||||
#endif // CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
Loading…
Add table
Add a link
Reference in a new issue