Banjo API 1.0.0-rc.2
Low-level C99 game development API
Loading...
Searching...
No Matches
event_callbacks.c

Tutorial: Input via callbacks.

Tutorial: Input via callbacks. Register handlers for keyboard, mouse buttons, cursor motion, and enter/leave events.

Where start.c got you a window and ESC-to-quit, this tutorial shows the general callback-based event-handling pattern. You'll register a dedicated function for each event type, see how Banjo plumbs your user_data pointer through, and see how to share state across the App API's setup / step / teardown callbacks.

What you'll learn

Prerequisites

You've already worked through start.c: opening a window, the dispatch loop, and the basic teardown order. This tutorial assumes you know what bj_begin, bj_bind_window, and bj_dispatch_events do.

Walkthrough

1. The callback signature

Every event callback follows the same shape:

void cb(bj_window* window, const T_event* event, void* user_data);

where T_event is one of bj_cursor_event, bj_button_event, bj_key_event, or bj_enter_event. window is the window the event targets; user_data is whatever pointer you passed at registration time, opaque to Banjo. (Window resize uses a slightly different callback, registered per-window and handed the new size directly; see step 5.)

2. Sharing state across callbacks

start.c only needed a file-scope window pointer. This tutorial wants to count events of each type, and the counter needs to be visible from setup, step, teardown, and every event callback. The App API plumbs that for us: the user_data pointer returned by setup is passed to step and teardown, and we pass the same pointer to bj_set_key_callback and friends. Allocate the counter in setup, return its address, and every callback receives it.

3. Callbacks fire from bj_dispatch_events

Registering a callback doesn't start a thread. Callbacks fire synchronously, on your main thread, from inside bj_dispatch_events. That's the same call site that drains the queue when you use polling; the two styles share the same machinery.

4. ESC handler, by hand

start.c used the built-in bj_close_on_escape shortcut. This tutorial does the same thing manually inside key_callback: when the key event matches BJ_KEY_ESCAPE, call bj_set_window_should_close. Doing it by hand is useful when you want ESC to do something more than just close (save state, prompt, etc.).

5. Opting into resize

Windows are fixed-size by default. Passing BJ_WINDOW_FLAG_RESIZABLE to bj_bind_window lets the user drag the window's edges; without it the window cannot be resized. Banjo recreates the framebuffer at the new size for you, so the only thing left to do is react, which is what bj_set_resize_callback is for.

That callback differs from the input callbacks above in two ways: it is registered on a specific window rather than globally, and it receives the new width and height directly instead of an event struct. This is where a real program recomputes a projection, re-lays out its scene, or rescales an off-screen surface; here we just count and log it.

Registering the callback also fires it once immediately with the current size, so the first Resize event line logged at startup is the initial 800x600. That is what lets a program keep all of its size-dependent setup in the resize handler and nowhere else.

What's next

  • event_polling.c: the alternative polling style, useful when you need full control over when each event fires.
  • drawing_2d.c: actually put pixels on the screen.
  • The Event topic for the full event-type reference.
