2026-05-02 21:08:20 +02:00
|
|
|
// Smoke test for the Tier 1+2+3 UI architecture. Opens a window, draws a
|
|
|
|
|
// background, a button (Tier 3 component), a slider (Tier 3 component), a
|
|
|
|
|
// progress bar (Tier 3 component), and a circle that follows the mouse
|
|
|
|
|
// (Tier 2 standard shader, dispatched directly). Hit-testing for the button
|
|
|
|
|
// and slider is the user's responsibility — see the onMouseMove listener.
|
|
|
|
|
|
2026-05-18 04:58:52 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-02 21:08:20 +02:00
|
|
|
#include "vulkan/vulkan.h"
|
2026-05-18 04:58:52 +02:00
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
|
|
|
|
|
import Crafter.Graphics;
|
|
|
|
|
import Crafter.Event;
|
|
|
|
|
import std;
|
|
|
|
|
using namespace Crafter;
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
Device::Initialize();
|
2026-05-18 04:58:52 +02:00
|
|
|
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
|
|
|
|
static Window window(1280, 720, "Hello UI");
|
|
|
|
|
#else
|
2026-05-02 21:08:20 +02:00
|
|
|
Window window(1280, 720, "Hello UI");
|
2026-05-18 04:58:52 +02:00
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
|
2026-05-18 04:58:52 +02:00
|
|
|
auto init = window.StartInit();
|
2026-05-02 21:08:20 +02:00
|
|
|
|
2026-05-18 04:58:52 +02:00
|
|
|
GraphicsDescriptorHeap heap;
|
2026-05-02 21:08:20 +02:00
|
|
|
heap.Initialize(/*images*/ 16, /*buffers*/ 16, /*samplers*/ 4);
|
|
|
|
|
window.descriptorHeap = &heap;
|
|
|
|
|
|
|
|
|
|
Font font("font.ttf");
|
|
|
|
|
|
|
|
|
|
FontAtlas atlas;
|
|
|
|
|
atlas.Initialize(init);
|
|
|
|
|
|
|
|
|
|
UIRenderer ui;
|
|
|
|
|
ui.fontAtlas = &atlas;
|
|
|
|
|
ui.Initialize(window, heap, init);
|
|
|
|
|
window.passes.push_back(&ui);
|
|
|
|
|
|
|
|
|
|
// User-owned per-shader buffers. Mapped, written each frame, dispatched
|
|
|
|
|
// by the user. Capacity is up to the user; resize means re-Register.
|
2026-05-18 04:58:52 +02:00
|
|
|
GraphicsBuffer<QuadItem, true> quadsBuf;
|
|
|
|
|
GraphicsBuffer<CircleItem, true> circlesBuf;
|
|
|
|
|
GraphicsBuffer<GlyphItem, true> glyphsBuf;
|
|
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-02 21:08:20 +02:00
|
|
|
quadsBuf.Create(
|
|
|
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 256);
|
|
|
|
|
circlesBuf.Create(
|
|
|
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 64);
|
|
|
|
|
glyphsBuf.Create(
|
|
|
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
|
|
|
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 4096);
|
2026-05-18 04:58:52 +02:00
|
|
|
#else
|
|
|
|
|
quadsBuf.Create(256);
|
|
|
|
|
circlesBuf.Create(64);
|
|
|
|
|
glyphsBuf.Create(4096);
|
|
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
|
|
|
|
|
auto quadsSlot = ui.RegisterBuffer(quadsBuf);
|
|
|
|
|
auto circlesSlot = ui.RegisterBuffer(circlesBuf);
|
|
|
|
|
auto glyphsSlot = ui.RegisterBuffer(glyphsBuf);
|
|
|
|
|
|
|
|
|
|
// Application-side state. Library doesn't track this; the user owns it.
|
|
|
|
|
Rect btnRect{};
|
|
|
|
|
Rect sliderRect{};
|
|
|
|
|
bool hovered = false;
|
|
|
|
|
bool sliderHovered = false;
|
|
|
|
|
bool dragging = false;
|
|
|
|
|
float sliderT = 0.42f;
|
|
|
|
|
float progress = 0.0f;
|
|
|
|
|
|
|
|
|
|
// Hit-testing — purely user code. EventListener objects hold the
|
|
|
|
|
// subscription; their destructors unregister at scope exit.
|
|
|
|
|
EventListener<void> moveSub(&window.onMouseMove, [&]() {
|
|
|
|
|
float mx = window.currentMousePos.x;
|
|
|
|
|
float my = window.currentMousePos.y;
|
|
|
|
|
hovered = btnRect.Contains(mx, my);
|
|
|
|
|
sliderHovered = sliderRect.Contains(mx, my);
|
|
|
|
|
if (dragging && sliderRect.w > 0) {
|
|
|
|
|
sliderT = std::clamp((mx - sliderRect.x) / sliderRect.w, 0.0f, 1.0f);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
EventListener<void> clickSub(&window.onMouseLeftClick, [&]() {
|
|
|
|
|
if (sliderHovered) dragging = true;
|
|
|
|
|
});
|
|
|
|
|
EventListener<void> releaseSub(&window.onMouseLeftRelease, [&]() {
|
|
|
|
|
dragging = false;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Application color palette. No library Theme — just a struct.
|
|
|
|
|
ButtonColors btnPalette{
|
|
|
|
|
.bg = {0.20f, 0.22f, 0.28f, 1.0f},
|
|
|
|
|
.bgHover = {0.30f, 0.55f, 0.95f, 1.0f},
|
|
|
|
|
.bgPressed = {0.18f, 0.40f, 0.78f, 1.0f},
|
|
|
|
|
.text = {1.0f, 1.0f, 1.0f, 1.0f},
|
|
|
|
|
.border = {0.0f, 0.0f, 0.0f, 0.0f},
|
|
|
|
|
.cornerRadius = 8.0f,
|
|
|
|
|
.borderThickness = 0.0f,
|
|
|
|
|
};
|
|
|
|
|
SliderColors sliderPalette{
|
|
|
|
|
.track = {0.20f, 0.20f, 0.25f, 1.0f},
|
|
|
|
|
.trackFilled = {0.30f, 0.55f, 0.95f, 1.0f},
|
|
|
|
|
.thumb = {0.85f, 0.85f, 0.90f, 1.0f},
|
|
|
|
|
.thumbHover = {1.00f, 1.00f, 1.00f, 1.0f},
|
|
|
|
|
.trackHeight = 6.0f,
|
|
|
|
|
.thumbRadius = 10.0f,
|
|
|
|
|
};
|
|
|
|
|
ProgressColors progressPalette{
|
|
|
|
|
.bg = {0.15f, 0.15f, 0.18f, 1.0f},
|
|
|
|
|
.fill = {0.40f, 0.85f, 0.50f, 1.0f},
|
|
|
|
|
.cornerRadius = 4.0f,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
EventListener<UIBuildArgs> buildSub(&ui.onBuild, [&](UIBuildArgs a) {
|
2026-05-18 04:58:52 +02:00
|
|
|
auto cmd = a.cmd;
|
2026-05-02 21:08:20 +02:00
|
|
|
|
|
|
|
|
// Update demo progress.
|
|
|
|
|
progress = std::fmod(progress + 0.005f, 1.0f);
|
|
|
|
|
|
|
|
|
|
// Layout via SubRect — resize-safe.
|
|
|
|
|
Rect canvas = Rect::FromWindow(window);
|
|
|
|
|
Rect topBar = canvas.SubRect(80, Rect::Anchor::Top).Inset(20, 20, 10, 20);
|
|
|
|
|
btnRect = topBar.SubRect(160, Rect::Anchor::Left);
|
|
|
|
|
sliderRect = canvas.Inset(60).SubRect(20, Rect::Anchor::Top);
|
|
|
|
|
sliderRect.y = canvas.h * 0.5f;
|
|
|
|
|
sliderRect.x = 60.0f;
|
|
|
|
|
sliderRect.w = canvas.w - 120.0f;
|
|
|
|
|
Rect progressRect{ 60.0f, canvas.h * 0.5f + 60.0f, canvas.w - 120.0f, 16.0f };
|
|
|
|
|
|
|
|
|
|
// Reset per-frame counters.
|
|
|
|
|
std::uint32_t qc = 0, cc = 0, gc = 0;
|
|
|
|
|
|
|
|
|
|
UIBuffer buf{
|
|
|
|
|
.quads = quadsBuf.value,
|
|
|
|
|
.quadCount = &qc,
|
|
|
|
|
.quadCap = 256,
|
|
|
|
|
.glyphs = glyphsBuf.value,
|
|
|
|
|
.glyphCount = &gc,
|
|
|
|
|
.glyphCap = 4096,
|
|
|
|
|
.atlas = &atlas,
|
|
|
|
|
.renderer = &ui,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Background quad — required because the swapchain is not cleared
|
|
|
|
|
// (no TRANSFER_DST_BIT on the swapchain image).
|
|
|
|
|
QuadItem bg{
|
|
|
|
|
canvas.x, canvas.y, canvas.w, canvas.h,
|
|
|
|
|
0.f, 0, 0, 1.f,
|
|
|
|
|
0, 0, 0, 0,
|
|
|
|
|
0, 0, 0, 0,
|
|
|
|
|
};
|
|
|
|
|
if (qc < buf.quadCap) buf.quads[qc++] = bg;
|
|
|
|
|
|
|
|
|
|
// Tier 3 components.
|
|
|
|
|
DrawButton(buf, btnRect,
|
|
|
|
|
hovered ? "Hovered!" : "Hover me",
|
|
|
|
|
hovered, /*pressed*/ false,
|
|
|
|
|
font, 18.0f, btnPalette);
|
|
|
|
|
|
|
|
|
|
DrawSlider(buf, sliderRect, sliderT, dragging, sliderPalette);
|
|
|
|
|
|
|
|
|
|
DrawProgressBar(buf, progressRect, progress, progressPalette);
|
|
|
|
|
|
|
|
|
|
// Tier 2 standard shader, used directly: a circle following the mouse.
|
|
|
|
|
circlesBuf.value[cc++] = CircleItem{
|
|
|
|
|
window.currentMousePos.x, window.currentMousePos.y, 6.0f, 0.0f,
|
|
|
|
|
1.0f, 1.0f, 1.0f, 0.85f,
|
|
|
|
|
0, 0, 0, 0,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Flush + dispatch. The library inserts the inter-dispatch barriers.
|
|
|
|
|
if (qc > 0) {
|
2026-05-18 04:58:52 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-02 21:08:20 +02:00
|
|
|
quadsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
2026-05-18 04:58:52 +02:00
|
|
|
#else
|
|
|
|
|
quadsBuf.FlushDevice();
|
|
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
ui.DispatchQuads(cmd, quadsSlot, qc);
|
|
|
|
|
}
|
|
|
|
|
if (cc > 0) {
|
2026-05-18 04:58:52 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-02 21:08:20 +02:00
|
|
|
circlesBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
2026-05-18 04:58:52 +02:00
|
|
|
#else
|
|
|
|
|
circlesBuf.FlushDevice();
|
|
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
ui.DispatchCircles(cmd, circlesSlot, cc);
|
|
|
|
|
}
|
|
|
|
|
if (gc > 0) {
|
2026-05-18 04:58:52 +02:00
|
|
|
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
|
2026-05-02 21:08:20 +02:00
|
|
|
glyphsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
2026-05-18 04:58:52 +02:00
|
|
|
#else
|
|
|
|
|
glyphsBuf.FlushDevice();
|
|
|
|
|
#endif
|
2026-05-02 21:08:20 +02:00
|
|
|
ui.DispatchText(cmd, glyphsSlot, gc);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.FinishInit();
|
|
|
|
|
window.Render();
|
|
|
|
|
window.StartUpdate();
|
|
|
|
|
window.StartSync();
|
|
|
|
|
}
|