animated example
This commit is contained in:
parent
216972e73a
commit
c9fd1b1585
17 changed files with 576 additions and 465 deletions
BIN
examples/Forts3DMainMenu/Inter.ttf
Normal file
BIN
examples/Forts3DMainMenu/Inter.ttf
Normal file
Binary file not shown.
27
examples/Forts3DMainMenu/project.cpp
Normal file
27
examples/Forts3DMainMenu/project.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
||||
std::vector<std::string> graphicsArgs(args.begin(), args.end());
|
||||
Configuration* graphics = LocalProject({
|
||||
.projectFile = "../../project.cpp",
|
||||
.args = graphicsArgs,
|
||||
});
|
||||
|
||||
Configuration cfg;
|
||||
cfg.path = "./";
|
||||
cfg.name = "Forts3DMainMenu";
|
||||
cfg.outputName = "Forts3DMainMenu";
|
||||
ApplyStandardArgs(cfg, args);
|
||||
cfg.dependencies = { graphics };
|
||||
|
||||
std::array<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||
|
||||
cfg.files.push_back("Inter.ttf");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
BIN
examples/VulkanAnimated/Inter.ttf
Normal file
BIN
examples/VulkanAnimated/Inter.ttf
Normal file
Binary file not shown.
55
examples/VulkanAnimated/README.md
Normal file
55
examples/VulkanAnimated/README.md
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# VulkanAnimated
|
||||
|
||||
A live HUD demo: three `Observable<float>`s drive `ProgressBar`s, three
|
||||
`Observable<std::string>`s drive `Text` labels, and an FPS readout in
|
||||
the corner ticks every frame. Everything updates from a single
|
||||
`onUpdate` listener — no `Invalidate()` / `Redraw()` calls.
|
||||
|
||||
## What it shows
|
||||
|
||||
- **`Observable<T>` data flow**: change a value in `onUpdate`, the
|
||||
next frame's `RebuildFrame` re-emits the draw list with the new value
|
||||
automatically. No tree rebuild.
|
||||
- **`ProgressBar::bindValue(obs, lo, hi)`** — the bar fill normalises the
|
||||
observable's current value into 0..1 each frame.
|
||||
- **`Text::bind(observable)`** — the displayed string is sourced from
|
||||
the observable each frame, replacing any baked-in runs.
|
||||
- **Composition pattern**: a small lambda helper builds one HP/MP-style
|
||||
row (`Text` + `ProgressBar` inside an `HStack`) given the observables
|
||||
and a colour, then the row is dropped into the parent `VStack` like
|
||||
any other widget.
|
||||
- **Different update rates per observable**: `health` oscillates at 0.7
|
||||
rad/s, `mana` at 1.3, `charge` advances linearly modulo 1 — visible
|
||||
proof that each observable updates independently.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cd examples/VulkanAnimated
|
||||
crafter-build -r
|
||||
```
|
||||
|
||||
You should see "Animated HUD" with three coloured bars (red HP, blue MP,
|
||||
yellow Charge) all moving at different rates, with the FPS readout in
|
||||
the top-right ticking once per frame.
|
||||
|
||||
Click `Quit` to exit.
|
||||
|
||||
## Notes
|
||||
|
||||
The whole tick handler is just:
|
||||
|
||||
```cpp
|
||||
EventListener<FrameTime> tick(&window.onUpdate, [&](FrameTime ft){
|
||||
t += ft.delta.count();
|
||||
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);
|
||||
// …
|
||||
});
|
||||
```
|
||||
|
||||
That's the entire animation system. There is deliberately no
|
||||
`Animation<T>` / tween primitive in the library — drive observables
|
||||
from any source you like.
|
||||
115
examples/VulkanAnimated/main.cpp
Normal file
115
examples/VulkanAnimated/main.cpp
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
#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<float> drive ProgressBars at
|
||||
// different rates / colours, while Observable<std::string> 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<float> health{1.0f};
|
||||
UI::Observable<float> mana {0.5f};
|
||||
UI::Observable<float> charge{0.0f};
|
||||
UI::Observable<std::string> healthLabel;
|
||||
UI::Observable<std::string> manaLabel;
|
||||
UI::Observable<std::string> chargeLabel;
|
||||
UI::Observable<std::string> 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<FrameTime> tick(&window.onUpdate, [&](FrameTime ft) {
|
||||
const float dt = static_cast<float>(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<std::string>& label,
|
||||
UI::Observable<float>& 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 Observable<float>s drive the bars; "
|
||||
"Observable<std::string>s 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();
|
||||
}
|
||||
27
examples/VulkanAnimated/project.cpp
Normal file
27
examples/VulkanAnimated/project.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
|
||||
std::vector<std::string> graphicsArgs(args.begin(), args.end());
|
||||
Configuration* graphics = LocalProject({
|
||||
.projectFile = "../../project.cpp",
|
||||
.args = graphicsArgs,
|
||||
});
|
||||
|
||||
Configuration cfg;
|
||||
cfg.path = "./";
|
||||
cfg.name = "VulkanAnimated";
|
||||
cfg.outputName = "VulkanAnimated";
|
||||
ApplyStandardArgs(cfg, args);
|
||||
cfg.dependencies = { graphics };
|
||||
|
||||
std::array<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||
|
||||
cfg.files.push_back("Inter.ttf");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
|
@ -1,25 +1,38 @@
|
|||
# HelloWindow Example
|
||||
# VulkanTriangle
|
||||
|
||||
## Description
|
||||
The minimal ray-traced example. Renders a single static triangle through
|
||||
`vkCmdTraceRaysKHR`. No UI.
|
||||
|
||||
This example demonstrates how to load shaders and render a triangle.
|
||||
## What it shows
|
||||
|
||||
## Expected Result
|
||||
- `Device::Initialize()` + `Window` + swapchain bring-up.
|
||||
- A `DescriptorHeapVulkan` sized for one image + one buffer slot, with
|
||||
slot ranges allocated via the bump-allocator API
|
||||
(`AllocateImageSlots`, `AllocateBufferSlots`).
|
||||
- A `PipelineRTVulkan` built from raygen / miss / closesthit SPIR-V
|
||||
shaders compiled at build time.
|
||||
- `Mesh::Build` constructing a BLAS and `RenderingElement3D::BuildTLAS`
|
||||
the per-frame TLAS.
|
||||
- Direct descriptor writes via `vkWriteResourceDescriptorsEXT` for the
|
||||
swapchain views and TLAS device addresses.
|
||||
- `RTPass{&pipeline}` plugged into `window.passes` — the canonical
|
||||
way to add ray tracing to a window in this library.
|
||||
|
||||
A blue tinted vulkan window with a white triangle in the center.
|
||||
It's the smallest sensible test of the bindless `VK_EXT_descriptor_heap`
|
||||
+ ray-tracing path.
|
||||
|
||||
## Highlighted Code Snippet
|
||||
|
||||
```cpp
|
||||
EventListener<VkCommandBuffer> listener(&window.onDraw, [&descriptors, &meshShader](VkCommandBuffer cmd){
|
||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, Pipeline::pipelineLayout, 0, 2, &descriptors.set[0], 0, NULL);
|
||||
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, Pipeline::pipeline);
|
||||
Device::vkCmdDrawMeshTasksEXTProc(cmd, meshShader.threadCount, 1, 1);
|
||||
});
|
||||
```
|
||||
|
||||
## How to Run
|
||||
## Run
|
||||
|
||||
```bash
|
||||
crafter-build build executable -r
|
||||
```
|
||||
cd examples/VulkanTriangle
|
||||
crafter-build -r
|
||||
```
|
||||
|
||||
You should see a 1280×720 window with a triangle filling roughly the
|
||||
centre.
|
||||
|
||||
## Notes
|
||||
|
||||
`raygen.glsl`'s `traceRayEXT` call is currently commented out — the
|
||||
example exercises the dispatch and `imageStore` paths only. Uncomment
|
||||
it to actually trace into the BLAS.
|
||||
|
|
|
|||
52
examples/VulkanUI/README.md
Normal file
52
examples/VulkanUI/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# VulkanUI
|
||||
|
||||
A walking tour of the V1 widget set. UI-only (no 3D pass), so the entire
|
||||
visible image comes from one compute dispatch per frame.
|
||||
|
||||
## What it shows
|
||||
|
||||
- **Layout**: nested `VStack` / `HStack` / `Spacer` / `TabView`, fluent
|
||||
builder API, `Length::Px` / `Pct` / `Frac` / `Auto` units, DPI scaling
|
||||
(your `Window::scale` flows through automatically).
|
||||
- **Theming**: `themes::default_dark()` with `theme.primary` / `secondary`
|
||||
/ `danger` / `input` styles applied per-widget via `.style(...)`.
|
||||
- **Text**: per-run colour styling via `TextRun`, an em-dash in the
|
||||
header to confirm UTF-8 decoding works end-to-end.
|
||||
- **Buttons**: rounded background (SDF in the shader), centred SDF
|
||||
glyphs, `onClick` callbacks. Quit calls `_Exit(0)` so a working click
|
||||
visibly closes the window.
|
||||
- **Progress bar**: a `ProgressBar` at 42 %.
|
||||
- **TabView**: three tabs (Graphics / Input / Audio); clicking the
|
||||
tab bar swaps content.
|
||||
- **InputField**: focusable text edits with caret blink, UTF-8 typing,
|
||||
Backspace / Delete / arrow keys / Home / End, key repeat, horizontal
|
||||
scrolling that keeps the caret visible, clipping that prevents
|
||||
overflow from drawing past the field's bounds.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cd examples/VulkanUI
|
||||
crafter-build -r
|
||||
```
|
||||
|
||||
## Interactions to try
|
||||
|
||||
| Action | Expected |
|
||||
|---|---|
|
||||
| Click `Play` / `Options` | Prints `[click] ...` to stderr |
|
||||
| Click `Quit` | App exits |
|
||||
| Click a tab label (Graphics / Input / Audio) | Tab body swaps |
|
||||
| Click an `InputField` | Border turns blue, caret appears and blinks |
|
||||
| Type | Characters appear at the caret, including multi-byte UTF-8 |
|
||||
| Hold a letter | After ~500 ms the character starts repeating at ~25 Hz |
|
||||
| Backspace / Delete | Removes one full UTF-8 codepoint |
|
||||
| ← / → / Home / End | Moves the caret |
|
||||
| Type past the right edge | Text scrolls left, caret stays visible |
|
||||
| Click outside any input | Caret disappears (focus cleared) |
|
||||
|
||||
## Notes
|
||||
|
||||
The shader (`shaders/ui.comp.glsl` in the library) is compiled to
|
||||
`ui.comp.spv` next to the binary by the build system.
|
||||
The font (`Inter.ttf`) is bundled via `cfg.files.push_back`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue