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 provides a set of retained-mode UI widgets that are each identified by an integer ID. You create a widget once, and the renderer draws it every frame until you delete it. All widget IDs are global — use unique integers across your entire application to avoid collisions. Widgets are rendered in the order they appear in the element list, and their positions are in back-buffer coordinates. This guide walks through every built-in widget type with real usage examples.

Color Packing

Several widgets accept packed color integers where the low nibble (bits 0–3) is the fill color and the high nibble (bits 4–7) is the border or outline color. LWXGL defines 16 named palette constants you can combine:
// Low nibble = fill, high nibble = border
// Example: white fill (0xF), gray border (0x8) → 0x8F
int style = (CLR_GRAY << 4) | CLR_WHITE;  // 0x8F
Use CLR_NONE (-1) wherever a fill or border should be skipped entirely (transparent). The full palette:
ConstantIndexConstantIndex
CLR_BLACK0x0CLR_GRAY0x8
CLR_BLUE0x1CLR_LBLUE0x9
CLR_GREEN0x2CLR_LGREEN0xA
CLR_CYAN0x3CLR_LCYAN0xB
CLR_RED0x4CLR_LRED0xC
CLR_MAGENTA0x5CLR_LMAGENTA0xD
CLR_ORANGE0x6CLR_YELLOW0xE
CLR_LGRAY0x7CLR_WHITE0xF

Widget Types

Text elements render a single string at a fixed position. LWXGL offers two variants depending on who owns the string memory.

Signature

void CreateText(int id, int x, int y, const char* text, int color);
void CreateCopiedText(int id, int x, int y, const char* text, int color);
  • id — unique element ID
  • x, y — top-left position in back-buffer coordinates
  • text — pointer to the string to display
  • color — palette index (0–15) for the text foreground

CreateText vs CreateCopiedText

CreateText stores the pointer you pass — it does not copy the string. The string must remain valid for the lifetime of the element. This is ideal for string literals or long-lived buffers.CreateCopiedText calls strdup internally and stores a heap copy. When DeleteElement is called the copied string is freed automatically (because the element’s copied flag is true). Use this when the source string is ephemeral (e.g. a local char[], a sprintf buffer, or a string you plan to free).
// Safe: string literal lives forever
CreateText(10, 20, 50, "Hello, world!", CLR_WHITE);

// Safe: dynamic string — LWXGL owns the copy
char msg[64];
snprintf(msg, sizeof(msg), "Score: %d", score);
CreateCopiedText(11, 20, 70, msg, CLR_YELLOW);
// msg can now be modified or go out of scope
If you use CreateText with a stack buffer or a string you later free, the element will hold a dangling pointer. Prefer CreateCopiedText for any dynamically generated strings.

Deleting text elements

DeleteElement(10);  // frees the heap copy if CreateCopiedText was used
Buttons render a labeled rectangle with three visual states: unpressed, hovered, and pressed. Clicking the button (mouse button 1 release while inside) fires the onclick callback.

Signature

void CreateButton(int id, int x, int y, int w, int h,
                  int u, int hvr, int p,
                  const char* label, void (*onclick)(void));
  • id — unique element ID
  • x, y — top-left position
  • w, h — button dimensions in pixels
  • u — packed color for the unpressed state: low nibble = fill, high nibble = border
  • hvr — packed color for the hovered state
  • p — packed color for the pressed (mouse-down) state
  • label — button label, centered automatically; the pointer is stored directly (not copied) so the string must remain valid for the element’s lifetime
  • onclick — function called on left-click release; may be NULL

Color packing example

// Unpressed: dark gray fill, light gray border  → 0x87
// Hovered:   light gray fill, white border      → 0xF7
// Pressed:   white fill, white border           → 0xFF
int u   = (CLR_LGRAY << 4) | CLR_GRAY;   // 0x87
int hvr = (CLR_WHITE << 4) | CLR_LGRAY;  // 0xF7
int p   = (CLR_WHITE << 4) | CLR_WHITE;  // 0xFF

Complete example

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

void on_save_clicked(void) {
    printf("Save button clicked!\n");
}

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

    int u   = (CLR_LGRAY << 4) | CLR_GRAY;
    int hvr = (CLR_WHITE << 4) | CLR_LGRAY;
    int p   = (CLR_WHITE << 4) | CLR_WHITE;

    CreateButton(1, 150, 130, 100, 30, u, hvr, p, "Save", on_save_clicked);

    MainWindowLoop(60, NULL);
    TerminateWindow();
    return 0;
}
The label pointer is stored by reference — use a string literal or a buffer that outlives the element. To use a dynamic label, keep the buffer alive or rebuild the button when the label changes.
Input elements are single-line text fields. They capture keyboard input when the mouse cursor is inside them and display a blinking cursor (_) while focused.

Signature

void CreateInput(int id, int x, int y, int w, int h, int u, int hvr, int max);
char* GetInput(int id);
  • id — unique element ID
  • x, y — top-left position
  • w — width in pixels; pass -1 to auto-size: (max + 1) * 9 + 10
  • h — height in pixels
  • u — packed color for the inactive state (low nibble = fill, high nibble = border)
  • hvr — packed color for the hovered/active state
  • max — maximum number of characters; capped internally at min(max, 127)
  • GetInput(id) — returns a pointer to the 128-byte internal char input[128] buffer; valid until the element is deleted

