Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DRESsedAlarm184/LWXGL/llms.txt

Use this file to discover all available pages before exploring further.

LWXGL includes two higher-level layout features that go beyond individual widget placement: a built-in modal dialog system for interrupting user flow, and a scrollable back-buffer system for content that exceeds the window height. Both are straightforward to use but have specific setup rules — modals can be spawned at any time, while scrolling must be configured before the window is created.

SpawnModal

void SpawnModal(int type, const char* msg, void (*on_confirm)());
Spawns a modal dialog centered on the screen. Three types are available:
ConstantValueDescription
MODAL_ALERT0Message + OK button only
MODAL_CONFIRM1Message + OK + Cancel buttons
MODAL_INPUT2Message + text input field + OK + Cancel
  • type — one of the constants above
  • msg — message text displayed in the dialog; newlines (\n) are supported for multi-line messages. The text is word-wrapped automatically.
  • on_confirm — callback invoked when the user clicks OK; pass NULL if no action is needed on confirmation. Clicking Cancel dismisses the dialog without calling on_confirm.
Calling SpawnModal while another modal is open immediately replaces the current modal: the old msg buffer is freed and the new modal’s state (including a fresh empty input buffer) is installed. The dialog is rendered as a black rectangle with a white border, centered over the window. The message is drawn at the top; OK (green) and Cancel (red) appear at the bottom. Cancel is not shown for MODAL_ALERT. All coordinates are in viewport space, so the modal stays centered even in a scrolled window. Use MODAL_ALERT for informational messages that require acknowledgement:
SpawnModal(MODAL_ALERT, "File saved successfully!", NULL);

// With a callback on acknowledgement
void on_ok(void) {
    printf("User acknowledged the alert.\n");
}
SpawnModal(MODAL_ALERT, "Operation complete.", on_ok);
Use MODAL_CONFIRM for yes/no decisions. on_confirm is only called when the user clicks OK:
void do_delete(void) {
    // User confirmed — proceed with deletion
    delete_selected_item();
}

SpawnModal(MODAL_CONFIRM,
           "Are you sure you want to delete this item?\nThis cannot be undone.",
           do_delete);
Use MODAL_INPUT to collect a short text string from the user. Inside on_confirm, call GetModalInput() to read the entered text (up to 150 characters).
const char* GetModalInput();
void on_name_entered(void) {
    const char* name = GetModalInput();
    printf("User entered: %s\n", name);
    // Use name immediately — the buffer is owned by the modal system
    // and will be cleared on the next SpawnModal call
}

SpawnModal(MODAL_INPUT, "Enter your name:", on_name_entered);
GetModalInput() returns a pointer to the modal’s internal char[151] buffer (holds up to 150 characters plus a null terminator). The pointer is valid as long as no new modal is spawned. Copy the string with strncpy if you need it after the next SpawnModal call, as calling SpawnModal again zeros the input buffer.

QueryModalOpen

int QueryModalOpen();
Returns 1 if a modal is currently displayed, 0 otherwise. Use this to suppress global hotkeys and game logic that should pause while the user is interacting with a dialog.
void on_frame(int tick, float dt) {
    if (QueryModalOpen()) return;  // pause game logic during modal

    update_game(dt);
}

void on_key(int key) {
    if (QueryModalOpen()) return;  // ignore hotkeys while modal is open

    if (key == 'd') spawn_debug_modal();
}

Event handling during modals

While a modal is open, the LWXGL event loop intercepts all click and key events before they reach element handlers or user callbacks:
  • Clicks are tested against the OK and Cancel button hit zones only
  • Keys are routed to the modal’s input field (for MODAL_INPUT) or discarded (for MODAL_ALERT / MODAL_CONFIRM)
  • Button onclick callbacks, checkbox toggles, and user callbacks registered via EventAttachClick and EventAttachKey are not called while a modal is open
If you have a MODAL_INPUT dialog open and the user presses Enter (\n), the character is accepted into the input field rather than closing the dialog. The user must click OK to confirm.

Complete modal example

#include "libLWXGL.h"
#include <string.h>
#include <stdio.h>

