ui/icons.rs
1//! Embedded icon assets + the gpui [`AssetSource`] that serves them.
2//!
3//! Most glyphs come from the **Solar Icons** set (Linear weight) by 480 Design,
4//! licensed under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/);
5//! attribution: "Solar Icons by 480 Design". A few (`terminal`, `plus`,
6//! `close`, `stop`, `git-branch`, `star`) are hand-drawn in the same style.
7//!
8//! Icons render via [`icon`]: `icon(icons::PAPERCLIP).size(px(16.)).text_color(…)`.
9
10use std::borrow::Cow;
11
12use gpui::{AssetSource, Result, SharedString, Styled as _, Svg, svg};
13
14macro_rules! icon_assets {
15 ($(($const_name:ident, $path:literal)),+ $(,)?) => {
16 $(pub const $const_name: &str = concat!("icons/", $path, ".svg");)+
17
18 /// Every icon in the set: `(constant name, asset path)`. The paths are
19 /// `&'static str` so they can be handed straight to [`icon`] — which is
20 /// what makes the set browsable at all.
21 pub const ALL: &[(&str, &str)] = &[
22 $((stringify!($const_name), concat!("icons/", $path, ".svg"))),+
23 ];
24
25 /// Serves the embedded icons to gpui's SVG renderer.
26 pub struct Assets;
27
28 impl AssetSource for Assets {
29 fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
30 Ok(match path {
31 $(concat!("icons/", $path, ".svg") => Some(Cow::Borrowed(
32 include_bytes!(concat!("../assets/icons/", $path, ".svg")).as_slice(),
33 )),)+
34 _ => None,
35 })
36 }
37
38 fn list(&self, path: &str) -> Result<Vec<SharedString>> {
39 let all = [$(concat!("icons/", $path, ".svg")),+];
40 Ok(all
41 .iter()
42 .filter(|p| p.starts_with(path))
43 .map(|p| SharedString::from(*p))
44 .collect())
45 }
46 }
47 };
48}
49
50icon_assets![
51 // Solar Icons (Linear), CC BY 4.0 — 480 Design.
52 (MONITOR, "monitor"),
53 (LAPTOP, "laptop"),
54 (PEN_NEW_SQUARE, "pen-new-square"),
55 (SORT_VERTICAL, "sort-vertical"),
56 (LIST, "list"),
57 (FOLDER_WITH_FILES, "folder-with-files"),
58 (FOLDER, "folder"),
59 // Hand-drawn git-branch glyph in the Solar Linear style (like the
60 // terminal/plus/return ports) — the set has no branch icon.
61 (GIT_BRANCH, "git-branch"),
62 // Compact history-ref glyphs, drawn in the same linear style.
63 (CLOUD, "cloud"),
64 (TAG, "tag"),
65 (SIDEBAR_MINIMALISTIC, "sidebar-minimalistic"),
66 // Mirrored variant (the reference window-controls.tsx `-scale-x-100`): the LEFT
67 // sidebar toggle shows the panel line on the left; gpui divs have no
68 // scale transform at the pinned rev, so the flip is baked into the asset.
69 (SIDEBAR_MINIMALISTIC_LEFT, "sidebar-minimalistic-left"),
70 (KEY_MINIMALISTIC, "key-minimalistic"),
71 (KEYBOARD, "keyboard"),
72 (ARROW_LEFT, "arrow-left"),
73 (ARROW_RIGHT, "arrow-right"),
74 (ARROW_UP, "arrow-up"),
75 // arrow-up mirrored (like the sidebar flip) — the Solar Linear set here
76 // has no plain arrow-down.
77 (ARROW_DOWN, "arrow-down"),
78 // Hand-drawn return/enter arrow in the Solar Linear style (like the
79 // terminal/plus/close ports) — the set has no return glyph.
80 (RETURN, "return"),
81 (ALT_ARROW_DOWN, "alt-arrow-down"),
82 // Hand-drawn expand/maximize arrows in the Solar Linear style (like the
83 // terminal/plus/return ports) — the set has no expand glyph.
84 (EXPAND_ARROWS, "expand-arrows"),
85 // Hand-drawn fold-all chevrons, drawn as a family with EXPAND_ARROWS
86 // (same stroke, caps, 90° joints) — Solar has no unfold-less either.
87 (FOLD_VERTICAL, "fold-vertical"),
88 (ALT_ARROW_LEFT, "alt-arrow-left"),
89 (ALT_ARROW_RIGHT, "alt-arrow-right"),
90 (SMARTPHONE, "smartphone"),
91 (ARCHIVE_UP_MINIMALISTIC, "archive-up-minimalistic"),
92 (REFRESH, "refresh"),
93 (RESTART, "restart"),
94 (ADD_CIRCLE, "add-circle"),
95 (TUNING, "tuning"),
96 (PAPERCLIP, "paperclip"),
97 (PEN, "pen"),
98 (ARCHIVE_MINIMALISTIC, "archive-minimalistic"),
99 (TRASH_BIN_MINIMALISTIC, "trash-bin-minimalistic"),
100 (SETTINGS_MINIMALISTIC, "settings-minimalistic"),
101 (LOGOUT_2, "logout-2"),
102 (MAGNIFER, "magnifer"),
103 (COMMAND, "command"),
104 (DOCUMENT, "document"),
105 (DOCUMENT_ADD, "document-add"),
106 // The verbs an agent's tool calls come in — fetch, link, read, discover.
107 // Ported from `../desktop`'s tool-name→icon map, which stays over there:
108 // the map is that app's data model, and these are just glyphs.
109 (DOWNLOAD, "download-minimalistic"),
110 (LINK, "link-minimalistic"),
111 (BOOK, "book"),
112 (COMPASS, "compass"),
113 // What an agent recalls from. Named for the shape, like every icon here —
114 // `magnifer` rather than `search` — so the app's word for the store, brain
115 // or memory or notes, stays in the app.
116 (CPU, "cpu"),
117 (GLOBAL, "global"),
118 (CHECKLIST, "checklist"),
119 (TEXT, "text"),
120 (HASHTAG, "hashtag"),
121 (CALENDAR, "calendar"),
122 (WIDGET, "widget"),
123 (WIFI_OFF, "wifi-off"),
124 (CLOSE_CIRCLE, "close-circle"),
125 // Hand-drawn info glyph in the Solar Linear style (like the terminal/
126 // plus/return ports) — the embedded set has no info-circle.
127 (INFO_CIRCLE, "info-circle"),
128 (DANGER_TRIANGLE, "danger-triangle"),
129 (CHAT_ROUND_LINE, "chat-round-line"),
130 // Hand-drawn bell in the Solar Linear style (like the terminal/plus/return
131 // ports) — the embedded set has none.
132 (BELL, "bell"),
133 // The two ends of an appearance switch. A pair, so a switch between them
134 // needs no label to say which way is which.
135 (SUN, "sun"),
136 (MOON, "moon"),
137 // Media transport. The three volume glyphs are one Solar family on purpose:
138 // a level control swaps between them as you slide, and a speaker cone that
139 // changed shape mid-slide would read as a bug. `volume-loud` used to be
140 // hand-drawn here and no longer is, for exactly that reason.
141 (VOLUME_MUTE, "volume-mute"),
142 (VOLUME_LOW, "volume-low"),
143 (VOLUME_LOUD, "volume-loud"),
144 // Linear like their neighbours; the bold pairs are the two states a
145 // transport paints solid, the same split STAR/STAR_BOLD already makes.
146 (PLAY, "play"),
147 (PLAY_BOLD, "play-bold"),
148 (PAUSE, "pause"),
149 (PAUSE_BOLD, "pause-bold"),
150 (SKIP_PREVIOUS, "skip-previous"),
151 (SKIP_NEXT, "skip-next"),
152 (SHUFFLE, "shuffle"),
153 (REPEAT, "repeat"),
154 (REPEAT_ONE, "repeat-one"),
155 (HEART, "heart"),
156 (HEART_BOLD, "heart-bold"),
157 (PLAYLIST, "playlist"),
158 (MICROPHONE, "microphone"),
159 // Hand-drawn reference glyphs (terminal-panel.tsx / composer-actions.tsx /
160 // menu-check.tsx / logo.tsx).
161 (TERMINAL, "terminal"),
162 (PLUS, "plus"),
163 // Hand-drawn dots in the Solar Linear style — the embedded set has no
164 // menu-dots, and the `···` is what opens a row's menu.
165 (MENU_DOTS, "menu-dots"),
166 (CLOSE, "close"),
167 (STOP, "stop"),
168 (CHECK, "check"),
169 (COPY, "copy"),
170 // Hand-drawn star pair in the Solar Linear style (like the terminal/
171 // plus/return ports) — outline for the favorite affordance, bold for the
172 // favorited state and the picker's favorites rail tab.
173 (STAR, "star"),
174 (STAR_BOLD, "star-bold"),
175];
176
177/// An icon element for an embedded asset path. Size and colour are set by the
178/// caller (`.size(..)`, `.text_color(..)`), matching the web app's
179/// `[&_svg]:size-4` idiom.
180pub fn icon(path: &'static str) -> Svg {
181 svg().path(path).flex_none()
182}