Icon System
Orion’s icon system has one source of truth: SVG files from iconoir in share/icons/. At startup, nanosvg rasterizes those SVGs into GPU texture sheets (bitmap_strip_t). No compiled-in PNG sheets exist; the old icon_sheet_16x16.png and filepicker.png have been deleted.
Architecture
share/icons/*.svg
│
│ startup (svg_build_strip)
▼
nanosvg → RGBA pixel buffer → R_CreateTextureRGBA → GPU texture
│
▼
bitmap_strip_t { tex, icon_w, icon_h, cols, sheet_w, sheet_h }
│
├─ g_sysicon_strip (sysicon_* enum, SYSICON_SIZE × SYSICON_SIZE, 32 cols)
├─ g_icons_strip (icon_id_t enum, 16 × 16, 16 cols)
└─ g_tool_strip (IE_ICONS enum, 24 × 24, 16 cols)
↑ imageeditor only
currentColor in every SVG is patched to white before rasterizing, so icons render as white-on-alpha and can be tinted at draw time by passing any uint32_t color to draw_icon16 / draw_toolbar_icon_in_rect.
The three icon strips
| Strip | Index enum | Tile size | Source | Loaded by |
|---|---|---|---|---|
| sysicon | sysicon_* in orion/user/icons.h | SYSICON_SIZE (24 px) | share/icons/*.svg | orion/user/init.c |
| picker | icon_id_t in orion/user/sysicons.h | 16 px | share/icons/*.svg | orion/user/init.c |
| imageeditor tools | IE_ICONS in apps/imageeditor/image-editor.h | 24 px | share/icons/*.svg | apps/imageeditor/windows/win_toolpalette.c |
All three strips read from the same flat directory (share/icons/). File names match the iconoir regular-weight name, e.g. arrow-up.svg, trash.svg.
SYSICON_SIZE and toolbar sizing
SYSICON_SIZE = 24 is defined in orion/user/messages.h and drives the toolbar button area:
#define SYSICON_SIZE 24 // canonical SVG tile size
#define TOOLBAR_HEIGHT (SYSICON_SIZE + 4) // button = icon + 2 px each side
#define TB_SPACING TOOLBAR_HEIGHT // toolbar buttons are square
Changing SYSICON_SIZE automatically resizes toolbar buttons, vertical toolbars, and the icon sheet tile size. No other constants need touching.
Using icons in code
Toolbar buttons (.orion files — preferred)
<!-- reference icons by SVG base name; loaded on demand, no enum needed -->
<Button name="commit" command="commit.commit" icon="git-commit" text="Commit"/>
<Button name="undo" command="commit.undo" icon="undo" text="Undo" />
Toolbar buttons (manual C arrays)
static const toolbar_item_t k_items[] = {
{ TOOLBAR_ITEM_BUTTON, ID_DELETE, "trash", 0, 0, "Delete", NULL },
{ TOOLBAR_ITEM_BUTTON, ID_UNDO, "undo", 0, 0, "Undo", NULL },
};
The icon field is a const char* SVG base name. NULL = no icon. The framework calls sysicon_resolve() which checks the preloaded strip first, then loads the SVG from disk on first use.
Owner-drawn code (integer sysicon IDs)
// draw_icon16 still uses sysicon_* integer IDs from the preloaded strip
draw_icon16(sysicon_eye_show, x, y, 0xFFFFFFFF); // white
draw_icon16(sysicon_warning, x, y, 0xFF2244CC); // tinted
draw_sysicon(sysicon_eye_show, x, y, 12, 0xFFFFFFFF); // explicitly sized
draw_icon16 scales the source tile to 16×16 pixels. This keeps compact owner-drawn controls independent of the canonical 24×24 toolbar tile size. The tint color multiplies against the white-on-alpha icon (0xFFFFFFFF = white, 0xFF0000FF = red).
Picking and adding icons
1. Find the iconoir name
Browse iconoir.com or search the GitHub tree:
curl -s "https://api.github.com/repos/iconoir-icons/iconoir/git/trees/<SHA>" \
| python3 -c "import sys,json; [print(i['path'].replace('.svg',''))
for i in json.load(sys.stdin)['tree']
if 'keyword' in i['path']]"
Iconoir uses lowercase kebab-case (arrow-up, folder-open, fill-color). Some names you might guess wrong:
| What you’d guess | Real iconoir name |
|---|---|
brush | design-nib |
eraser | erase |
scissors | scissor |
paint-bucket | fill-color |
cursor | cursor-pointer |
eye-off | eye-closed |
pencil | edit-pencil or design-pencil |
clipboard | paste-clipboard |
fast-forward | forward |
file-plus | page-plus |
hand-gesture | drag-hand-gesture |
music-note-beamed | music-double-note |
2. Add the SVG file
# Download directly
curl -fsSo share/icons/my-icon.svg \
https://raw.githubusercontent.com/iconoir-icons/iconoir/main/icons/regular/my-icon.svg
Or drop a custom 24 × 24 SVG (stroke-based, currentColor for the stroke) in share/icons/ and the system picks it up on next build.
3. Map it
For a new sysicon — add an entry to k_sysicon_names[] in orion/user/svg_icon_loader.c:
[sysicon_my_new_icon - SYSICON_BASE] = "my-icon",
Then add the enum value to orion/user/icons.h:
sysicon_my_new_icon, // inside the anonymous enum
For an imageeditor tool — add to k_tool_svg_names[] in apps/imageeditor/windows/win_toolpalette.c and add to IE_ICONS in apps/imageeditor/image-editor.h.
4. Copy to build
make share # copies share/ → build/share/orion/
Icons are loaded at next run; no recompile needed unless you added an enum value.
Download script
tools/download_iconoir.sh fetches all icons referenced by the current mappings in one shot:
./tools/download_iconoir.sh # → share/icons/
./tools/download_iconoir.sh /other/path # → custom directory
After downloading, run make share to copy into the build tree.
Any iconoir name that returns HTTP 404 is printed as MISSING: <name> — drop a hand-crafted SVG with that filename to fill the gap.
NULL mappings and startup diagnostics
Icons with no iconoir equivalent are mapped to NULL in the C arrays. At startup you will see:
UNMAPPED icon[N] ← enum entry is NULL in the mapping array
MISSING icon[N] "x" ← mapped to "x" but share/icons/x.svg not found
UNMAPPED lines for game-engine-specific sysicons (sysicon_voxel, sysicon_sword, etc.) are expected and harmless. MISSING lines mean a named SVG isn’t on disk — run the download script or add a custom file.
If every icon in a strip is missing or unmapped, svg_build_strip returns false and the strip stays empty (icons render as blank tiles — no crash).
Toolbar icons — string-based, no enum needed
Toolbar buttons reference icons by SVG base name (a const char*), not a sysicon integer. The icon is resolved on demand the first time a button is drawn:
- The preloaded sysicon strip is scanned for the name.
- If not found, each registered icons directory is tried in order.
- The first matching
<name>.svgis rasterized and cached as a GPU texture.
Icon directory pools (searched in registration order):
| Directory (source) | Runtime path | Registered by |
|---|---|---|
share/icons/ | share/orion/icons/ | init_sysicon_strip (automatic) |
apps/<app>/share/icons/ | share/<app>/icons/ | app gem_init via svg_add_icons_dir() |
Icons in an app’s own pool are found without polluting the global namespace: "git-commit" resolves from share/gitclient/icons/ while "floppy-disk" comes from share/orion/icons/.
Adding a new icon to gitclient (or any app):
<!-- gitclient.orion — just use the SVG base name -->
<Button name="commit" command="commit.commit" icon="git-commit" text="Commit" />
- Drop
git-commit.svginapps/gitclient/share/icons/. - Run
make share(copies it tobuild/share/gitclient/icons/). - Launch — no recompile needed.
No C enum entry is needed.
For owner-drawn code that uses draw_icon16() you still need a sysicon_* enum entry and a k_sysicon_names[] mapping — that path uses integer strip indices.
Adding custom icons not in iconoir
Drop any hand-crafted SVG in share/icons/<name>.svg. Strict requirements to ensure the icon looks correct at every size:
| Property | Value |
|---|---|
viewBox | 0 0 24 24 (square, 24 × 24 units) |
width / height | 24 (matches viewBox) |
fill | none on root element |
stroke | currentColor (loader replaces with white at rasterise time) |
stroke-width | 1.5 (iconoir standard) |
stroke-linecap | round |
stroke-linejoin | round |
| Allowed elements | <path>, <line>, <rect>, <circle>, <polyline> only |
| Forbidden | fills, gradients, masks, <text>, raster images, editor metadata |
| Legibility | must read clearly at 16 × 16 actual display size |
The loader centers and scales the SVG to the target tile size automatically.
Stroke-width note
Iconoir uses exactly stroke-width="1.5". Do not use 2 — it produces heavier marks than the surrounding iconoir icons and breaks visual consistency.
Drawing new icons with an AI model
When an icon does not exist in iconoir and must be custom-drawn, use this prompt template to generate a compliant SVG:
Create a 24 × 24 monochrome SVG icon for “[describe the concept]”. Match iconoir’s outline style:
stroke-width="1.5",stroke="currentColor",stroke-linecap="round",stroke-linejoin="round",fill="none". Use only<path>,<circle>,<rect>,<line>, or<polyline>. The icon must remain legible at 16 × 16. No fills, gradients, masks, text nodes, or raster images. Return only the valid SVG — no explanation, no wrapper HTML.
Example (git commit node — circle on a horizontal line):
<svg width="24" height="24" viewBox="0 0 24 24"
stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="3.5" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 12H8.5" stroke="currentColor" stroke-linecap="round"/>
<path d="M15.5 12H22" stroke="currentColor" stroke-linecap="round"/>
</svg>
After generating:
- Verify
stroke-width="1.5"is on the root<svg>(not per-path). - Confirm no
fillvalues other thannone, nostyle=attributes. - Open in a browser and check readability at 16 × 16 by scaling the viewport.
- Save to
share/icons/<name>.svg, runmake share, launch the app.
File locations
| Path | Purpose |
|---|---|
share/icons/*.svg | Global icon pool (orion system icons) |
apps/<name>/share/icons/*.svg | App-specific icon pool (loaded via svg_add_icons_dir) |
orion/user/svg_icon_loader.h/.c | Strip builder, sysicon/picker mappings, sysicon_resolve(), svg_add_icons_dir() |
orion/user/icons.h | sysicon_* enum and SYSICON_BASE (owner-drawn code only) |
orion/user/sysicons.h | icon_id_t enum for file-picker icons |
orion/user/messages.h | SYSICON_SIZE, TOOLBAR_HEIGHT, TB_SPACING; toolbar_item_t |
orion/user/draw_impl.c | draw_icon16, draw_icon, draw_theme_icon |
orion/user/init.c | init_sysicon_strip, init_icons_strip, svg_set_icons_dir() |
apps/imageeditor/image-editor.h | IE_ICONS enum |
apps/imageeditor/windows/win_toolpalette.c | Imageeditor tool mapping + strip load |
tools/download_iconoir.sh | Bulk SVG download script |
tools/nanosvg.h / tools/nanosvgrast.h | Bundled nanosvg rasterizer |