Skip to main content

03: Events and Input

Every interactive app needs two things: a stream of events ("key pressed", "mouse moved", "window resized") and a way to poll current state ("is W held right now?"). The vneevents library provides both — typed event classes, a thread-safe queue, an EventManager singleton, and a static Input API.

The vnetestbed 01_test_events sample demonstrates the full pipeline with a real GLFW window and ImGui panels. Running it is the fastest way to see everything working before you write any integration code.

Quick Start

#include <vertexnova/events/events.h>

using namespace vne::events;

// 1. Implement a listener
class MyListener : public EventListener {
public:
void onEvent(const Event& event) override {
if (event.type() == EventType::eKeyPressed) {
auto& e = static_cast<const KeyPressedEvent&>(event);
// e.keyCode(), e.modifiers()
}
}
};

int main() {
auto& manager = EventManager::instance();

// 2. Register
auto listener = std::make_shared<MyListener>();
manager.registerListener(EventType::eKeyPressed, listener);

// 3. Push events (typically from window callbacks, not hardcoded)
manager.pushEvent(std::make_unique<KeyPressedEvent>(KeyCode::eA));
manager.pushEvent(std::make_unique<MouseMovedEvent>(100.0, 200.0));

// 4. Process — dispatches queued events to all listeners
manager.processEvents();

// 5. Poll input state
if (Input::isKeyPressed(static_cast<int>(KeyCode::eW))) { /* move forward */ }
auto [mx, my] = Input::mousePosition();

// 6. Advance frame — updates "just pressed" / "just released" state
Input::nextFrame();

return 0;
}
PieceWhat it does
EventListenerInterface; override onEvent(const Event&) to handle events.
EventManager::instance()Singleton. Owns the queue and dispatcher.
registerListener(type, ptr)Subscribe a listener to one event type.
pushEvent(unique_ptr<Event>)Enqueue an event (thread-safe). Typically called from window/input callbacks.
processEvents()Drain the queue and call onEvent on every registered listener. Call once per frame.
dispatchImmediate(event)Bypass the queue — dispatch to listeners right now, synchronously.
Input::isKeyPressed(int)Returns true while the key is held. Pass static_cast<int>(KeyCode::eW).
Input::nextFrame()Resets "just pressed" / "just released" state. Call at the end of each frame.

Event types

Keyboard

ClassEventTypeAccessors
KeyPressedEventeKeyPressedkeyCode(), modifiers()
KeyReleasedEventeKeyReleasedkeyCode(), modifiers()
KeyRepeatEventeKeyRepeatkeyCode(), repeatCount() — fires repeatedly while a key is held
KeyTypedEventeKeyTypedkeyCode() — use this for text input, not KeyPressedEvent

modifiers() returns a bitmask of ModifierKey flags: eModShift, eModCtrl, eModAlt, eModSuper, etc.

Mouse

ClassEventTypeAccessors
MouseButtonPressedEventeMouseButtonPressedbutton() (MouseButton::eLeft/eRight/eMiddle), modifiers()
MouseButtonReleasedEventeMouseButtonReleasedbutton(), modifiers()
MouseMovedEventeMouseMovedx(), y() (double, screen coords), modifiers()
MouseScrolledEventeMouseScrolledxOffset(), yOffset()

Window

ClassEventTypeAccessors
WindowResizeEventeWindowResizewidth(), height() (uint32_t)
WindowCloseEventeWindowClosenone — signals the app should quit

Touch

ClassEventTypeAccessors
TouchPressEventeTouchPresstouchId(), x(), y()
TouchMoveEventeTouchMovetouchId(), x(), y()
TouchReleaseEventeTouchReleasetouchId(), x(), y()

Input polling

Call these anywhere in your frame loop. All methods are static and thread-safe.

// Keyboard
Input::isKeyPressed(static_cast<int>(KeyCode::eEscape)); // true every frame the key is held
Input::isKeyJustPressed(static_cast<int>(KeyCode::eSpace)); // true on the first frame only
Input::isKeyJustReleased(static_cast<int>(KeyCode::eSpace)); // true on the release frame only

