166 lines
5.2 KiB
C++
166 lines
5.2 KiB
C++
/*
|
|
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<RenderPass*>(&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<EventListener<void>>(
|
|
&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<EventListener<const std::string_view>>(
|
|
&window.onTextInput,
|
|
[this](std::string_view t) {
|
|
if (focused_) focused_->OnTextInput(t);
|
|
}
|
|
);
|
|
|
|
// Non-character keys (Backspace, arrows, Enter, …).
|
|
keyListener_ = std::make_unique<EventListener<CrafterKeys>>(
|
|
&window.onAnyKeyDown,
|
|
[this](CrafterKeys key) {
|
|
if (focused_) focused_->OnKeyDown(key);
|
|
}
|
|
);
|
|
|
|
// Per-frame: re-layout, emit, push items.
|
|
updateListener_ = std::make_unique<EventListener<FrameTime>>(
|
|
&window.onUpdate,
|
|
[this](FrameTime) { 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<float>(window_->width), static_cast<float>(window_->height) },
|
|
sc
|
|
);
|
|
|
|
// Emit draw items.
|
|
drawList.Reset();
|
|
drawList.atlas = &renderer.atlas;
|
|
drawList.bindlessBaseHeapIdx = renderer.BindlessBaseHeapIdx();
|
|
drawList.scale = sc;
|
|
if (background_) {
|
|
drawList.AddRect(
|
|
{ 0, 0, static_cast<float>(window_->width), static_cast<float>(window_->height) },
|
|
*background_
|
|
);
|
|
}
|
|
UI::EmitTree(*root_, drawList);
|
|
|
|
// Stage to GPU.
|
|
renderer.SetItems(drawList.items);
|
|
}
|