Messages & Events
Sending Messages
// Synchronous: calls win->proc immediately; returns proc's return value
int send_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam);
// Asynchronous: queued, delivered on the next repost_messages(-1) call
void post_message(window_t *win, uint32_t msg, uint32_t wparam, void *lparam);
Standard Window Messages
| Constant | When sent | wparam / lparam |
|---|---|---|
evCreate | Window just created | lparam = value from create_window |
evDestroy | Window about to be freed | – |
evPaint | Repaint requested | – |
evNCPaint | Non-client area repaint | – |
evSetFocus | Window gains focus | – |
evKillFocus | Window loses focus | – |
evLeftButtonDown | LMB pressed | LOWORD=x, HIWORD=y (window-local) |
evLeftButtonUp | LMB released | same |
evRightButtonDown | RMB pressed | same |
evMouseMove | Mouse moved | same |
evMouseLeave | Mouse left window | – |
evKeyDown | Key pressed | SDL scancode |
evKeyUp | Key released | SDL scancode |
evTextInput | Text character input | lparam = const char * UTF-8 |
evWheel | Mouse wheel | wparam = MAKEDWORD(x,y) mouse pos; lparam = MAKEDWORD(dx,dy) scroll deltas |
evCommand | Control notification | LOWORD=id, HIWORD=notification code |
evResize | Window resized / moved | – |
evStatusBar | Update status bar text | lparam = (void *)const char * |
evHScroll | Built-in H scrollbar moved | wparam = new scroll position |
evVScroll | Built-in V scrollbar moved | wparam = new scroll position |
evHitTest | Find child at point | lparam = window_t ** |
evRefreshStencil | Stencil buffer needs update | – |
evUser (1000) | First app-defined message | – |
Control Notification Codes
Sent to the root window via evCommand:
| Code | Control | Meaning |
|---|---|---|
btnClicked | Button / Checkbox | Button was clicked |
edUpdate | Text edit | Text content changed |
cbSelectionChange | Combobox | Selected item changed |
RVN_SELCHANGE | ColumnView | Single-click selection change |
RVN_DBLCLK | ColumnView | Double-click on item |
kMenuBarNotificationItemClick | MenuBar | Menu item selected |
Decoding in the parent window procedure:
case evCommand: {
uint16_t notif = HIWORD(wparam); // notification code
uint16_t id = LOWORD(wparam); // item ID
window_t *ctrl = (window_t *)lparam;
if (notif == btnClicked) {
if (strcmp(ctrl->title, "OK") == 0) { /* … */ }
}
if (notif == kMenuBarNotificationItemClick) {
switch (id) {
case MY_MENU_OPEN: open_file(); break;
case MY_MENU_QUIT: running = false; break;
}
}
return true;
}
Toolbar Button Clicks
// Set up toolbar buttons once in evCreate
toolbar_button_t buttons[] = {
{ .icon = icon16_folder, .ident = ID_OPEN, .flags = 0 },
{ .icon = icon16_save, .ident = ID_SAVE, .flags = 0 },
};
send_message(win, tbAddButtons,
sizeof(buttons)/sizeof(buttons[0]), buttons);
// In window proc – receive toolbar click
case tbButtonClick:
switch (wparam) { // ident
case ID_OPEN: open_file(); break;
case ID_SAVE: save_file(); break;
}
return true;
Mouse Wheel Handling
The evWheel message is sent when the user scrolls the mouse wheel. The message uses a WinAPI-style convention:
- wparam: Mouse position as
MAKEDWORD(x, y)in window-local scaled coordinates - lparam: Scroll deltas as
MAKEDWORD(dx, dy)(cast fromvoid*)dx: Horizontal scroll amount (positive = scroll right, negative = scroll left)dy: Vertical scroll amount (positive = wheel up, negative = wheel down)- Values are already multiplied by
SCROLL_SENSITIVITY(3)
Extracting scroll deltas from lparam:
case evWheel: {
int dx = (int16_t)LOWORD((uintptr_t)lparam);
int dy = (int16_t)HIWORD((uintptr_t)lparam);
// Horizontal: positive dx scrolls right
scroll_x += dx;
// Vertical: positive dy = wheel up, so subtract for natural content movement
scroll_y -= dy;
// Clamp to valid range
if (scroll_y < 0) scroll_y = 0;
if (scroll_y > max_scroll) scroll_y = max_scroll;
invalidate_window(win);
return true;
}
Built-in scrollbar integration:
Windows with WINDOW_HSCROLL or WINDOW_VSCROLL flags automatically handle wheel scrolling via their built-in scrollbars. The framework extracts deltas from lparam and calls sb_try_scroll() to update the scrollbar position and fire evHScroll/evVScroll notifications.
If a window cannot handle wheel events (no scrollbars or not enabled), the event bubbles to the parent window, following WinAPI behavior.
Keyboard Input
case evKeyDown:
switch (wparam) {
case SDL_SCANCODE_ESCAPE: running = false; break;
case SDL_SCANCODE_S: save_file(); break;
}
return true;
case evTextInput:
append_char(win, (const char *)lparam);
return true;
Event Loop
extern bool running;
ui_event_t e;
while (running) {
while (get_message(&e)) // blocks until event, then drains queue
dispatch_message(&e);
repost_messages(); // process posted (async) messages + repaint
}
get_message() sleeps with SDL_WaitEvent when the SDL queue is empty, so the process yields the CPU instead of spinning. Calls to post_message() (including invalidate_window()) push a lightweight wakeup event into the SDL queue so that the wait is interrupted and the internal message queue is processed promptly.
Message Hooks
Register a global hook to intercept any message before it reaches its target window:
void register_hook(uint32_t msg, winhook_func_t func, void *userdata);
void unregister_hook(uint32_t msg, winhook_func_t func);