/* 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 */ module; #include "vulkan/vulkan.h" module Crafter.Graphics:UIScene_impl; import :UIScene; import :Window; import :Types; import :DescriptorHeapVulkan; import :UIRenderer; import :UIHit; import :UILayout; import :UIDrawList; import :UIWidget; import Crafter.Event; import std; using namespace Crafter; using namespace Crafter::UI; UIScene::~UIScene() { // Release listeners before the rest of the scene tears down. mouseListener_.reset(); updateListener_.reset(); textListener_.reset(); keyListener_.reset(); focused_ = nullptr; if (window_) { // De-register the renderer pass. auto& v = window_->passes; v.erase(std::remove(v.begin(), v.end(), static_cast(&renderer)), v.end()); // Clear the descriptor-heap pointer if we owned it; the heap's // destructor releases its Vulkan buffers on its own. if (ownsHeap_ && window_->descriptorHeap == &ownedHeap_) { window_->descriptorHeap = nullptr; } } } float UIScene::WindowScale() const { if (!window_) return 1.0f; #ifdef CRAFTER_GRAPHICS_WINDOW_WAYLAND return window_->scale; #else return 1.0f; #endif } void UIScene::Initialize(Window& window, const std::filesystem::path& spvPath) { window_ = &window; // Auto-create a heap for UI-only apps. Generous defaults so most // user-augmented heaps will fit too — if the user wants to share with // 3D content, they should pre-create their own heap and attach it // before calling Initialize. if (!window.descriptorHeap) { ownedHeap_.Initialize(/*images*/ 388, /*buffers*/ 35, /*samplers*/ 17); window.descriptorHeap = &ownedHeap_; ownsHeap_ = true; } // One-shot init — needed by the atlas image transition. Each // StartInit/FinishInit pair reuses the per-frame command buffer. VkCommandBuffer cmd = window.StartInit(); renderer.Initialize(window, cmd, spvPath); window.FinishInit(); // Register as a RenderPass (after any other pass already in // window.passes — typically RTPass for mixed scenes). window.passes.push_back(&renderer); // Mouse: update focus to the topmost focusable under the cursor (or // null if none), then dispatch the click via the bubble chain. mouseListener_ = std::make_unique>( &window.onMouseLeftClick, [this]() { if (!root_) return; float x = window_->currentMousePos.x; float y = window_->currentMousePos.y; Widget* hit = UI::HitTest(*root_, x, y); Widget* focusTarget = nullptr; for (Widget* w = hit; w != nullptr; w = w->parent) { if (w->IsFocusable()) { focusTarget = w; break; } } SetFocus(focusTarget); UI::DispatchClick(*root_, x, y); } ); // Text input: only the currently-focused widget receives it. textListener_ = std::make_unique>( &window.onTextInput, [this](std::string_view t) { if (focused_) focused_->OnTextInput(t); } ); // Non-character keys (Backspace, arrows, Enter, …). keyListener_ = std::make_unique>( &window.onAnyKeyDown, [this](CrafterKeys key) { if (focused_) focused_->OnKeyDown(key); } ); // Per-frame: re-layout, emit, push items. We capture FrameTime here // so we can advance the scene's clock (caret blink, animations). updateListener_ = std::make_unique>( &window.onUpdate, [this](FrameTime ft) { elapsedSec_ += static_cast(ft.delta.count()); RebuildFrame(); } ); } void UIScene::SetFocus(Widget* w) { if (w == focused_) return; if (focused_) focused_->OnBlur(); focused_ = w; if (focused_) focused_->OnFocus(); } void UIScene::RebuildFrame() { if (!root_ || !window_) return; float sc = WindowScale(); // Layout the tree against the current surface size. UI::RunLayout( *root_, { static_cast(window_->width), static_cast(window_->height) }, sc ); // Emit draw items. drawList.Reset(); drawList.atlas = &renderer.atlas; drawList.bindlessBaseHeapIdx = renderer.BindlessBaseHeapIdx(); drawList.scale = sc; drawList.time = elapsedSec_; if (background_) { drawList.AddRect( { 0, 0, static_cast(window_->width), static_cast(window_->height) }, *background_ ); } UI::EmitTree(*root_, drawList); // Stage to GPU. renderer.SetItems(drawList.items); }