Icons by name
Cosmos ships an icon sheet, and until now the only way to reach it was the cell number:
gui_icon("icon_index:111;color:#cc0;") # ...which one is 111?
Nobody remembers 111, nothing can check it, and a mission that wants its own art has to find and edit every screen that draws one. So every glyph now has a name:
gui_icon_name("wanted", "#cc0") # the same icon
gui_icon_name("quest.job", "#cc0") # better: say what it MEANS
A look, or a meaning
There are two kinds of name, and the difference is the whole point.
| example | what it is | |
|---|---|---|
| A look | square, wanted, bell, sitemap |
one per drawn cell — what the glyph is |
| A meaning | quest.job, quest.state, list.expand |
an alias onto a look — what it's for |
Ask for the meaning wherever you can. quest.job says why the icon is there;
wanted only says what it looks like today. Re-point the meaning once and every screen
drawing it changes together.
from sbs_utils.procedural.gui.icon_sheet import ICON_ALIAS
ICON_ALIAS["quest.job"] = "flag" # every quest log now flags its jobs
The meanings that ship:
| meaning | look | used for |
|---|---|---|
quest.arc |
sitemap |
the heading over a run of beats |
quest.job |
wanted |
work posted for someone to take |
quest.objective |
flag |
something the crew is to do |
quest.beat |
talks |
a moment they live through |
quest.cue |
bell |
a stage direction; fires unseen |
quest.state |
square |
the state pip, recolored per state |
check.on / check.off |
square / square-outline |
a checked and an unchecked row |
list.expand / list.collapse |
expand / collapse |
a fold that opens or closes |
list.prev / list.next |
rewind / forward |
paging through a list |
Color is per use, not per icon
Every built-in glyph is white on transparent, so one glyph serves every state — pass the color at the point of drawing:
for quest in quests:
gui_icon_name("quest.state", "#6d6" if quest.done else "#888")
Bring your own sheet
A name is not tied to the built-in sheet. Claim the look for a cell of your own and it wins over the built-in index:
from sbs_utils.procedural.gui import gui_icon_add_atlas
from sbs_utils.procedural.media_paths import media_shared
# One 64px cell out of your own sheet, claiming the name "wanted".
gui_icon_add_atlas("wanted", media_shared("icons/quest-sheet"), 0, 0, 64, 64)
From then on every gui_icon_name("quest.job") in the game draws your art — with no
edit to the code that draws it. That is what lets a screen be written before its art
exists, and lets an add-on re-skin screens it doesn't own.
Or claim a whole sheet at once, laid out row-major:
gui_icon_add_atlas_grid(media_shared("icons/quest-sheet"), 8, 8,
["wanted", "flag", "talks", None, "bell"], cell=64)
Claiming a look has to be deliberate
gui_icon_add_atlas is gui_image_add_atlas(..., domain="icon"), and only the icon
domain re-skins. A plain gui_image_add_atlas("square", ...) — a perfectly ordinary
thing to call an image — does not become the icon square. Without that scope
one image registration could silently re-skin every state pip in the game, and the
author would have no way to know why.
Icons written as a fact sheet
A sheet is a catalog, and a catalog is what AMD is for. An
image section registers the same keys with no Python at all —
Sheet, Cell and the domain are written once on the section, so an entry is one line:
## [Icons](icons)
---
icons
Sheet: icons/quest-sheet
Cell: 64
---
The quest log's glyphs. White silhouettes - color is applied per use.
### [Job](wanted)
---
At: 0, 0
---
### [Beat](talks)
---
At: 1, 0
Color: #888
---
images_load_amd("icons.amd") # or images_declare_document(doc) for a section of a bigger file
A section whose kind noun is icons registers in the icon domain — so those keys are
looks. Any other word (images, art, atlas) registers ordinary atlas keys, which is
what a card deck or a set of console backdrops wants:
## [Cards](cards)
---
images
Sheet: casino/terran_deck
Cell: 190, 280
Domain: casino
---
### [Back](card_back)
---
At: 0, 0
---
sbs lint checks these: a sheet that is not on disk, an At: with no Cell: to measure
against, and a cell that falls off the edge of the sheet. All three draw a blank widget
today with no error anywhere.
Where a custom sheet should live
Put it in a shared media pack rather than in each
mission that draws it. media_shared() finds it wherever it was unpacked, so the
same call works in a clone and in a fetched copy.
Sub-rects are pixels
When you cut cells out of your own sheet, gui_image_add_atlas(key, file, l, t, r, b)
takes pixel coordinates, not 0–1 texture coordinates. A 64px grid is
(col*64, row*64, (col+1)*64, (row+1)*64).
Things worth knowing
- An unknown name draws nothing and logs a warning once, rather than falling back to some arbitrary glyph. A wrong icon is worse than a missing one, because it looks deliberate.
- Both backings lay out the same. A built-in name goes out as an engine icon; a custom one goes out as an image (the engine has no icon concept for art it didn't ship). Both are square columns sized off the row height, so re-skinning a name cannot shift a layout.
click_tagworks on the built-in path only today. A clickable icon whose name may be re-skinned isn't supported yet — usegui_icondirectly if you need the click.icon_names()lists everything that resolves;icon_resolve(name)returns(icon_index, atlas_key)with exactly one of the two set.
The whole sheet
176 named glyphs. The number after each name is the raw icon_index, in case you're
reading older code that used it. Names in italics are meanings that point at that look.
Science & space
Ship systems & engineering
Combat
People
Medical
Places & cargo
Signals, orders & the map
Rank pips
Emblems
Shapes & widget furniture
Icons from game-icons.net. The gallery is generated from
icon_sheet.py by mkdocs/gen_icon_gallery.py — re-run it after adding a name.