#include "vulkan/vulkan.h" import Crafter.Graphics; import Crafter.Event; import Crafter.Math; import std; using namespace Crafter; // A simple "game HUD" demo: three Observable drive ProgressBars at // different rates / colours, while Observable labels feed the // Text widgets next to them. UIScene::RebuildFrame re-emits each frame, so // the only application code needed is "update the observables in // onUpdate" — no manual Invalidate / Redraw calls. int main() { Device::Initialize(); Window window(1280, 720, "VulkanAnimated"); window.StartInit(); window.FinishInit(); Font font("Inter.ttf"); UI::Theme theme = UI::themes::default_dark(); UI::UIScene scene; scene.Initialize(window, "ui.comp.spv"); scene.background(UI::Color{0.06f, 0.07f, 0.10f, 1.0f}); // ─── Observables ───────────────────────────────────────────────────── UI::Observable health{1.0f}; UI::Observable mana {0.5f}; UI::Observable charge{0.0f}; UI::Observable healthLabel; UI::Observable manaLabel; UI::Observable chargeLabel; UI::Observable fpsLabel; // ─── Per-frame tick: drive the observables. UIScene re-emits on // onUpdate, so any read of these values is automatically picked // up in the next frame's draw list. float t = 0.0f; EventListener tick(&window.onUpdate, [&](FrameTime ft) { const float dt = static_cast(ft.delta.count()); t += dt; // Three offset waves at different rates / phases. health = 0.5f + 0.5f * std::sin(t * 0.7f); mana = std::abs(std::sin(t * 1.3f)); charge = std::fmod(t * 0.3f, 1.0f); healthLabel = std::format("HP {:>3.0f} / 100", health.Get() * 100.0f); manaLabel = std::format("MP {:>3.0f} / 100", mana.Get() * 100.0f); chargeLabel = std::format("Charge {:>3.0f}%", charge.Get() * 100.0f); fpsLabel = (dt > 0.0f) ? std::format("{:>5.1f} fps", 1.0f / dt) : std::string{"---.- fps"}; }); // ─── Helper: one HP/MP-style row (label on the left, bar on the right). // Build into a local (avoids returning a reference to a temporary // that dies at the end of the chain expression). auto bar = [&](UI::Observable& label, UI::Observable& value, UI::Color fg) -> UI::HStack { UI::HStack h; h.width(UI::Length::Frac(1)) .spacing(12) .children( UI::Text{}.bind(label).font(font).size(16) .width(UI::Length::Px(160)), UI::ProgressBar{} .bindValue(value, 0.0f, 1.0f) .foreground(fg) .size(UI::Length::Frac(1), UI::Length::Px(20)) ); return h; }; scene.Root( UI::VStack{} .padding(28) .spacing(16) .children( UI::HStack{} .width(UI::Length::Frac(1)) .children( UI::Text{"Animated HUD"}.font(font).size(28), UI::Spacer{}, UI::Text{}.bind(fpsLabel).font(font).size(16) .color(UI::Color{0.55f, 0.85f, 1.0f, 1.0f}) ), UI::Text{"Three Observables drive the bars; " "Observables drive the labels."} .font(font).size(14).color(UI::Color{0.65f, 0.65f, 0.65f, 1}), bar(healthLabel, health, UI::Color{0.90f, 0.30f, 0.30f, 1.0f}), bar(manaLabel, mana, UI::Color{0.30f, 0.55f, 0.95f, 1.0f}), bar(chargeLabel, charge, UI::Color{0.95f, 0.85f, 0.30f, 1.0f}), UI::Spacer{}, UI::HStack{} .width(UI::Length::Frac(1)) .spacing(8) .children( UI::Spacer{}, UI::Button{"Quit"}.font(font).style(theme.danger) .onClick([]{ std::_Exit(0); }) ) ) ); window.Render(); window.StartUpdate(); window.StartSync(); }