Skip to content

Instantly share code, notes, and snippets.

@erik-nilcoast
Created July 3, 2026 20:29
Show Gist options
  • Select an option

  • Save erik-nilcoast/4f082288dd32f85f06e541423ffb2c70 to your computer and use it in GitHub Desktop.

Select an option

Save erik-nilcoast/4f082288dd32f85f06e541423ffb2c70 to your computer and use it in GitHub Desktop.
boxc - single-header C99 box-model layout engine
#pragma once
// A minimal, allocation-free box model: one flex axis per box, nestable for
// 2D layouts (a row of columns, a column of rows, ...). Callers own all Box
// memory -- static, stack, or embedded in a larger struct -- box_layout()
// only reads the sizing fields below and writes x/y/w/h back.
//
// Single-header: this file is always just declarations. In exactly one C
// file, #define BOX_IMPLEMENTATION before #include "box.h" to pull in the
// implementation -- defining it in more than one translation unit is a
// duplicate-symbol link error, same as any other stb-style header.
typedef enum { BOX_ROW, BOX_COLUMN } BoxDir;
typedef enum { BOX_START, BOX_CENTER, BOX_END, BOX_SPACE_BETWEEN } BoxAlign;
typedef struct Box {
// input: size along the PARENT's main axis. A fixed box claims exactly
// `fixed` px; a flexible box (fixed == 0) shares leftover space by `grow`.
int fixed;
int grow;
// input: size along the parent's cross axis. 0 stretches to fill it.
int cross;
// input: this box's own layout, applied to its children.
BoxDir dir;
BoxAlign align_main, align_cross; // align_cross ignores SPACE_BETWEEN
int padding, gap;
struct Box *children;
int n_children;
// output: filled in by box_layout().
int x, y, w, h;
} Box;
// Resolves x/y/w/h for `root` and its whole subtree, given the rect `root`
// must fit into.
void box_layout(Box *root, int x, int y, int w, int h);
#ifdef BOX_IMPLEMENTATION
#include <stdbool.h>
// A child's resolved main-axis size is a pure function of its own fixed/grow
// fields plus the parent's leftover/grow_total -- recomputing it here instead
// of caching it in a temp array avoids needing an allocation or a fixed cap
// on child count.
static int child_main(const Box *c, int leftover, int grow_total) {
if (c->fixed > 0) return c->fixed;
return grow_total > 0 ? leftover * c->grow / grow_total : 0;
}
static void layout_children(Box *b) {
bool row = b->dir == BOX_ROW;
int main = row ? b->w : b->h;
int cross = row ? b->h : b->w;
int gaps = b->n_children > 1 ? b->n_children - 1 : 0;
int avail = main - 2 * b->padding - b->gap * gaps;
int fixed_total = 0, grow_total = 0;
for (int i = 0; i < b->n_children; i++) {
Box *c = &b->children[i];
if (c->fixed > 0)
fixed_total += c->fixed;
else
grow_total += c->grow;
}
int leftover = avail - fixed_total;
if (leftover < 0) leftover = 0;
int used = 0;
for (int i = 0; i < b->n_children; i++) used += child_main(&b->children[i], leftover, grow_total);
int gap = b->gap;
int pos = b->padding;
int slack = avail - used - gap * gaps;
if (slack < 0) slack = 0;
switch (b->align_main) {
case BOX_CENTER: pos += slack / 2; break;
case BOX_END: pos += slack; break;
case BOX_SPACE_BETWEEN:
if (gaps > 0) gap += slack / gaps;
break;
case BOX_START: break;
}
for (int i = 0; i < b->n_children; i++) {
Box *c = &b->children[i];
int size = child_main(c, leftover, grow_total);
int cross_size = c->cross > 0 ? c->cross : cross - 2 * b->padding;
int cross_pos = b->padding;
if (c->cross > 0) {
int cross_slack = cross - 2 * b->padding - c->cross;
if (cross_slack < 0) cross_slack = 0;
switch (b->align_cross) {
case BOX_CENTER: cross_pos += cross_slack / 2; break;
case BOX_END: cross_pos += cross_slack; break;
default: break; // START, SPACE_BETWEEN (not meaningful cross-axis)
}
}
int cx = b->x + (row ? pos : cross_pos);
int cy = b->y + (row ? cross_pos : pos);
int cw = row ? size : cross_size;
int ch = row ? cross_size : size;
box_layout(c, cx, cy, cw, ch);
pos += size + gap;
}
}
void box_layout(Box *b, int x, int y, int w, int h) {
b->x = x;
b->y = y;
b->w = w;
b->h = h;
if (b->n_children > 0) layout_children(b);
}
#endif // BOX_IMPLEMENTATION
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment