/* 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 */ export module Crafter.Graphics:UIDrawList; import std; import :UILength; import :UIWidget; export namespace Crafter::UI { class FontAtlas; // forward decl (full def in :UIAtlas) // Item type tags. Must match the shader-side constants exactly. enum class ItemType : std::uint32_t { Rect = 0, RoundRect = 1, Glyph = 2, Image = 3, ClipPush = 5, ClipPop = 6, }; // GPU-bound draw item. Layout matches the shader's UIItem struct under // GL_EXT_scalar_block_layout (no std140/std430 padding). Keep this in // sync with shaders/ui.comp.glsl. // // Field meanings by ItemType: // Rect: posPx, sizePx, color (alpha-premultiplied). // RoundRect: same as Rect + cornerRadiusPx. // Glyph: posPx/sizePx = on-screen quad; uvRect = atlas region; // color tints the SDF sample; cornerRadiusPx unused. // Image: posPx/sizePx = quad; uvRect = source rect (0..1); // imageIdx = bindless slot offset; color tints. // ClipPush: posPx/sizePx = clip rect to push (intersected with current). // ClipPop: fields ignored. struct UIItem { std::uint32_t type; // ItemType std::uint32_t flags; float posPx[2]; float sizePx[2]; float color[4]; float colorB[4]; float uvRect[4]; std::uint32_t imageIdx; std::uint32_t cornerRadiusPx; float reserved[2]; }; static_assert(sizeof(UIItem) == 88, "UIItem size must match shader-side struct"); // CPU-side accumulator. Widgets call `Add(...)` (or convenience helpers) // during their Emit pass; the renderer copies the resulting buffer into // the per-frame mapped SSBO and dispatches the compute shader. class DrawList { public: std::vector items; // Set by the renderer before EmitTree(). Widgets that draw text or // images consult these — without an atlas, glyph emission is a // no-op (useful for layout-only debug dumps). FontAtlas* atlas = nullptr; std::uint32_t bindlessBaseHeapIdx = 0; // base heap slot for Image widgets float scale = 1.0f; // device scale (mirrors LayoutContext::scale) void Reset() { items.clear(); } void Add(const UIItem& it) { items.push_back(it); } // Convenience constructors for common items. These keep widget // Emit code short and self-documenting. void AddRect(Rect r, Color c) { UIItem it{}; it.type = static_cast(ItemType::Rect); it.posPx[0] = r.x; it.posPx[1] = r.y; it.sizePx[0] = r.w; it.sizePx[1] = r.h; // Premultiply alpha so the shader's "OVER" operator works without // a per-pixel multiply. it.color[0] = c.r * c.a; it.color[1] = c.g * c.a; it.color[2] = c.b * c.a; it.color[3] = c.a; items.push_back(it); } void AddRoundRect(Rect r, Color c, float radiusPx) { UIItem it{}; it.type = static_cast(ItemType::RoundRect); it.posPx[0] = r.x; it.posPx[1] = r.y; it.sizePx[0] = r.w; it.sizePx[1] = r.h; it.color[0] = c.r * c.a; it.color[1] = c.g * c.a; it.color[2] = c.b * c.a; it.color[3] = c.a; it.cornerRadiusPx = static_cast(radiusPx); items.push_back(it); } // Glyph item: `quad` is the glyph's on-screen rect, `atlasUV` is // its (x, y, w, h) region in 0..1 atlas-UV space. void AddGlyph(Rect quad, Color color, std::array atlasUV) { UIItem it{}; it.type = static_cast(ItemType::Glyph); it.posPx[0] = quad.x; it.posPx[1] = quad.y; it.sizePx[0] = quad.w; it.sizePx[1] = quad.h; it.color[0] = color.r * color.a; it.color[1] = color.g * color.a; it.color[2] = color.b * color.a; it.color[3] = color.a; it.uvRect[0] = atlasUV[0]; it.uvRect[1] = atlasUV[1]; it.uvRect[2] = atlasUV[2]; it.uvRect[3] = atlasUV[3]; items.push_back(it); } // Image item: `imageHeapOffset` is added to the renderer's // bindless-base slot at draw time to find the right descriptor. void AddImage(Rect quad, Color tint, std::uint32_t imageHeapOffset, std::array sourceUV = {0, 0, 1, 1}) { UIItem it{}; it.type = static_cast(ItemType::Image); it.posPx[0] = quad.x; it.posPx[1] = quad.y; it.sizePx[0] = quad.w; it.sizePx[1] = quad.h; it.color[0] = tint.r * tint.a; it.color[1] = tint.g * tint.a; it.color[2] = tint.b * tint.a; it.color[3] = tint.a; it.uvRect[0] = sourceUV[0]; it.uvRect[1] = sourceUV[1]; it.uvRect[2] = sourceUV[2]; it.uvRect[3] = sourceUV[3]; it.imageIdx = imageHeapOffset; items.push_back(it); } // Clip stack — emit a ClipPush at the start of the clipped region // and a matching ClipPop at the end. The shader maintains a small // fixed-size stack and intersects pushes with the existing clip. void PushClip(Rect r) { UIItem it{}; it.type = static_cast(ItemType::ClipPush); it.posPx[0] = r.x; it.posPx[1] = r.y; it.sizePx[0] = r.w; it.sizePx[1] = r.h; items.push_back(it); } void PopClip() { UIItem it{}; it.type = static_cast(ItemType::ClipPop); items.push_back(it); } }; // Walk the laid-out tree and emit every widget's items. inline void EmitTree(const Widget& root, DrawList& dl) { root.Emit(dl); } }