From e73dfd27ba95d42b80c90d622250ad1f3be9bf33 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 15 Jun 2026 18:43:12 +0900 Subject: kernel(rml-compositing): surface-element box readback, per-row drag, xdg-decoration - SurfaceElement::rendered_width()/height(): read back the RCSS-resolved root content box (the substrate already computes it for popup placement) so a window manager can size a client to its on-screen tile. Reading geometry, not computing it -- RCSS still owns layout. - UiSurface::bind_list_drag(list, name, cb(row, phase, x, y)): the per-row drag binding (list analogue of bind_drag + bind_list_event), for per-window move and resize in a data-for list. - Expose wlr_xdg_decoration_v1 through the wlr wrapper (for ext-xdg-shell's server-side-decoration negotiation). --- packages/kernel/include/unbox/kernel/ui.hpp | 45 ++++++++++++++++ packages/kernel/include/unbox/kernel/wlr.hpp | 5 ++ packages/kernel/src/ui_substrate.cpp | 80 ++++++++++++++++++++++++++++ packages/kernel/src/ui_substrate.hpp | 5 ++ 4 files changed, 135 insertions(+) diff --git a/packages/kernel/include/unbox/kernel/ui.hpp b/packages/kernel/include/unbox/kernel/ui.hpp index e0800a3..b9261d3 100644 --- a/packages/kernel/include/unbox/kernel/ui.hpp +++ b/packages/kernel/include/unbox/kernel/ui.hpp @@ -196,6 +196,32 @@ public: virtual void bind_list_event(std::string_view list, std::string_view event, std::function callback) = 0; + // bind_list_drag(list, name, callback): the per-ROW drag binding — the list + // analogue of bind_drag (which targets a single named element). Author it on + // a ROW element with the RCSS `drag: drag;` property plus the three + // data-event-drag* attributes all naming AND passing the row index: + // + //
+ // + // The substrate delivers each of RmlUi's Dragstart/Drag/Dragend for that row + // to ONE callback tagged with the phase, the row index (from it_index), and + // x/y = the pointer in THIS surface's LOCAL document coordinates (px, origin = + // surface top-left), exactly as bind_drag. Use it for per-window move + // (titlebar) and resize (grips): the row index identifies which list element + // is being dragged. A tap (no travel past RmlUi's threshold) fires + // data-event-click, not this — so tap and drag coexist (same rule as + // bind_drag). Invoked on the event-loop thread; a throwing callback is caught + // at the substrate boundary and disables YOUR extension only — never the + // session. Register before the first frame; re-registering the same name + // replaces it. `list` is documentary (the event name is model-global, like + // bind_list_event) — keep names unique per surface. + virtual void bind_list_drag( + std::string_view list, std::string_view name, + std::function callback) = 0; + // Mark a bound scalar changed so the substrate re-reads its getter and // re-renders on the next frame. dirty() with no name marks ALL bound // scalars dirty (use sparingly). For a list, dirty() re-reads @@ -377,6 +403,25 @@ public: [[nodiscard]] virtual auto width() const -> int = 0; [[nodiscard]] virtual auto height() const -> int = 0; + // The on-screen box (px) RCSS laid this element's ROOT out to — the + // resolved content rectangle the live texture is actually drawn into, in the + // hosting ui surface's local document coordinates (px == dp; the substrate + // keeps the dp-ratio at 1.0). This is the kernel READING BACK the rectangle + // RCSS computed (the substrate already resolves it to place child popups); it + // never lets C++ COMPUTE a window's geometry — layout still lives entirely in + // RCSS. A window manager uses it to feed the tile size back to the client as a + // render resolution (ext-xdg-shell::Toplevel::set_size), so the client paints + // AT the tile box instead of being letterbox-scaled into it. + // + // Returns 0 when the element is not (yet) hosted by any laid-out document: + // before the first frame has rendered, or if no ui surface currently authors + // an for it. If several surfaces host it, the FIRST + // found is reported (the window field hosts each element in exactly one). The + // value tracks the LIVE layout, so during an RCSS transition it changes each + // frame and settles when the animation completes. + [[nodiscard]] virtual auto rendered_width() const -> int = 0; + [[nodiscard]] virtual auto rendered_height() const -> int = 0; + // Give this element's ROOT client surface keyboard focus on the kernel's // seat: subsequent keys route to it (via the seat the kernel already drives). // This is the seat MECHANISM only — focus POLICY (which window, click-to- diff --git a/packages/kernel/include/unbox/kernel/wlr.hpp b/packages/kernel/include/unbox/kernel/wlr.hpp index ca99c17..4a25c9d 100644 --- a/packages/kernel/include/unbox/kernel/wlr.hpp +++ b/packages/kernel/include/unbox/kernel/wlr.hpp @@ -84,6 +84,11 @@ extern "C" { #include #include #include +// xdg-decoration (server-side decorations): lets the compositor tell clients NOT +// to draw their own titlebars (CSD), so the RML window field's chrome is the only +// decoration. ext-xdg-shell creates the manager + forces SERVER_SIDE when RML +// compositing draws the chrome. Plain declarations only — static-blanking inert. +#include #include #include #undef namespace diff --git a/packages/kernel/src/ui_substrate.cpp b/packages/kernel/src/ui_substrate.cpp index 5971c56..a6a5bfa 100644 --- a/packages/kernel/src/ui_substrate.cpp +++ b/packages/kernel/src/ui_substrate.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include // The installed asset root, resolved against for a RELATIVE UiSurfaceSpec:: @@ -447,6 +448,15 @@ struct Surface { Substrate::Impl* owner; }; std::list list_event_bindings; + // Per-row drag callbacks (bind_list_drag): combine the drag phase + surface- + // local x/y (like DragBinding) with the row index from the data expression's + // first argument (like ListEventBinding). Same error-isolation boundary. + struct ListDragBinding { + std::function cb; + ExtensionId who; + Substrate::Impl* owner; + }; + std::list list_drag_bindings; // touch-mode-changed notification (one per surface; see ui.hpp). Fired on a // transition, error-isolated to `who`. touch-mode does NO visual scaling @@ -865,6 +875,13 @@ struct Substrate::Impl { // after the tree was re-walked. Idempotent per frame. void layout_surface_element_children(SurfaceElementState& el); + // The resolved on-screen content box (px) of this element's ROOT in the + // FIRST ui surface document that hosts it (ui.hpp SurfaceElement:: + // rendered_width/height — the kernel reading back RCSS's computed rectangle). + // {0,0} when no laid-out document hosts the element yet. + [[nodiscard]] auto surface_element_rendered_size(const SurfaceElementState& el) const + -> std::pair; + // ---- surface-element input-back ---- // Resolve a hovered RmlUi element to the surface-element NODE it samples (its // is a node URI), walking up parents. Returns null if the pick is @@ -1942,6 +1959,25 @@ void Substrate::Impl::layout_surface_element_children(SurfaceElementState& el) { } } +auto Substrate::Impl::surface_element_rendered_size(const SurfaceElementState& el) const + -> std::pair { + // Read back the rectangle RCSS laid the root out to (the same resolved + // content box used for child placement above). First hosting document wins; + // {0,0} before any document has laid it out. GetClientWidth/Height are the + // element's content-box dimensions in document px — exactly the texture's + // drawn box. Rounded to whole px (a client buffer size is integral). + for (const Surface& surf : surfaces) { + Rml::Element* root_img = find_img_by_src(surf.document, el.root.uri); + if (root_img == nullptr) { + continue; + } + const int w = static_cast(root_img->GetClientWidth() + 0.5F); + const int h = static_cast(root_img->GetClientHeight() + 0.5F); + return {w, h}; + } + return {0, 0}; +} + // ---- Surface-element input-back (pick -> surface-local -> wl_seat) ----------- // // Port of the spike's route_point: the substrate already feeds pointer/touch @@ -3061,6 +3097,44 @@ void SurfaceHandle::bind_list_event(std::string_view /*list*/, std::string_view }); } +void SurfaceHandle::bind_list_drag( + std::string_view /*list*/, std::string_view name, + std::function callback) { + // The per-row drag binding = bind_drag (phase from the event id, x/y from the + // event's surface-local mouse_x/mouse_y) + bind_list_event (row index from the + // data expression's first argument, it_index). The event name is model-global + // (like bind_list_event) so `list` is documentary; keep names unique. Survives + // dev hot-reload like every binding (registered once on the open ctor). + Surface& s = *surface_; + if (!s.ctor) { + return; + } + s.list_drag_bindings.push_back({std::move(callback), s.who, s.owner}); + Surface::ListDragBinding* binding = &s.list_drag_bindings.back(); + s.ctor.BindEventCallback( + std::string(name), + [binding](Rml::DataModelHandle, Rml::Event& ev, const Rml::VariantList& args) { + try { + if (!binding->cb) { + return; + } + UiSurface::DragPhase phase = UiSurface::DragPhase::move; + drag_phase_for(ev.GetId(), phase); + std::size_t row = 0; + if (!args.empty()) { + row = static_cast(args[0].Get()); + } + const double x = static_cast(ev.GetParameter("mouse_x", 0.0F)); + const double y = static_cast(ev.GetParameter("mouse_y", 0.0F)); + binding->cb(row, phase, x, y); + } catch (...) { + if (binding->owner->disable) { + binding->owner->disable(binding->who); + } + } + }); +} + void SurfaceHandle::on_touch_mode_changed(std::function callback) { surface_->touch_mode_cb = std::move(callback); } @@ -3169,6 +3243,12 @@ SurfaceElementHandle::~SurfaceElementHandle() { auto SurfaceElementHandle::source_uri() const -> std::string { return state_->root.uri; } auto SurfaceElementHandle::width() const -> int { return state_->root.width; } auto SurfaceElementHandle::height() const -> int { return state_->root.height; } +auto SurfaceElementHandle::rendered_width() const -> int { + return substrate_->impl_->surface_element_rendered_size(*state_).first; +} +auto SurfaceElementHandle::rendered_height() const -> int { + return substrate_->impl_->surface_element_rendered_size(*state_).second; +} void SurfaceElementHandle::focus_keyboard() { substrate_->impl_->focus_keyboard(*state_); } diff --git a/packages/kernel/src/ui_substrate.hpp b/packages/kernel/src/ui_substrate.hpp index bdedd9f..3727f7e 100644 --- a/packages/kernel/src/ui_substrate.hpp +++ b/packages/kernel/src/ui_substrate.hpp @@ -108,6 +108,8 @@ public: [[nodiscard]] auto source_uri() const -> std::string override; [[nodiscard]] auto width() const -> int override; [[nodiscard]] auto height() const -> int override; + [[nodiscard]] auto rendered_width() const -> int override; + [[nodiscard]] auto rendered_height() const -> int override; void focus_keyboard() override; void on_pressed(std::function handler) override; @@ -151,6 +153,9 @@ public: std::function getter) override; void bind_list_event(std::string_view list, std::string_view event, std::function callback) override; + void bind_list_drag( + std::string_view list, std::string_view name, + std::function callback) override; void on_touch_mode_changed(std::function callback) override; void dirty(std::string_view name) override; void dirty() override; -- cgit v1.2.3