// Mouse buttons
Input::isMouseButtonPressed(static_cast<int>(MouseButton::eLeft));
Input::isMouseButtonJustPressed(static_cast<int>(MouseButton::eLeft));

// Position and scroll
auto [x, y] = Input::mousePosition(); // std::pair<int, int>
auto [sx, sy] = Input::mouseScroll(); // std::pair<float, float>
auto [width, height] = Input::windowSize();

// Call once at the end of each frame
Input::nextFrame();

isKeyPressed is true every frame the key is down. isKeyJustPressed fires only on the first frame — use it for actions that should trigger once per press (jump, toggle, shoot).

The Input state is updated by your window backend (GLFW callbacks or equivalent) calling Input::updateKeyState, Input::updateMousePosition, etc. before processEvents.

Per-frame pipeline

vneevents per-frame pipeline — event path (queued) and input path (polled)

Per-frame loop

while (running) {
// Window library polls OS events → GLFW callbacks fire → push into EventManager + update Input state
glfwPollEvents();

// Drain queue → registered listeners get onEvent()
EventManager::instance().processEvents();

// Poll current state for movement, camera, etc.
if (Input::isKeyPressed(static_cast<int>(KeyCode::eW))) move_forward();
if (Input::isKeyJustPressed(static_cast<int>(KeyCode::eEscape))) running = false;

render();

Input::nextFrame();
}

Events and polling are complementary: events tell you what changed this frame (one call per occurrence); polling tells you what is true right now (call any time). Use events for reactions (open menu, fire weapon); use polling for continuous state (movement, camera rotation).

EventManager reference

MethodNotes
registerListener(EventType, shared_ptr<EventListener>)Listeners are held as shared_ptr — keep your listener alive as long as it's registered.
unregisterListener(EventType, const EventListener*)Removes the listener for that event type.
pushEvent(unique_ptr<Event>)Thread-safe. Can be called from any thread.
processEvents()Not thread-safe — call from your main/render thread only.
dispatchImmediate(const Event&)Skips the queue; calls onEvent synchronously on all listeners for that type.
pendingEventCount()Number of events waiting in the queue.
clearPendingEvents()Discard all queued events without dispatching.

Event categories

Every event reports a categoryFlags() bitmask. Use it to filter by category without checking individual types:

void onEvent(const Event& event) override {
if (event.categoryFlags() & EventCategory::eKeyboard) {
// handles KeyPressed, KeyReleased, KeyRepeat, KeyTyped
}
}
FlagCovers
eInputAll input events (keyboard, mouse, touch)
eKeyboardKey pressed, released, repeat, typed
eMouseMouse moved, scrolled
eMouseButtonMouse button pressed, released
eTouchScreenTouch press, move, release
eWindowWindow resize, close

See it in vnetestbed

The 01_test_events sample is the reference integration: GLFW callbacks push vneevents events into the queue; the app's frame loop calls processEvents; ImGui panels show the live event stream and polled input state. It also emulates touch from the left mouse button (touch id 0) so you can test touch events on desktop.

git clone --recursive https://github.com/vertexnova/vnetestbed.git
cd vnetestbed
cmake -B build -DVNE_TESTBED_SAMPLES=ON
cmake --build build

Run (see the repo README for the exact binary path on your platform):

./build/bin/samples/sample_01_test_events

Press keys, move the mouse, scroll, resize the window. The Events panel logs each event with type and data. The Input Poll panel shows live held-key and cursor state. Exit with ESC or by closing the window.

vnetestbed 01_test_events sample — Events and Input Poll panels

Full details: vneevents README · vnetestbed README · sample README.


Next: See the Learn index for what's coming — windowing, rendering, and more.

Questions or ideas? Create a discussion, open an issue, or suggest a feature in the relevant repository.

VertexNova Discussions →