#include <banjo/app.h>
#include <banjo/main.h>
#include <banjo/error.h>
#include <banjo/event.h>
#include <banjo/log.h>
#include <banjo/memory.h>
#include <banjo/system.h>
#include <banjo/window.h>
typedef struct {
size_t cursor;
size_t button;
size_t key;
size_t enter;
size_t resize;
// Callback functions follow a standard signature:
// (bj_window* window, const event_type* event, void* user_data)
// The user_data parameter is whatever you passed during registration.
void cursor_callback(bj_window* p_window, const bj_cursor_event* e, void* data) {
++counter->cursor;
bj_info("Cursor event, window %p, (%d,%d)",
(void*)p_window, e->x, e->y
);
}
void button_callback(bj_window* p_window, const bj_button_event* e, void* data) {
++counter->button;
bj_info("Button event, window %p, button %d, %s, (%d,%d)",
(void*)p_window, e->button,
e->action == BJ_PRESS ? "pressed" : "released",
e->x, e->y
);
}
void key_callback(bj_window* p_window, const bj_key_event* e, void* data) {
++counter->key;
(void)p_window;
const char* action_str = "pressed";
if(e->action != BJ_PRESS) {
action_str = e->action == BJ_RELEASE ? "released" : "repeated";
}
bj_info("Key 0x%04X (%s) Scancode 0x%04X (with no mods) was %s",
e->key, bj_key_name(e->key), e->scancode, action_str
);
if(e->key == BJ_KEY_ESCAPE) {
}
}
void enter_callback(bj_window* p_window, const bj_enter_event* e, void* data) {
++counter->enter;
bj_info("Enter event, window %p, %s, (%d,%d)",
(void*)p_window,
e->enter ? "entered" : "left",
e->x, e->y
);
}
// The resize callback is the odd one out: it is per-window and receives
// the new size directly rather than an event struct. Banjo has already
// resized the framebuffer by the time this fires; here we just count and
// log it. A real program would recompute layout or rescale here.
void resize_callback(bj_window* p_window, int width, int height, void* data) {
++counter->resize;
bj_info("Resize event, window %p, %dx%d", (void*)p_window, width, height);
}
static void* setup(struct bj_app* app, void* init_data) {
(void)init_data;
event_counter* counter = bj_calloc(sizeof(event_counter));
bj_free(counter);
bj_quit_app(app, 1);
return 0;
}
// Opt the window into user resizing; it is fixed-size otherwise.
window = bj_bind_window("Event Callbacks", 100, 100, 800, 600,
// Register callbacks for each event type. When bj_dispatch_events() runs,
// these functions will be called automatically for each matching event.
// The second parameter (counter) is passed to the callback as user_data.
// Resize is registered per-window, not globally like the input callbacks.
return counter;
}
static void step(struct bj_app* app, struct bj_tick_info tick, void* user_data) {
(void)tick;
(void)user_data;
// bj_dispatch_events() processes all queued events and invokes the
// registered callbacks. Unlike polling where you manually check each event,
// callbacks are automatically invoked for you.
bj_quit_app(app, 0);
}
}
static void teardown(struct bj_app* app, void* user_data) {
(void)app;
event_counter* counter = (event_counter*)user_data;
bj_info("Total events: %ld cursor, %ld button, %ld key, %ld enter, %ld resize",
counter->cursor, counter->button, counter->key, counter->enter, counter->resize
);
bj_end();
bj_free(counter);
}
int main(int argc, char* argv[]) {
(void)argc; (void)argv;
return bj_run_app(setup, step, 0, teardown, 0);
}
Application lifecycle: callback-driven setup, step, and teardown.
int main(int argc, char *argv[])
Definition audio_pcm.c:177
static void step(struct bj_app *app, struct bj_tick_info tick, void *user_data)
Definition audio_pcm.c:144
static void teardown(struct bj_app *app, void *user_data)
Definition audio_pcm.c:170
bj_audio_play_note_data data
Definition audio_pcm.c:104
static void * setup(struct bj_app *app, void *init_data)
Definition audio_pcm.c:107
bj_window * window
Definition bitmap_blit.c:24
Recoverable error handling.
Sytem event management API.
void button_callback(bj_window *p_window, const bj_button_event *e, void *data)
void cursor_callback(bj_window *p_window, const bj_cursor_event *e, void *data)
void resize_callback(bj_window *p_window, int width, int height, void *data)
void enter_callback(bj_window *p_window, const bj_enter_event *e, void *data)
void key_callback(bj_window *p_window, const bj_key_event *e, void *data)
int bj_run_app(bj_app_setup_fn setup, bj_app_step_fn step, bj_app_fixed_step_fn fixed_step, bj_app_teardown_fn teardown, void *init_data)
Drive the application lifecycle.
void bj_quit_app(struct bj_app *app, int exit_code)
Signal the given application to exit on the next iteration.
Timing snapshot handed to the step and fixed-step callbacks.
Definition app.h:106
struct bj_window bj_window
Definition api.h:354
int y
Cursor y position.
Definition event.h:413
int y
Cursor y position.
Definition event.h:404
int x
Cursor x position.
Definition event.h:420
int y
Cursor y position.
Definition event.h:421
int scancode
Scancode (layout-independent)
Definition event.h:431
int button
Button identifier (e.g., BJ_BUTTON_LEFT)
Definition event.h:423
bj_bool enter
BJ_TRUE if entering window, BJ_FALSE if leaving.
Definition event.h:405
enum bj_event_action action
Action (press/release/repeat)
Definition event.h:432
enum bj_event_action action
Action (press/release)
Definition event.h:422
int x
Cursor x position.
Definition event.h:403
enum bj_key key
Key identifier.
Definition event.h:430
int x
Cursor x position.
Definition event.h:412
const char * bj_key_name(int key)
Get the string name of a key.
void bj_dispatch_events(void)
Poll and dispatch all pending events.
bj_cursor_callback_fn bj_set_cursor_callback(bj_cursor_callback_fn callback, void *user_data)
Set the global callback for cursor events.
bj_enter_callback_fn bj_set_enter_callback(bj_enter_callback_fn callback, void *user_data)
Set the global callback for mouse enter/leave events.
bj_key_callback_fn bj_set_key_callback(bj_key_callback_fn callback, void *user_data)
Set the global callback for keyboard key events.
bj_button_callback_fn bj_set_button_callback(bj_button_callback_fn callback, void *user_data)
Set the global callback for mouse button events.
@ BJ_KEY_ESCAPE
Esc key.
Definition event.h:176
@ BJ_PRESS
The key or button was pressed.
Definition event.h:392
@ BJ_RELEASE
The key or button was released.
Definition event.h:391
Represent a mouse button event.
Definition event.h:419
Represent a mouse cursor movement event.
Definition event.h:411
Represent a mouse enter or leave event.
Definition event.h:402
Represent a keyboard key event.
Definition event.h:429
#define bj_info(...)
Log a message using the BJ_LOG_INFO level.
Definition log.h:141
void * bj_calloc(size_t size)
Allocate size bytes of zero-initialised memory.
void bj_free(void *memory)
Free a previously allocated memory block.
bj_bool bj_begin(int systems, struct bj_error **error)
Initialises the system.
void bj_end(void)
De-initialises the system.
@ BJ_VIDEO_SYSTEM
Definition system.h:81
void bj_set_window_should_close(struct bj_window *window)
Flag a given window to be closed.
struct bj_window * bj_bind_window(const char *title, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t flags, struct bj_error **error)
Create a new struct bj_window with the specified attributes.
void bj_set_resize_callback(struct bj_window *window, bj_window_resize_fn fn, void *user_data)
Register the resize callback for window.
bj_bool bj_should_close_window(struct bj_window *window)
Get the close flag state of a window.
void bj_unbind_window(struct bj_window *window)
Deletes a struct bj_window object and releases associated memory.
@ BJ_WINDOW_FLAG_RESIZABLE
User may resize the window.
Definition window.h:190
Logging utility functions.
Portable main() replacement with platform-aware entry shim.
All memory-related functions, including custom allocators.
Header file for system interactions.
Header file for bj_window type.