// 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. #ifndef CRAFTER_GRAPHICS_WINDOW_DOM #include "vulkan/vulkan.h" #endif import Crafter.Graphics; import Crafter.Event; import std; using namespace Crafter; int main() { Device::Initialize(); #ifdef CRAFTER_GRAPHICS_WINDOW_DOM static Window window(1280, 720, "Hello UI"); #else Window window(1280, 720, "Hello UI"); #endif auto init = window.StartInit(); GraphicsDescriptorHeap heap; 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. GraphicsBuffer quadsBuf; GraphicsBuffer circlesBuf; GraphicsBuffer glyphsBuf; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM 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); #else quadsBuf.Create(256); circlesBuf.Create(64); glyphsBuf.Create(4096); #endif 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 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 clickSub(&window.onMouseLeftClick, [&]() { if (sliderHovered) dragging = true; }); EventListener 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 buildSub(&ui.onBuild, [&](UIBuildArgs a) { auto cmd = a.cmd; // 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) { #ifndef CRAFTER_GRAPHICS_WINDOW_DOM quadsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); #else quadsBuf.FlushDevice(); #endif ui.DispatchQuads(cmd, quadsSlot, qc); } if (cc > 0) { #ifndef CRAFTER_GRAPHICS_WINDOW_DOM circlesBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); #else circlesBuf.FlushDevice(); #endif ui.DispatchCircles(cmd, circlesSlot, cc); } if (gc > 0) { #ifndef CRAFTER_GRAPHICS_WINDOW_DOM glyphsBuf.FlushDevice(cmd, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); #else glyphsBuf.FlushDevice(); #endif ui.DispatchText(cmd, glyphsSlot, gc); } }); window.FinishInit(); window.Render(); window.StartUpdate(); window.StartSync(); }