summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-15 18:43:12 +0900
committerAdam Malczewski <[email protected]>2026-06-15 18:43:12 +0900
commite73dfd27ba95d42b80c90d622250ad1f3be9bf33 (patch)
tree309ecf439e66726598847f5d06b767fa47586c9a
parent38f0091c64523cbda8c080c0525c74e725f40c87 (diff)
downloadunbox-e73dfd27ba95d42b80c90d622250ad1f3be9bf33.tar.gz
unbox-e73dfd27ba95d42b80c90d622250ad1f3be9bf33.zip
kernel(rml-compositing): surface-element box readback, per-row drag, xdg-decoration
- SurfaceElement::rendered_width()/height(): read back the RCSS-resolved root <img> 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).
-rw-r--r--packages/kernel/include/unbox/kernel/ui.hpp45
-rw-r--r--packages/kernel/include/unbox/kernel/wlr.hpp5
-rw-r--r--packages/kernel/src/ui_substrate.cpp80
-rw-r--r--packages/kernel/src/ui_substrate.hpp5
4 files changed, 135 insertions, 0 deletions
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<void(std::size_t row)> 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 <name> AND passing the row index:
+ //
+ // <div ... style="drag: drag;"
+ // data-event-dragstart="<name>(it_index)"
+ // data-event-drag="<name>(it_index)"
+ // data-event-dragend="<name>(it_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<void(std::size_t row, DragPhase phase, double x, double y)> 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(<list-name>) 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 <img> 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 <img src=source_uri()> 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 <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <wlr/types/wlr_xdg_shell.h>
+// 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 <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/util/log.h>
#include <wlr/version.h>
#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 <list>
#include <string>
#include <unordered_map>
+#include <utility>
#include <vector>
// The installed asset root, resolved against for a RELATIVE UiSurfaceSpec::
@@ -447,6 +448,15 @@ struct Surface {
Substrate::Impl* owner;
};
std::list<ListEventBinding> 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<void(std::size_t, UiSurface::DragPhase, double, double)> cb;
+ ExtensionId who;
+ Substrate::Impl* owner;
+ };
+ std::list<ListDragBinding> 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 <img> 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<int, int>;
+
// ---- surface-element input-back ----
// Resolve a hovered RmlUi element to the surface-element NODE it samples (its
// <img src> 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<int, int> {
+ // Read back the rectangle RCSS laid the root <img> 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<int>(root_img->GetClientWidth() + 0.5F);
+ const int h = static_cast<int>(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<void(std::size_t, UiSurface::DragPhase, double, double)> 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<std::size_t>(args[0].Get<int>());
+ }
+ 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(row, phase, x, y);
+ } catch (...) {
+ if (binding->owner->disable) {
+ binding->owner->disable(binding->who);
+ }
+ }
+ });
+}
+
void SurfaceHandle::on_touch_mode_changed(std::function<void(bool)> 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<void()> handler) override;
@@ -151,6 +153,9 @@ public:
std::function<bool(std::size_t)> getter) override;
void bind_list_event(std::string_view list, std::string_view event,
std::function<void(std::size_t)> callback) override;
+ void bind_list_drag(
+ std::string_view list, std::string_view name,
+ std::function<void(std::size_t, DragPhase, double, double)> callback) override;
void on_touch_mode_changed(std::function<void(bool)> callback) override;
void dirty(std::string_view name) override;
void dirty() override;