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’s element system is the primary way you place visible widgets on screen. Every widget — text labels, buttons, input fields, images, and more — is stored as an Element struct in a global vector and referenced by a caller-supplied integer ID. Understanding how that vector works, how IDs are assigned, and how the system cleans up is fundamental to writing correct LWXGL applications.

The Element Struct

Every widget is wrapped in an Element, defined in elements/structs.cc:
typedef struct {
    int x, y, w, h;   // Position and size in pixels
    int v;             // Visibility: 1 = visible, 0 = hidden
    int anchor;        // INT_MIN = not anchored; otherwise, pixel offset from scroll origin
    int type;          // Widget type index (see table below)
    void *elem;        // Pointer to the type-specific data struct
} Element;
The elem pointer is cast to the appropriate struct at render time and when the element is deleted. The type field determines which struct elem points to and which renderer function handles it.

Element Types

Type IDNameStructCreate Function
0TextTextElementCreateText / CreateCopiedText
1ButtonButtonElementCreateButton
2InputInputElementCreateInput
3RectRectElementCreateRect
4ImageImageElementCreateImage / CreateTGAImage
5CheckboxCheckboxElementCreateCheckbox
6ConsoleConsoleElementCreateConsole
7EllipseEllipseElementCreateEllipse

The elements Vector

All Element* pointers live in a global std::vector<Element*> elements. This vector is sparse: it may contain NULL entries at positions that have been deleted or never used. The vector grows on demand but never shrinks. The renderer skips NULL entries during every frame.

_allocate_element Behavior

Every Create* function calls the internal _allocate_element(id, type, data, x, y, w, h) helper, which performs two operations before inserting:
  1. Resize if needed — if id >= elements.size(), the vector is extended with NULL fills up to id + 1.
  2. Replace if occupied — if elements[id] != NULL, DeleteElement(id) is called first, freeing the old element’s type-specific data and the Element struct.
The newly created Element is then stored at elements[id] with v = 1 (visible) and anchor = INT_MIN (not anchored).
Because _allocate_element calls DeleteElement on an occupied slot, creating an element at an existing ID is a safe, complete replacement — not a memory leak.

Choosing Element IDs

IDs are arbitrary non-negative integers that you choose at call time. There is no auto-increment; the library never picks an ID for you. A few practical conventions:
// Reserve low IDs for persistent UI chrome
CreateButton(0, 10, 10, 80, 25, ...);   // "Save" button
CreateButton(1, 100, 10, 80, 25, ...);  // "Open" button

// Use higher IDs for dynamic content
for (int i = 0; i < item_count; i++) {
    CreateText(100 + i, 10, 50 + i * 20, item_labels[i], CLR_WHITE);
}
When CreateWindow is called with FLAG_CANVAS, ID 0 is reserved for the full-window canvas ImageElement. Reserve a different ID range for your other widgets in that case.

DeleteElement

void DeleteElement(int id);
Frees the element at id in full, then sets elements[id] = NULL. The type-specific cleanup varies:
  • Text (0) — frees the text string only if it was duplicated by CreateCopiedText (copied == true)
  • Button (1), Input (2), Rect (3), Checkbox (5), Console (6)delete the type struct
  • Image (4) — destroys the XImage, frees data and prev pixel buffers, frees the custom font buffer if set, and calls XFreePixmap on the backing pixmap

ElemModifyBounds

void ElemModifyBounds(int id, int x, int y, int w, int h);
Moves and/or resizes an existing element. Pass -1 for w or h to leave that dimension unchanged.
// Move button to (200, 50) without changing its size
ElemModifyBounds(0, 200, 50, -1, -1);

// Move AND resize the button
ElemModifyBounds(0, 200, 50, 120, 30);
ElemModifyBounds updates the Element struct directly. For ImageElement types, it does not reallocate the underlying pixmap — use UpdateImage to push new pixel data after resizing.

ElemSetVisible

void ElemSetVisible(int id, int visible);
Sets the element’s v flag to visible (1 = show, 0 = hide). Hidden elements are skipped by both the renderer and the ElemInside hit test, so they do not receive hover or click events. The element data is preserved; calling ElemSetVisible(id, 1) restores it to the screen unchanged.
ElemSetVisible(10, 0);  // Hide element 10
ElemSetVisible(10, 1);  // Show element 10 again

ElemInside

int ElemInside(int id);
Returns 1 if the current mouse position is over the element’s bounding box and the element is visible and no modal is open; returns 0 otherwise. The y check accounts for the current scroll offset, so this works correctly in scrollable windows. For Checkbox elements the hit region is extended rightward to include the label text, mirroring the internal _inside_elem logic.
void on_frame(int tick, float dt) {
    if (ElemInside(5)) {
        ImmediateText(10, 400, "Hovering over element 5", CLR_YELLOW);
    }
}

Anchoring Elements to the Scroll Position

In a scrollable window the bb back-buffer is taller than the visible viewport. Most elements scroll naturally because their y coordinates are in document space. Sometimes, however, you want an element to stay fixed in the viewport (e.g., a toolbar or a status bar at the bottom). That is what anchoring is for.

ElemAnchor

void ElemAnchor(int anchor, int ids[], int count);
  • anchor = 0 — removes anchoring from the listed elements, restoring each element’s y to its saved pre-anchor position.
  • anchor ≠ 0 — records each element’s current y as a viewport offset (stored in e->anchor).
// Anchor elements 0, 1, and 2 to the viewport
int toolbar_ids[] = {0, 1, 2};
ElemAnchor(1, toolbar_ids, 3);

ResolveAnchors

void ResolveAnchors();
Call this each frame (typically at the start of your on_every callback) to update every anchored element’s y to bb.scroll + e->anchor. This keeps anchored elements pinned at their viewport offset regardless of the current scroll position.
void on_frame(int tick, float dt) {
    ResolveAnchors();          // Keep toolbar pinned
    // ... rest of frame logic
}
ResolveAnchors iterates the entire elements vector, so call it once per frame rather than multiple times.

Practical ID Range Conventions

Because IDs are arbitrary integers there is no enforced convention, but the following approach scales well:
// Persistent UI controls: IDs 0–9
#define BTN_SAVE   0
#define BTN_OPEN   1
#define LBL_STATUS 2

// Dynamic list items: IDs 10–99
#define LIST_BASE  10

// Images and canvas: IDs 100+
#define IMG_LOGO   100
Keeping ranges separate makes it easy to delete an entire logical group by iterating a known range, and avoids accidental ID collisions when different parts of the application create elements independently.

Build docs developers (and LLMs) love