animated example

This commit is contained in:
Jorijn van der Graaf 2026-05-02 00:03:24 +02:00
commit c9fd1b1585
17 changed files with 576 additions and 465 deletions

View file

@ -454,16 +454,32 @@ void Device::keyboard_key(void *data, wl_keyboard *keyboard, uint32_t serial, ui
std::string buf;
buf.resize(16);
int n = xkb_state_key_get_utf8(xkb_state, keycode, buf.data(), 16);
if (n > 0) {
if ((unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) {
buf.resize(n);
focusedWindow->onTextInput.Invoke(buf);
}
std::string utf8;
if (n > 0 && (unsigned char)buf[0] >= 0x20 && buf[0] != 0x7f) {
buf.resize(n);
utf8 = buf;
focusedWindow->onTextInput.Invoke(utf8);
}
// Replace the active repeat with this key — most recent press wins,
// matching xkbcommon's typical behaviour and most desktop apps.
keyRepeat.active = (keyRepeat.rate > 0);
keyRepeat.key = crafterKey;
keyRepeat.utf8 = std::move(utf8);
keyRepeat.pressTime = std::chrono::steady_clock::now();
keyRepeat.lastFireTime = keyRepeat.pressTime;
} else {
focusedWindow->heldkeys[(std::uint8_t)crafterKey] = false;
focusedWindow->onKeyUp[(std::uint8_t)crafterKey].Invoke();
focusedWindow->onAnyKeyUp.Invoke(crafterKey);
// If the released key was the one repeating, stop. Otherwise leave
// the existing repeat alone (user pressed/released a modifier
// mid-repeat etc.).
if (keyRepeat.active && keyRepeat.key == crafterKey) {
keyRepeat.active = false;
keyRepeat.utf8.clear();
}
}
}
@ -472,7 +488,36 @@ void Device::keyboard_modifiers(void *data, wl_keyboard *keyboard, uint32_t seri
}
void Device::keyboard_repeat_info(void *data, wl_keyboard *keyboard, int32_t rate, int32_t delay) {
keyRepeat.rate = rate;
keyRepeat.delay = delay;
if (rate <= 0) keyRepeat.active = false; // compositor disabled repeat
}
void Device::TickKeyRepeats() {
if (!keyRepeat.active || !focusedWindow) return;
if (keyRepeat.rate <= 0) return;
auto now = std::chrono::steady_clock::now();
using ms = std::chrono::milliseconds;
auto sincePress = std::chrono::duration_cast<ms>(now - keyRepeat.pressTime).count();
if (sincePress < keyRepeat.delay) return;
auto period = std::chrono::milliseconds(1000 / keyRepeat.rate);
auto sinceLastFire = std::chrono::duration_cast<ms>(now - keyRepeat.lastFireTime).count();
if (sinceLastFire < period.count()) return;
// Catch up — emit one event per missed period so a paused frame doesn't
// make the repeat permanently lag behind.
while (now - keyRepeat.lastFireTime >= period) {
focusedWindow->onKeyDown[(std::uint8_t)keyRepeat.key].Invoke();
focusedWindow->onAnyKeyDown.Invoke(keyRepeat.key);
focusedWindow->onKeyHold[(std::uint8_t)keyRepeat.key].Invoke();
focusedWindow->onAnyKeyHold.Invoke(keyRepeat.key);
if (!keyRepeat.utf8.empty()) {
focusedWindow->onTextInput.Invoke(keyRepeat.utf8);
}
keyRepeat.lastFireTime += period;
}
}
void Device::seat_handle_capabilities(void* data, wl_seat* seat, uint32_t capabilities) {

View file

@ -123,10 +123,14 @@ void UIScene::Initialize(Window& window, const std::filesystem::path& spvPath) {
}
);
// Per-frame: re-layout, emit, push items.
// Per-frame: re-layout, emit, push items. We capture FrameTime here
// so we can advance the scene's clock (caret blink, animations).
updateListener_ = std::make_unique<EventListener<FrameTime>>(
&window.onUpdate,
[this](FrameTime) { RebuildFrame(); }
[this](FrameTime ft) {
elapsedSec_ += static_cast<float>(ft.delta.count());
RebuildFrame();
}
);
}
@ -153,6 +157,7 @@ void UIScene::RebuildFrame() {
drawList.atlas = &renderer.atlas;
drawList.bindlessBaseHeapIdx = renderer.BindlessBaseHeapIdx();
drawList.scale = sc;
drawList.time = elapsedSec_;
if (background_) {
drawList.AddRect(
{ 0, 0, static_cast<float>(window_->width), static_cast<float>(window_->height) },

View file

@ -724,6 +724,10 @@ void Window::Render() {
vkCmdPipelineBarrier(drawCmdBuffers[currentBuffer], VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
// Synthesise key-repeat events before listeners run, so the focused
// widget's OnTextInput / OnKeyDown sees them in the same frame.
Device::TickKeyRepeats();
onUpdate.Invoke({startTime, startTime-lastFrameBegin});
#ifdef CRAFTER_TIMING
totalUpdate = std::chrono::nanoseconds(0);