diff options
| author | Adam Malczewski <[email protected]> | 2026-06-14 12:20:24 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-14 12:20:24 +0900 |
| commit | a044b44c936569db7e790546b81b4792c8e72058 (patch) | |
| tree | 00f048830f8f77c9d6e8ab11cdc0729b3ed8a56c /packages/kernel/src | |
| parent | 4fd36d643f9c66455e245eb65f5f9f916200ed5a (diff) | |
| download | unbox-a044b44c936569db7e790546b81b4792c8e72058.tar.gz unbox-a044b44c936569db7e790546b81b4792c8e72058.zip | |
kernel: add UiSurface::bind_drag (RmlUi drag events with surface-local coords)
Forwards RmlUi Dragstart/Drag/Dragend for a named callback as DragPhase
{start,move,end} with surface-local x/y, so an extension can drive an
interactive drag from a captured ui-surface touch (the touch bus never
sees it). Mirrors bind_event's error-isolation + hot-reload handling.
Diffstat (limited to 'packages/kernel/src')
| -rw-r--r-- | packages/kernel/src/server.cpp | 4 | ||||
| -rw-r--r-- | packages/kernel/src/ui_substrate.cpp | 102 | ||||
| -rw-r--r-- | packages/kernel/src/ui_substrate.hpp | 10 |
3 files changed, 114 insertions, 2 deletions
diff --git a/packages/kernel/src/server.cpp b/packages/kernel/src/server.cpp index cbd23c3..66f2cc3 100644 --- a/packages/kernel/src/server.cpp +++ b/packages/kernel/src/server.cpp @@ -105,6 +105,10 @@ auto Server::ui_click_element(const char* tag, int index) -> bool { return impl_->substrate != nullptr && impl_->substrate->click_element(tag, index); } +auto Server::ui_drag_element(const char* tag, int index, double dx, double dy) -> bool { + return impl_->substrate != nullptr && impl_->substrate->drag_element(tag, index, dx, dy); +} + auto Server::ui_reload_surface() -> bool { return impl_->substrate != nullptr && impl_->substrate->reload_first_surface(); } diff --git a/packages/kernel/src/ui_substrate.cpp b/packages/kernel/src/ui_substrate.cpp index 1f289bd..4c9efe8 100644 --- a/packages/kernel/src/ui_substrate.cpp +++ b/packages/kernel/src/ui_substrate.cpp @@ -9,7 +9,9 @@ #include <RmlUi/Core/DataVariable.h> #include <RmlUi/Core/Element.h> #include <RmlUi/Core/ElementDocument.h> +#include <RmlUi/Core/Event.h> #include <RmlUi/Core/Factory.h> +#include <RmlUi/Core/ID.h> #include <RmlUi/Core/SystemInterface.h> #include <RmlUi/Core/Variant.h> @@ -403,6 +405,18 @@ struct Surface { Substrate::Impl* owner; }; std::list<EventBinding> event_bindings; + // Drag bindings: one callback fed by RMLUi's Dragstart/Drag/Dragend for the + // element(s) authoring data-event-drag{start,,end}=<name>. The phase is read + // off the live Rml::Event id; x/y are the event's mouse_x/mouse_y which the + // substrate already feeds the context in surface-LOCAL coords (ctx_motion + // subtracts s.x/s.y), so they ARE surface-local px (origin top-left). Same + // error-isolation boundary as EventBinding. + struct DragBinding { + std::function<void(UiSurface::DragPhase, double, double)> cb; + ExtensionId who; + Substrate::Impl* owner; + }; + std::list<DragBinding> drag_bindings; // List bindings (slice 10 / b2). A bound list is a runtime-sized indexed // sequence the document iterates with data-for; each row exposes named @@ -857,8 +871,10 @@ void Substrate::Impl::unwatch_surface(Surface* s) { bool Substrate::Impl::reload_surface(Surface& s) { // Only file-backed, already-loaded surfaces reload. The data model + the - // extension's registered bind_*/bind_list*/bind_event getters are CONTEXT- - // and substrate-owned, so they survive — we never touch s.ctor/s.*_bindings. + // extension's registered bind_*/bind_list*/bind_event/bind_drag getters are + // CONTEXT- and substrate-owned, so they survive — we never touch + // s.ctor/s.*_bindings (the reloaded document re-binds data-event-drag* to + // the still-present model callback by name). if (s.context == nullptr || s.resolved_path.empty() || !s.doc_loaded) { return false; } @@ -1762,6 +1778,36 @@ auto Substrate::click_element(const char* tag, int index) -> bool { return false; } +auto Substrate::drag_element(const char* tag, int index, double dx, double dy) -> bool { + for (Surface& s : impl_->surfaces) { + if (s.document == nullptr || s.context == nullptr) { + continue; + } + Rml::ElementList elements; + s.document->GetElementsByTagName(elements, Rml::String(tag)); + if (index < 0 || index >= static_cast<int>(elements.size())) { + return false; + } + Rml::Element* el = elements[static_cast<std::size_t>(index)]; + // The element's content-box centre in context (= surface-local) coords: + // the same space ctx_motion feeds and the same space mouse_x/mouse_y are + // reported in, so the delivered drag coords should match these moves. + const Rml::Vector2f origin = el->GetAbsoluteOffset(Rml::BoxArea::Content); + const int cx = static_cast<int>(origin.x + el->GetClientWidth() / 2.0F); + const int cy = static_cast<int>(origin.y + el->GetClientHeight() / 2.0F); + // Press at the centre, then move PAST RmlUi's drag threshold so it emits + // Dragstart + Drag; a second move proves move tracks travel; release ends. + s.context->ProcessMouseMove(cx, cy, 0); + s.context->ProcessMouseButtonDown(0, 0); + s.context->ProcessMouseMove(cx + static_cast<int>(dx), cy + static_cast<int>(dy), 0); + s.context->ProcessMouseMove(cx + static_cast<int>(dx) * 2, + cy + static_cast<int>(dy) * 2, 0); + s.context->ProcessMouseButtonUp(0, 0); + return true; + } + return false; +} + auto Substrate::reload_first_surface() -> bool { for (Surface& s : impl_->surfaces) { return impl_->reload_surface(s); @@ -1929,6 +1975,58 @@ void SurfaceHandle::bind_event(std::string_view name, std::function<void()> call } namespace { +// Map a live RMLUi drag event to the public DragPhase. Pure: only the three +// drag ids are routed here (the binding hooks no other event), so an unknown id +// is treated as a move (the safe middle phase) rather than dropped. Returns +// false for an id we should ignore entirely (none currently). +auto drag_phase_for(Rml::EventId id, UiSurface::DragPhase& out) -> bool { + switch (id) { + case Rml::EventId::Dragstart: out = UiSurface::DragPhase::start; return true; + case Rml::EventId::Dragend: out = UiSurface::DragPhase::end; return true; + case Rml::EventId::Drag: out = UiSurface::DragPhase::move; return true; + default: out = UiSurface::DragPhase::move; return true; + } +} +} // namespace + +void SurfaceHandle::bind_drag(std::string_view name, + std::function<void(UiSurface::DragPhase, double, double)> callback) { + Surface& s = *surface_; + if (!s.ctor) { + return; + } + s.drag_bindings.push_back({std::move(callback), s.who, s.owner}); + Surface::DragBinding* binding = &s.drag_bindings.back(); + // One model callback name carries all three phases; the document authors + // data-event-dragstart / data-event-drag / data-event-dragend all naming + // <name> on a drag-enabled element (RCSS `drag: drag;`). The phase is read + // off the live event id; mouse_x/mouse_y are already surface-local px + // (ctx_motion feeds the context coords relative to the surface origin), so + // they pass straight through as x/y. Same error-isolation boundary as + // bind_event (a throw disables the owning extension only). Survives dev + // hot-reload like every other binding: registered once on the open ctor, + // re-applied by the substrate against the reloaded document. + s.ctor.BindEventCallback( + std::string(name), + [binding](Rml::DataModelHandle, Rml::Event& ev, const Rml::VariantList&) { + try { + if (!binding->cb) { + return; + } + UiSurface::DragPhase phase = UiSurface::DragPhase::move; + drag_phase_for(ev.GetId(), phase); + const double x = static_cast<double>(ev.GetParameter<float>("mouse_x", 0.0F)); + const double y = static_cast<double>(ev.GetParameter<float>("mouse_y", 0.0F)); + binding->cb(phase, x, y); + } catch (...) { + if (binding->owner->disable) { + binding->owner->disable(binding->who); + } + } + }); +} + +namespace { // Find an existing list binding by name in the surface, or nullptr. auto find_list(Surface& s, std::string_view name) -> Surface::ListBinding* { for (auto& b : s.list_bindings) { diff --git a/packages/kernel/src/ui_substrate.hpp b/packages/kernel/src/ui_substrate.hpp index 32072b4..d08c7de 100644 --- a/packages/kernel/src/ui_substrate.hpp +++ b/packages/kernel/src/ui_substrate.hpp @@ -102,6 +102,8 @@ public: void bind_bool(std::string_view name, std::function<bool()> getter) override; void bind_string(std::string_view name, std::function<std::string()> getter) override; void bind_event(std::string_view name, std::function<void()> callback) override; + void bind_drag(std::string_view name, + std::function<void(DragPhase, double, double)> callback) override; void bind_list(std::string_view name, std::function<std::size_t()> count) override; void bind_list_string(std::string_view list, std::string_view field, std::function<std::string(std::size_t)> getter) override; @@ -212,6 +214,14 @@ public: // Click the index-th `tag` element in the first surface's document (fires // its data-event-click). False if no such element. Drives a row event. auto click_element(const char* tag, int index) -> bool; + // Test seam: synthesize a real RmlUi drag on the index-th `tag` element of + // the first surface (press at the element's content centre, move PAST RmlUi's + // drag threshold by (dx,dy), then release), so a bind_drag callback receives + // start/move/end with surface-LOCAL coords — the same path a real captured + // pointer/touch drag takes, without an input device. False if no such + // element / no document. GL-path only (no-op skip when unavailable). Test + // instrumentation; single-thread only. + auto drag_element(const char* tag, int index, double dx, double dy) -> bool; // Test seam: synchronously reload the first surface's document from its file // (the same reload the dev inotify watcher drives), so tests trigger reload // deterministically without racing real filesystem events. Returns true if a |