#define ID_BTN_DELETE  1
#define ID_BTN_RENAME  2
#define ID_BTN_INFO    3
#define ID_LABEL       4

static char current_name[64] = "untitled";

void on_delete_confirmed(void) {
    printf("Deleted: %s\n", current_name);
    strcpy(current_name, "");
    DeleteElement(ID_LABEL);
    CreateCopiedText(ID_LABEL, 20, 200, "(deleted)", CLR_LGRAY);
}

void on_rename_confirmed(void) {
    const char* new_name = GetModalInput();
    if (strlen(new_name) > 0) {
        strncpy(current_name, new_name, sizeof(current_name) - 1);
        DeleteElement(ID_LABEL);
        CreateCopiedText(ID_LABEL, 20, 200, current_name, CLR_WHITE);
    }
}

void on_delete_click(void) {
    char msg[128];
    snprintf(msg, sizeof(msg), "Delete \"%s\"?\nThis cannot be undone.", current_name);
    SpawnModal(MODAL_CONFIRM, msg, on_delete_confirmed);
}

void on_rename_click(void) {
    SpawnModal(MODAL_INPUT, "Enter a new name:", on_rename_confirmed);
}

void on_info_click(void) {
    SpawnModal(MODAL_ALERT, "LWXGL Modal Demo v1.0\nThree modal types supported.", NULL);
}

int main(void) {
    CreateWindow(400, 300, "Modal Demo", CLR_BLACK, FLAG_NONE);

    int u  = (CLR_LGRAY << 4) | CLR_GRAY;
    int hv = (CLR_WHITE << 4) | CLR_LGRAY;
    int pr = (CLR_WHITE << 4) | CLR_WHITE;

    CreateButton(ID_BTN_DELETE, 20,  120, 100, 28, u, hv, pr, "Delete",  on_delete_click);
    CreateButton(ID_BTN_RENAME, 140, 120, 100, 28, u, hv, pr, "Rename",  on_rename_click);
    CreateButton(ID_BTN_INFO,   260, 120, 100, 28, u, hv, pr, "About",   on_info_click);

    CreateText(ID_LABEL, 20, 200, current_name, CLR_WHITE);

    MainWindowLoop(60, NULL);
    TerminateWindow();
    return 0;
}

Scrollable Windows

LWXGL supports scrollable windows by decoupling the back-buffer height from the window height. The back-buffer is taller than the visible area; the window acts as a viewport that slides up and down. A scrollbar is rendered automatically on the right edge of the window when the content is taller than the viewport.

ReserveScroll

void ReserveScroll(int height, int scrollbar_color, void (*Scroll)(int offset));
ReserveScroll must be called before CreateWindow. If the window already exists when ReserveScroll is called, the call is silently ignored.
  • height — total height of the back-buffer in pixels (the scrollable content area)
  • scrollbar_color — packed color for the scrollbar: low nibble = scrollbar track fill, high nibble = scrollbar thumb fill
  • Scroll — callback invoked when the scroll position changes (via mouse wheel or window resize); receives the new offset in pixels. Pass NULL if you don’t need the callback.
// 2000px tall content; scrollbar: gray track (8), white thumb (F)
ReserveScroll(2000, (CLR_WHITE << 4) | CLR_GRAY, on_scroll);
CreateWindow(800, 600, "Scrollable App", CLR_BLACK, FLAG_NONE);

Scrolling mechanics

  • Mouse wheel scrolls 45 pixels per tick (scroll-down = button 5 adds 45px, scroll-up = button 4 subtracts 45px)
  • The scroll position is clamped to [0, back_buffer_height − window_height] using std::clamp
  • When the window is resized (with FLAG_RESIZE), the scroll position is reclamped automatically and the Scroll callback fires if the position changed
  • While scrolling is active, the scrollbar is drawn as a 9px strip on the right window edge

QueryScroll

int QueryScroll();
Returns the current scroll offset in pixels. Use this in your frame callback to position elements relative to the viewport.
void on_frame(int tick, float dt) {
    int scroll = QueryScroll();
    // Elements at y < scroll or y > scroll + win_h are clipped automatically
}

