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 renders every frame into a hidden back-buffer pixmap and then blits the finished image to the visible window in a single Xlib call. This double-buffering approach eliminates tearing without requiring hardware acceleration. Understanding how frames are constructed — and the order in which the callback, elements, overlays, and the final blit occur — lets you place drawing calls in the right place and avoid flickering or draw-order surprises.

The Back-Buffer Pixmap (bb)

The internal bb struct wraps an X Pixmap. All drawing functions — element renderers, immediate-mode calls, and LWXGL’s own overlays — write exclusively to bb. At the very end of each frame, XCopyArea copies the visible portion of bb to the actual Window, followed by XSync.
┌─────────────────────────────────┐
│  bb (back-buffer Pixmap)        │
│  width = win_w                  │
│  height = win_h (or scroll.h)   │
│                                 │
│  All drawing happens here       │
└──────────────┬──────────────────┘
               │ XCopyArea (end of frame)

┌─────────────────────────────────┐
│  Window (visible on screen)     │
└─────────────────────────────────┘
When scroll is enabled via ReserveScroll, bb is taller than the window (bb.h equals the full document height). The viewport into bb is tracked by bb.scroll, and XCopyArea copies the strip [0, bb.scroll, win_w, win_h] to the window.

Frame Rendering Order

Each frame is produced by _render_window, called from MainWindowLoop. The sequence is fixed:
  1. If FLAG_BYPASS — call on_every(tick, dt) and XSync, then return immediately. No further steps execute.
  2. Clear the back-bufferXFillRectangle fills the viewport strip with the background palette color (bgcol).
  3. ORDER_ELEM_SECOND (default) — run on_every before drawing elements.
  4. Draw all visible elements — iterate the elements vector; for each non-NULL, visible entry whose bounding box intersects the viewport, call the appropriate renderer.
  5. ORDER_ELEM_FIRST — run on_every after drawing elements (only when this order is active).
  6. Render scrollbar — if scroll is enabled and bb.h > win_h, draw the scrollbar track and thumb.
  7. Render modal — if a modal dialog is active (QueryModalOpen() returns non-zero), draw it on top of everything.
  8. Render debug overlay — if F12 has been pressed (toggling debug_metrics.enabled), draw the frame-time and FPS overlay.
  9. Blit to windowXCopyArea(display, bb, window, gc, 0, bb.scroll, win_w, win_h, 0, 0) followed by XSync(display, False).

Controlling Element vs. Callback Order

void SetRenderingOrder(int order);
ConstantValueEffect
ORDER_ELEM_SECOND0on_every runs first, then elements are drawn on top (default)
ORDER_ELEM_FIRST1Elements are drawn first, then on_every draws on top
// Draw elements first, overlay custom HUD on top via on_every
SetRenderingOrder(ORDER_ELEM_FIRST);
The default ORDER_ELEM_SECOND means elements always appear in front of anything drawn by on_every. Switch to ORDER_ELEM_FIRST when you want your on_every callback to draw a HUD or overlay that must appear above widgets.

Immediate-Mode Drawing Functions

Immediate-mode functions draw directly to bb at the moment they are called. They must be called from within on_every (or, for FLAG_BYPASS mode, from the same callback). Unlike elements, immediate draws produce no persistent state — they must be re-issued every frame.

ImmediateText

void ImmediateText(int x, int y, const char* str, int color);
Draws str starting at (x, y) in the given palette color. Newlines (\n) advance y by 15 pixels (the font row height). The y coordinate is a top-of-text position; the function adds 11 pixels internally to align the Xlib baseline correctly.

ImmediateRect

void ImmediateRect(int x, int y, int w, int h, int fg, int bg);
Draws a filled rectangle if bg >= 0 (XFillRectangle), then an outline if fg >= 0 (XDrawRectangle). Pass CLR_NONE (-1) for either to skip that draw.

ImmediateEllipse

