Repository navigation
iconbrowser
Browsable, searchable icon picker for CET mods. Renders a filterable grid of all IconGlyphs with category filtering, search, and an optional preview panel.
The module is exposed as wu.IconBrowser.
local wu = GetMod("WindowUtils")
local iconbrowser = wu.IconBrowser
-- Inline icon picker (in onDraw)
selected = iconbrowser.draw("my_picker", selected, function(name, glyph)
print("Selected:", name, glyph)
end)The browser indexes all IconGlyphs keys at load time, assigns categories via prefix matching, and renders a virtualized grid that handles 7000+ icons at 60fps.
- On first use, all
IconGlyphskeys are indexed and categorized by prefix matching against a built-in prefix-to-category map - The prefix map is sorted longest-first so the most specific match wins (e.g. "AccountMultiple" before "Account")
- Icons that don't match any prefix are assigned to "Other"
- The grid uses manual row-skipping (only visible rows are rendered) for performance with large icon sets
- Per-instance state is tracked by ID, so multiple browsers can coexist independently
Render an inline icon browser. Creates/retrieves per-instance state keyed by id.
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | string | - | Unique instance ID |
| selected | string|nil | nil | Currently selected icon name |
| onSelect | function|nil | nil | Callback: onSelect(name, glyph)
|
| opts | table|nil | nil | Configuration overrides |
Returns: string|nil, string|nil, boolean - selected icon name, resolved glyph string, whether selection changed this frame
If the icon index is not built yet (IconGlyphs empty at that point), draw renders nothing and returns selected, nil, false.
Passing a non-nil selected overwrites the instance's stored selection every frame and re-resolves the glyph from IconGlyphs, so pass nil if you want the browser to own the selection.
The three return values let you track selection changes and use the glyph directly:
local selected, glyph, changed = iconbrowser.draw("my_picker", selected)
if changed then
myEntry.icon = selected
myEntry.glyph = glyph
save()
endThe onSelect callback is still available for event-driven usage:
iconbrowser.draw("my_picker", selected, function(name, glyph)
myEntry.icon = name
myEntry.glyph = glyph
save()
end)| Option | Type | Default | Description |
|---|---|---|---|
| cellSize | number | 28 | Icon cell size (1080p baseline pixels, clamped to at least 12) |
| showSearch | boolean | true | Show the search bar |
| showCategory | boolean | true | Show the category dropdown |
| showPreview | boolean | false | Show the preview panel below the grid |
| layout | string | "fill" |
"fixed" for a resizable panel, anything else fills available space |
| gridHeight | number | 300 | Grid height when layout is "fixed" |
| comboHeight | number | 10 | Category dropdown height in items |
| defaultCategory | string|nil | nil | Pre-select a category on first creation |
| elementIds | table|nil | nil | Bounds reporting ids for tutorials and hints (see below) |
That is the full set. There is no icon count option: the grid shows "No icons match" when the filter is empty and nothing else.
Each sub-key registers bounds for one part of the browser so tutorials and hints can target it. All are optional.
| Sub-key | Targets |
|---|---|
| search | The search bar (reported as a group) |
| combo | The category dropdown |
| grid | The icon grid panel or fill child |
| preview | The preview panel (only when showPreview is true) |
| previewButton | The large icon button inside the preview panel |
iconbrowser.draw("picker", selected, onSelect, {
showPreview = true,
elementIds = {
search = "icon_search",
combo = "icon_category",
grid = "icon_grid",
preview = "icon_preview",
previewButton = "icon_preview_btn",
},
})fill (default): The grid expands to fill all remaining vertical space, reserving room for the preview panel if enabled.
fixed: The grid renders inside a resizable panel with a configurable height. Useful for embedding in a larger layout.
-- Fixed-height embedded browser
iconbrowser.draw("embedded", selected, onSelect, {
layout = "fixed",
gridHeight = 200,
showPreview = false,
})When showPreview = true, a panel below the grid shows the selected icon's glyph, name, code reference, and category. Left-click or middle-click the preview icon or the code row to copy IconGlyphs.<Name> to the clipboard; a toast confirms the copy. Right-click does nothing. With no selection the panel shows "No icon selected".
iconbrowser.draw("picker", selected, onSelect, {
showPreview = true,
})The search bar filters icons by name and category (case-insensitive substring match). The whole query is treated as one literal substring, spaces included, so multi-word queries only match if the icon name or category contains that exact run of characters. The category dropdown filters to a single category, with "All" as the first entry. Both can be used together.
-- Browser with search but no category dropdown
iconbrowser.draw("simple", selected, onSelect, {
showCategory = false,
})Get sorted list of all category names.
Returns: table - sorted array of category name strings
This is the module's live table, not a copy. Every caller gets the same array the browser itself uses for the category dropdown and index lookups. Treat it as read-only. Sorting, inserting, or clearing it corrupts the category filter for every icon browser instance. Copy it first if you need to modify anything.
The list is empty until the index is built. The index is built at require time and retried on each call and each draw(), so an early call before IconGlyphs is populated can return an empty array.
Get the category for a given icon name.
| Parameter | Type | Description |
|---|---|---|
| name | string | Icon name (PascalCase key from IconGlyphs) |
Returns: string - category name, or "Other" if unknown
local cat = iconbrowser.getCategory("AccountCircle") -- "Account / User"Reset an instance's search query, category filter, and selection. Call with no argument to reset all instances. Useful when the picker lives inside a modal that opens and closes. Unknown ids are ignored.
Reset also clears the "default category already applied" flag, so a defaultCategory in opts is applied again on the next draw().
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | string|nil | nil | Instance ID, or nil to reset all |
-- Reset a specific instance
iconbrowser.reset("cat_picker")
-- Reset all instances
iconbrowser.reset()Remove all internal state for an icon browser instance, including its nested search state. Call when dynamically created browsers are no longer needed.
| Parameter | Type | Description |
|---|---|---|
| id | string | Instance ID to clean up |
Icons are categorized by prefix matching against a built-in map of ~60 categories. Categories include:
Account / User, Agriculture, Alert / Error, Alpha / Numeric, Animal, Arrange, Arrow, Audio, Automotive, Banking, Battery, Brand / Logo, Cellphone / Phone, Cloud, Clothing, Color, Currency, Database, Date / Time, Developer / Languages, Device / Tech, Drawing / Art, Edit / Modify, Emoji, Files / Folders, Food / Drink, Form, Gaming / RPG, Geographic Information System, Hardware / Tools, Health / Beauty, Holiday, Home Automation, Lock, Math, Medical / Hospital, Music, Nature, Navigation, Notification, People / Family, Photography, Places, Printer, Religion, Science, Settings, Shape, Shopping, Social Media, Sport, Text / Content / Format, Tooltip, Transportation, Vector, Video / Movie, View, Weather, and Other (fallback).