Anchoring Elements to the Viewport

By default, all elements are positioned in back-buffer (content) coordinates, so they scroll with the content. To anchor elements to the viewport so they remain visible regardless of scroll position, use ElemAnchor and ResolveAnchors.
void ElemAnchor(int anchor, int ids[], int count);
void ResolveAnchors();
  • ElemAnchor(anchor, ids, count):
    • When anchor != 0: records the element’s current y as its anchor offset (e->anchor = e->y) and marks it for viewport-relative positioning
    • When anchor == 0: restores the element’s y to the saved anchor value (e->y = e->anchor) and clears the anchor flag by setting e->anchor = INT_MIN, effectively un-anchoring the element
  • ResolveAnchors() — updates all anchored elements so their y = scroll_offset + anchor. Call this each frame (typically at the start of your frame callback).
// Anchor a toolbar to the top of the viewport
int toolbar_ids[] = {ID_TOOLBAR_BG, ID_TOOLBAR_BTN1, ID_TOOLBAR_BTN2};
ElemAnchor(1, toolbar_ids, 3);  // anchor = 1 (non-zero = mark as anchored)

// Each frame, resolve anchors so they track the viewport
void on_frame(int tick, float dt) {
    ResolveAnchors();
    // draw other content...
}

// To un-anchor (restores original y and clears anchor flag)
ElemAnchor(0, toolbar_ids, 3);  // anchor = 0 = unanchor

Complete Example: Scrollable Panel with Anchored Header

This example creates a 2000px tall content area with 40 text labels, a fixed header anchored to the viewport top, and a scroll position indicator.
#include "libLWXGL.h"
#include <stdio.h>

#define WIN_W 600
#define WIN_H 400
#define CONTENT_H 2000

#define ID_HEADER_BG   0
#define ID_HEADER_TXT  1
#define ID_SCROLL_INFO 2
#define ID_FIRST_LABEL 10  // labels use IDs 10–49

static char scroll_info[32];

void on_scroll(int offset) {
    snprintf(scroll_info, sizeof(scroll_info), "Scroll: %d px", offset);
    DeleteElement(ID_SCROLL_INFO);
    CreateCopiedText(ID_SCROLL_INFO, WIN_W - 120, 5, scroll_info, CLR_LGRAY);
}

void on_frame(int tick, float dt) {
    // Keep anchored elements tracking the viewport
    ResolveAnchors();
}

int main(void) {
    // Set up scrolling BEFORE CreateWindow
    ReserveScroll(CONTENT_H, (CLR_WHITE << 4) | CLR_GRAY, on_scroll);
    CreateWindow(WIN_W, WIN_H, "Scrollable Panel", CLR_BLACK, FLAG_NONE);

    // --- Anchored header bar ---
    CreateRect(ID_HEADER_BG, 0, 0, WIN_W, 30, CLR_WHITE, CLR_GRAY);
    CreateText(ID_HEADER_TXT, 10, 7, "Scrollable Content Demo", CLR_WHITE);
    CreateText(ID_SCROLL_INFO, WIN_W - 120, 5, "Scroll: 0 px", CLR_LGRAY);

    // Anchor all three header elements to the viewport top
    int header_ids[] = {ID_HEADER_BG, ID_HEADER_TXT, ID_SCROLL_INFO};
    ElemAnchor(1, header_ids, 3);

    // --- Scrollable content labels ---
    char label_buf[64];
    for (int i = 0; i < 40; i++) {
        snprintf(label_buf, sizeof(label_buf), "Item %d — y=%d", i + 1, 40 + i * 48);
        CreateCopiedText(ID_FIRST_LABEL + i, 20, 40 + i * 48, label_buf, CLR_LCYAN);
    }

    MainWindowLoop(60, on_frame);
    TerminateWindow();
    return 0;
}
Elements that are entirely outside the current viewport (y + h < scroll or y > scroll + win_h) are skipped by the renderer automatically, so large scrollable content does not incur a per-element draw cost for off-screen widgets.

Build docs developers (and LLMs) love