void ImmediateEllipse(int x, int y, int w, int h, int fg, int bg);
Draws a filled arc (ellipse) if bg >= 0, then an outline arc if fg >= 0. The bounding box is (x, y, w, h), matching the Xlib XDrawArc convention.

ImmediateLine

void ImmediateLine(int x1, int y1, int x2, int y2, int color);
Draws a single-pixel line from (x1, y1) to (x2, y2) in the given palette color.

Elements vs. Immediates

ElementsImmediate-mode
PersistenceStored in elements vector, rendered every frame automaticallyMust be re-called every frame
InteractionElemInside, click/hover callbacksManual hit testing
Visibility toggleElemSetVisibleOmit the call for that frame
Best forStatic or long-lived widgetsDynamic overlays, HUDs, per-frame shapes

FLAG_BYPASS Mode

When CreateWindow is called with FLAG_BYPASS, the entire LWXGL rendering pipeline is skipped. _render_window calls on_every(tick, dt), then calls XSync, and returns. No background clear, no element iteration, no overlays, no XCopyArea — all of that becomes your responsibility.
void bypass_frame(int tick, float dt) {
    XConnectionData xd;
    GetXConnection(&xd);

    // Manually clear the window
    XSetForeground(xd.dpy, xd.gc, xd.clrs[CLR_BLACK]);
    XFillRectangle(xd.dpy, xd.win, xd.gc, 0, 0, 800, 600);

    // Draw directly to the window
    XSetForeground(xd.dpy, xd.gc, xd.clrs[CLR_LGREEN]);
    XDrawString(xd.dpy, xd.win, xd.gc, 10, 20, "Custom Xlib", 11);
}
In FLAG_BYPASS mode, bb still exists as an XPixmap but LWXGL does not clear or blit it. If you want double-buffering in bypass mode, use GetXConnection to obtain bb and perform the XCopyArea yourself at the end of each frame.

The Scroll System

Calling ReserveScroll(height, scrollbar_color, Scroll) before CreateWindow allocates a bb pixmap whose height equals height rather than win_h. The bb.scroll field tracks the current vertical offset within the pixmap. During rendering, the viewport clear and XCopyArea both reference bb.scroll, so the visible portion of the document shifts smoothly as the user scrolls. The scrollbar is rendered automatically at the right edge of the window when bb.h > win_h. QueryScroll() returns the current bb.scroll value in pixels, which is useful for positioning custom drawings in document space from within on_every.

The Debug Overlay

Pressing F12 at runtime toggles debug_metrics.enabled. When enabled, a small black box with a white border is drawn in the top-left corner of the viewport during every frame, showing:
  • FT: N (us) — the 60-frame rolling average of work time per frame in microseconds
  • FPS: N.N — the instantaneous frames-per-second calculated from actual elapsed time
The overlay is drawn after modal rendering (step 8 in the frame sequence) so it always appears on top of all other content, including modals.

The Task Queue

NewQueuedTask schedules a one-shot callback to fire after a given number of elapsed seconds:
void NewQueuedTask(float run_after, void (*task)());
The task is stored with a target_time = elapsed_time + run_after. After each rendered frame, MainWindowLoop iterates the queue and fires any tasks whose target_time <= elapsed_time, removing them from the queue immediately after execution.
// Print a message to the console 3 seconds after app start
NewQueuedTask(3.0f, []() {
    ConsolePrint(console_id, "3 seconds have passed\n");
});
Tasks fire in the main loop thread, so it is safe to call any LWXGL function from within a task callback.

GetElapsedTime

float GetElapsedTime();
Returns the total elapsed time in seconds since MainWindowLoop began, accumulated from actual measured frame deltas. This is the same clock used by the task queue’s target_time comparisons, so you can schedule tasks relative to GetElapsedTime() for precision timing.
void on_frame(int tick, float dt) {
    float t = GetElapsedTime();
    // Pulse an element's visibility on a 1-second period
    ElemSetVisible(5, (int)(t) % 2 == 0);
}

Build docs developers (and LLMs) love