Auto-sizing width

Passing w = -1 sizes the field to hold exactly max characters in LWXGL’s 9px-wide font:
// Auto-width for 20 characters: (20+1)*9+10 = 199px
CreateInput(5, 20, 100, -1, 25, 0x87, 0xF7, 20);

Form example

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

#define ID_LABEL_NAME  1
#define ID_INPUT_NAME  2
#define ID_LABEL_EMAIL 3
#define ID_INPUT_EMAIL 4
#define ID_SUBMIT      5

void on_submit(void) {
    char* name  = GetInput(ID_INPUT_NAME);
    char* email = GetInput(ID_INPUT_EMAIL);
    printf("Name: %s  Email: %s\n", name, email);
}

int main(void) {
    CreateWindow(400, 200, "Form Demo", CLR_BLACK, FLAG_NONE);

    int inactive = (CLR_LGRAY << 4) | CLR_GRAY;
    int hover    = (CLR_WHITE << 4) | CLR_LGRAY;

    CreateText(ID_LABEL_NAME,  20, 40,  "Name:",  CLR_WHITE);
    CreateInput(ID_INPUT_NAME,  80, 30, -1, 25, inactive, hover, 24);

    CreateText(ID_LABEL_EMAIL,  20, 80,  "Email:", CLR_WHITE);
    CreateInput(ID_INPUT_EMAIL, 80, 70, -1, 25, inactive, hover, 40);

    int bu = (CLR_GREEN  << 4) | CLR_LGREEN;
    int bh = (CLR_LGREEN << 4) | CLR_WHITE;
    int bp = (CLR_WHITE  << 4) | CLR_WHITE;
    CreateButton(ID_SUBMIT, 140, 120, 80, 28, bu, bh, bp, "Submit", on_submit);

    MainWindowLoop(60, NULL);
    TerminateWindow();
    return 0;
}
GetInput returns the live internal buffer. Copy it with strncpy if you need to preserve the value after the user edits the field.
Checkboxes render a square box next to an optional text label. Clicking the box (left-click) toggles the checked state.

Signature

void CreateCheckbox(int id, int x, int y, int size, int cb_col, int txt_col, const char* label);
int GetCheckbox(int id);
  • id — unique element ID
  • x, y — top-left position of the box
  • size — pixel width and height of the box (always square; w and h are both set to size)
  • cb_col — packed color for the box: low nibble = fill, high nibble = border
  • txt_col — palette index for the label text
  • label — text displayed to the right of the box; may be NULL for no label
  • GetCheckbox(id) — returns 1 if checked, 0 if unchecked

Hit area

The clickable region extends beyond the box itself. When a label is present the hit area includes the label text: the right edge is x + size + 10 + strlen(label) * 9. This means clicking on the label text also toggles the checkbox.

Example

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

#define ID_CHECK_SOUND 10
#define ID_CHECK_MUSIC 11
#define ID_APPLY       12

void on_apply(void) {
    int sound = GetCheckbox(ID_CHECK_SOUND);
    int music = GetCheckbox(ID_CHECK_MUSIC);
    printf("Sound: %s  Music: %s\n",
           sound ? "on" : "off",
           music ? "on" : "off");
}

int main(void) {
    CreateWindow(300, 200, "Settings", CLR_BLACK, FLAG_NONE);

    int cb = (CLR_LGRAY << 4) | CLR_WHITE;  // fill=gray, border=white

    CreateCheckbox(ID_CHECK_SOUND, 40, 60,  16, cb, CLR_WHITE, "Enable sound");
    CreateCheckbox(ID_CHECK_MUSIC, 40, 90,  16, cb, CLR_WHITE, "Enable music");

    int bu = (CLR_GRAY  << 4) | CLR_LGRAY;
    int bh = (CLR_LGRAY << 4) | CLR_WHITE;
    int bp = (CLR_WHITE << 4) | CLR_WHITE;
    CreateButton(ID_APPLY, 100, 140, 80, 28, bu, bh, bp, "Apply", on_apply);

    MainWindowLoop(60, NULL);
    TerminateWindow();
    return 0;
}
Rectangles are static colored boxes. They are useful as backgrounds, dividers, and decorative borders.

Signature

void CreateRect(int id, int x, int y, int w, int h, int fg, int bg);
  • id — unique element ID
  • x, y — top-left position
  • w, h — dimensions
  • fg — border color (palette index), or CLR_NONE to skip the border
  • bg — fill color (palette index), or CLR_NONE for a transparent fill
CreateRect has a known rendering bug: the renderer passes e->y instead of e->h as the height argument to ImmediateRect, so the element does not draw correctly. For rectangles that must render properly, use ImmediateRect directly inside your frame callback instead:
void on_frame(int tick, float dt) {
    // Correct: ImmediateRect draws properly
    ImmediateRect(10, 10, 300, 150, CLR_WHITE, CLR_GRAY);
}

Example

// NOTE: due to the rendering bug, use ImmediateRect in on_frame for correct output.

void on_frame(int tick, float dt) {
    // Solid white-bordered panel with a dark background
    ImmediateRect(10, 10, 300, 150, CLR_WHITE, CLR_GRAY);

    // Border only (no fill)
    ImmediateRect(15, 15, 290, 140, CLR_LGRAY, CLR_NONE);

    // Solid fill, no border
    ImmediateRect(50, 50, 100, 40, CLR_NONE, CLR_BLUE);
}
Ellipses (including circles) are rendered as filled and/or outlined arc shapes.

Signature

void CreateEllipse(int id, int x, int y, int w, int h, int fg, int bg);
  • id — unique element ID
  • x, y — top-left of the bounding box
  • w, h — width and height of the bounding box (equal values produce a circle)
  • fg — outline color, or CLR_NONE
  • bg — fill color, or CLR_NONE
The renderer calls ImmediateEllipse which uses XFillArc for the fill and XDrawArc for the outline.

Example

// Filled green circle, 60px diameter, white outline
CreateEllipse(30, 170, 170, 60, 60, CLR_WHITE, CLR_GREEN);

// Outline-only ellipse (no fill)
CreateEllipse(31, 50, 80, 120, 60, CLR_CYAN, CLR_NONE);
The console widget is a scrollable, bordered text panel with a built-in scrollbar. It is ideal for log output, debug panels, and terminal-style displays. It auto-wraps text at cols characters and scrolls vertically. Mouse-wheel scrolling is handled automatically when the cursor is inside the widget.

Signature

void CreateConsole(int id, int x, int y, int cols, int rows, int con_clr, int txt_clr);
void ConsolePrint(int id, const char* format, ...);
void ConsoleClear(int id);
  • id — unique element ID
  • x, y — top-left position
  • cols — number of character columns (auto-sizes pixel width to cols * 9 + 17)
  • rows — number of visible text rows (auto-sizes pixel height to rows * 15 + 10)
  • con_clr — packed background+border color: low nibble = console fill, high nibble = console border and scrollbar thumb
  • txt_clr — packed text color: high nibble = text color, low nibble = hover status overlay text color
  • ConsolePrint — printf-style formatted output (uses vasprintf internally) appended to the console buffer; auto-scrolls to the bottom if the view was already at the bottom when the print occurred
  • ConsoleClear — clears all text and resets scroll to zero

Live log panel example

#include "libLWXGL.h"
#include <time.h>

#define ID_LOG_CONSOLE 40

static int tick_count = 0;

void on_frame(int tick, float dt) {
    // Print a log line every 60 ticks (~1 second at 60 fps)
    if (tick % 60 == 0) {
        ConsolePrint(ID_LOG_CONSOLE, "[%4d] Frame tick — dt=%.4fs\n",
                     tick_count++, dt);
    }
}

int main(void) {
    CreateWindow(520, 300, "Log Panel", CLR_BLACK, FLAG_NONE);

    // con_clr: black fill (0), white border+thumb (F) → 0xF0
    // txt_clr: light green text (A high nibble), gray hover text (8 low nibble) → 0xA8
    CreateConsole(ID_LOG_CONSOLE, 10, 10, 55, 17, 0xF0, 0xA8);

    ConsolePrint(ID_LOG_CONSOLE, "Application started.\n");

    MainWindowLoop(60, on_frame);
    TerminateWindow();
    return 0;
}
ConsolePrint accepts the same format strings as printf, including %d, %s, %f, and \n for newlines. Tabs (\t) are expanded to 4 spaces during rendering.
Pressing Space while the cursor is inside a console element jumps instantly to the last line. Mouse-wheel scrolls 3 lines per tick.

Common Element Operations

These functions work on any element type and are identified by the same integer ID used at creation.

ElemModifyBounds

Move or resize an element. Pass -1 for w or h to leave that dimension unchanged.
void ElemModifyBounds(int id, int x, int y, int w, int h);

// Move element 5 to (100, 200), keep its size
ElemModifyBounds(5, 100, 200, -1, -1);

// Resize element 5 to 200×40, keep its position
ElemModifyBounds(5, -1, -1, 200, 40);  // Note: pass current x/y explicitly for safety

ElemSetVisible

Show or hide an element without deleting it.
void ElemSetVisible(int id, int visible);  // visible: 1=show, 0=hide

ElemSetVisible(10, 0);  // hide
ElemSetVisible(10, 1);  // show again

ElemInside

Returns 1 if the mouse cursor is currently inside the element’s bounding box, 0 otherwise. Useful for hover effects in the frame callback.
void on_frame(int tick, float dt) {
    if (ElemInside(5)) {
        // cursor is over element 5
    }
}

DeleteElement

Frees all resources associated with an element. For CreateCopiedText elements the heap string is freed. For image elements the pixmap and pixel buffers are freed.
void DeleteElement(int id);

DeleteElement(5);
After DeleteElement, do not pass that ID to any element function until you recreate it. The slot is set to NULL but not removed from the list, so the ID is safe to reuse.

Build docs developers (and LLMs) love