Skip to main content

fluent_app/
assets.rs

1use std::{borrow::Cow, sync::Arc};
2
3use gpui::{AssetSource, Result, SharedString};
4
5const ADD: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7 2h2v5h5v2H9v5H7V9H2V7h5z"/></svg>"#;
6const ARROW_FORWARD: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M9.3 3.3 14 8l-4.7 4.7-1.4-1.4L10.2 9H2V7h8.2L7.9 4.7z"/></svg>"#;
7const CHECKMARK: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m6.2 11.4-3.3-3.3 1.4-1.4 1.9 1.9 5.5-5.5 1.4 1.4z"/></svg>"#;
8const CHEVRON_DOWN: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4.2 6 8 9.8 11.8 6l1.2 1.2-5 5-5-5z"/></svg>"#;
9const CHEVRON_RIGHT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m6 3 5 5-5 5-1.2-1.2L8.6 8 4.8 4.2z"/></svg>"#;
10const CHEVRON_UP: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4.2 10 8 6.2l3.8 3.8 1.2-1.2-5-5-5 5z"/></svg>"#;
11const CLEAR: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4.3 3 8 6.7 11.7 3 13 4.3 9.3 8l3.7 3.7-1.3 1.3L8 9.3 4.3 13 3 11.7 6.7 8 3 4.3z"/></svg>"#;
12const COPY: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M5 2h7v9H5zm-2 3h1v7h6v1H3z"/></svg>"#;
13const CUT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4 5a2 2 0 1 1 1.4-.6L8 7l2.6-2.6A2 2 0 1 1 12 5a2 2 0 0 1-.7-.1L9.4 8l1.9 3.1A2 2 0 1 1 10 12l-2-2-2 2a2 2 0 1 1-1.3-.9L6.6 8 4.7 4.9A2 2 0 0 1 4 5z"/></svg>"#;
14const DARK: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M9.6 1.2a6.8 6.8 0 1 0 5.2 5.2A5.2 5.2 0 1 1 9.6 1.2z"/></svg>"#;
15const DISMISS: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m4.3 3 3.7 3.7L11.7 3 13 4.3 9.3 8l3.7 3.7-1.3 1.3L8 9.3 4.3 13 3 11.7 6.7 8 3 4.3z"/></svg>"#;
16const FOLDER: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 4h5l1 1h6v7.5A1.5 1.5 0 0 1 12.5 14h-9A1.5 1.5 0 0 1 2 12.5z"/></svg>"#;
17const GRID: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 2h5v5H2zm7 0h5v5H9zM2 9h5v5H2zm7 0h5v5H9z"/></svg>"#;
18const LIGHT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 4a4 4 0 1 1 0 8A4 4 0 0 1 8 4zm0-3h1v2H7V1zm0 12h1v2H7v-2zM1 7h2v2H1zm12 0h2v2h-2zM3 2.3 4.4 3.7 3.7 4.4 2.3 3zm9.3 9.3 1.4 1.4-.7.7-1.4-1.4zM13.7 3l-1.4 1.4-.7-.7L13 2.3zM4.4 12.3 3 13.7l-.7-.7 1.4-1.4z"/></svg>"#;
19const LIST_TREE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 2h2v2H3zm4 .5h7v1H7zM3 7h2v2H3zm4 .5h7v1H7zM3 12h2v2H3zm4 .5h7v1H7z"/></svg>"#;
20const MINIMIZE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 8h10v1.5H3z"/></svg>"#;
21const MAXIMIZE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 3h10v10H3zm1.5 1.5v7h7v-7z"/></svg>"#;
22const PANEL_OPEN: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 3h12v10H2zm2 1.5v7h2.5v-7zm4 0v7h4.5v-7z"/></svg>"#;
23const PASTE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M6 1h4l.5 1H13v12H3V2h2.5zm0 2v1h4V3zM5 6v6h6V6z"/></svg>"#;
24const PLUG: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M6 1h1v4h2V1h1v4h1v3a4 4 0 0 1-3 3.9V15H7v-3.1A4 4 0 0 1 4 8V5h2z"/></svg>"#;
25const SEARCH: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7 2a5 5 0 0 1 3.9 8.1l3 3-1.4 1.4-3-3A5 5 0 1 1 7 2zm0 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"/></svg>"#;
26const SETTINGS: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7 1h2l.4 2 1.7.7 1.7-1.1 1.4 1.4-1.1 1.7.7 1.7 2 .4v2l-2 .4-.7 1.7 1.1 1.7-1.4 1.4-1.7-1.1-1.7.7-.4 2H7l-.4-2-1.7-.7-1.7 1.1-1.4-1.4 1.1-1.7-.7-1.7-2-.4V8l2-.4.7-1.7-1.1-1.7 1.4-1.4 1.7 1.1 1.7-.7zM8 6a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/></svg>"#;
27const SPINNER: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 1a7 7 0 0 1 7 7h-2a5 5 0 0 0-5-5z"/></svg>"#;
28const DOCUMENT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 2h7l3 3v9H3V2zm7 0v3h3"/></svg>"#;
29const DOWNLOAD: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 11 4 7h2.5V2h3v5H12zm-6 2h12v1.5H2z"/></svg>"#;
30const UPLOAD: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 2l4 4H9.5v5h-3V6H4zm-6 11h12v1.5H2z"/></svg>"#;
31const GENERIC: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 3h10v10H3z"/></svg>"#;
32
33// --- AndroidConnect design-system icons (Fluent System Icons-style, 16×16) ---
34const HOME: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 1.5 14.5 7H13v6.5a.5.5 0 0 1-.5.5H10V10H6v4H3.5a.5.5 0 0 1-.5-.5V7H1.5z"/></svg>"#;
35const MIRROR: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4 2h8a1.5 1.5 0 0 1 1.5 1.5v9A1.5 1.5 0 0 1 12 14H4a1.5 1.5 0 0 1-1.5-1.5v-9A1.5 1.5 0 0 1 4 2zm0 1.5v9h8v-9zM6.5 12h3v.5h-3z"/></svg>"#;
36const BELL: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 1.5a.75.75 0 0 1 .75.75v.55A4.5 4.5 0 0 1 12.5 7v2.5l1 1.5v.5h-11v-.5l1-1.5V7a4.5 4.5 0 0 1 3.75-4.2v-.55A.75.75 0 0 1 8 1.5zM6.5 13h3a1.5 1.5 0 0 1-3 0z"/></svg>"#;
37const BELL_OFF: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m2 2 12 12-1 1L1.5 3zm6 0a.75.75 0 0 1 .75.75v.55A4.5 4.5 0 0 1 12.5 7v2.5l.62.93L4.4 1.81A4.5 4.5 0 0 1 7.25 3.3v-.55A.75.75 0 0 1 8 2zM3.5 7c0-.4.05-.78.14-1.14l9 9H2.5v-.5l1-1.5V7zm3 6h3a1.5 1.5 0 0 1-3 0z"/></svg>"#;
38const CHAT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 2h10a1.5 1.5 0 0 1 1.5 1.5v7A1.5 1.5 0 0 1 13 12H7l-3.5 2.5V12H3A1.5 1.5 0 0 1 1.5 10.5v-7A1.5 1.5 0 0 1 3 2z"/></svg>"#;
39const PHONE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M5 1.5h6A1.5 1.5 0 0 1 12.5 3v10a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 13V3A1.5 1.5 0 0 1 5 1.5zm0 1.5v8h6V3zm2.25 9.25a.75.75 0 1 1 1.5 0 .75.75 0 0 1-1.5 0z"/></svg>"#;
40const QR: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 2h5v5H2zm1.5 1.5v2h2v-2zM9 2h5v5H9zm1.5 1.5v2h2v-2zM2 9h5v5H2zm1.5 1.5v2h2v-2zM9 9h2v2H9zm3 0h2v2h-2zm-3 3h2v2H9zm3 0h2v2h-2z"/></svg>"#;
41const BATTERY: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 5h10a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zm0 1v4h10V6zm12 1h1v2h-1zM3 7h6v2H3z"/></svg>"#;
42const WIFI: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 12.5a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM3.5 9.5a6 6 0 0 1 9 0l-1 1a4.5 4.5 0 0 0-7 0zM1.5 7a9 9 0 0 1 13 0l-1 1a7.5 7.5 0 0 0-11 0z"/></svg>"#;
43const SIGNAL: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 12h2v2H2zm3-2h2v4H5zm3-3h2v7H8zm3-4h2v11h-2z"/></svg>"#;
44const BLUETOOTH: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M7 1.5 11 5.5 8 8l3 2.5-4 4V9.5L4 12 3 11l4-3-4-3 1-1 3 2.5V1.5zm1.5 2.4v3.2L10 5.5zm0 5.7v3.2l1.5-1.6z"/></svg>"#;
45const SUN: &[u8] = LIGHT;
46const MOON: &[u8] = DARK;
47const VOL: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 2.5v11l-3.5-3H2v-5h2.5zm2.8 1.6c1.3 1 2.2 2.5 2.2 3.9s-.9 2.9-2.2 3.9l-.8-1.2c1-.7 1.5-1.7 1.5-2.7s-.5-2-1.5-2.7zm-1.4 2c.7.5 1.1 1.2 1.1 1.9 0 .7-.4 1.4-1.1 1.9l-.7-1.1c.4-.3.6-.6.6-.8 0-.2-.2-.5-.6-.8z"/></svg>"#;
48const VOL_MUTE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 2.5v11l-3.5-3H2v-5h2.5zm3 3 1.5 1.5L14 5.5 15 6.5 13.5 8 15 9.5 14 10.5l-1.5-1.5L11 10.5 10 9.5 11.5 8 10 6.5z"/></svg>"#;
49const PLAY: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4 2.5v11L13 8z"/></svg>"#;
50const PAUSE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M4 3h3v10H4zm5 0h3v10H9z"/></svg>"#;
51const PREV: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 3h2v10H3zm10 0v10L6 8z"/></svg>"#;
52const NEXT: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M11 3h2v10h-2zM3 3l7 5-7 5z"/></svg>"#;
53const SEND: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 2 14 8 2 14l2-6zm1.7 1.4L4.8 7H8l-3.5.8z"/></svg>"#;
54const BACK: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M10 3 5 8l5 5 1.2-1.2L7.4 8l3.8-3.8z"/></svg>"#;
55const MORE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 8a1.25 1.25 0 1 1 2.5 0A1.25 1.25 0 0 1 3 8zm3.75 0a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0zm3.75 0a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0z"/></svg>"#;
56const REFRESH: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M8 2a6 6 0 0 1 5.5 3.6L15 3.5V8h-4.5l1.6-1.5A4.5 4.5 0 0 0 3.5 8H2A6 6 0 0 1 8 2zm0 12a6 6 0 0 1-5.5-3.6L1 12.5V8h4.5L3.9 9.5A4.5 4.5 0 0 0 12.5 8H14A6 6 0 0 1 8 14z"/></svg>"#;
57const IMAGE: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 2.5h10A1.5 1.5 0 0 1 14.5 4v8a1.5 1.5 0 0 1-1.5 1.5H3A1.5 1.5 0 0 1 1.5 12V4A1.5 1.5 0 0 1 3 2.5zm0 1.5v6.5l3-3 2 2 3-3 2 2V4zM5 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>"#;
58const VIDEO: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M2 4h8a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1zm10 1.5 3-1.5v8l-3-1.5z"/></svg>"#;
59const ZIP: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M3 1.5h6L13 5v9.5H3zm6 0V5h4M6 3h1v1H6zm1 1h1v1H7zm-1 1h1v1H6zm1 1h1v1H7zm-1 1h1v1H6zm0 2h2v3H6z"/></svg>"#;
60const PIN: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M10.5 1.5 14.5 5.5 13 7l-1-1-3.5 3.5 1 4-1 1-3-3-2.5 2.5L2 13.5l2.5-2.5-3-3 1-1 4 1L10 4.5l-1-1z"/></svg>"#;
61const LOGO: &[u8] = br#"<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M5 1h6a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1zm1 2v9h4V3zM8 13a.75.75 0 1 1 0 1.5A.75.75 0 0 1 8 13z"/></svg>"#;
62
63/// Built-in FluentGUI assets plus an optional application asset fallback.
64#[derive(Default)]
65pub struct FluentAssets {
66    fallback: Option<Arc<dyn AssetSource>>,
67}
68
69impl FluentAssets {
70    pub fn new(fallback: Option<Arc<dyn AssetSource>>) -> Self {
71        Self { fallback }
72    }
73
74    fn builtin(path: &str) -> Option<&'static [u8]> {
75        Some(match path {
76            "icons/add.svg" | "icons/folder_add.svg" => ADD,
77            "icons/arrow_forward.svg" => ARROW_FORWARD,
78            "icons/checkmark.svg" | "icons/check.svg" => CHECKMARK,
79            "icons/chevron_down.svg" => CHEVRON_DOWN,
80            "icons/chevron_right.svg" | "icons/chevron.svg" => CHEVRON_RIGHT,
81            "icons/chevron_up.svg" => CHEVRON_UP,
82            "icons/clear.svg" | "icons/eraser.svg" => CLEAR,
83            "icons/copy.svg" => COPY,
84            "icons/cut.svg" => CUT,
85            "icons/dismiss.svg"
86            | "icons/delete.svg"
87            | "icons/plug_disconnected.svg"
88            | "icons/close.svg" => DISMISS,
89            "icons/document.svg" | "icons/file.svg" | "icons/doc.svg" => DOCUMENT,
90            "icons/download.svg" | "icons/download_arrow.svg" => DOWNLOAD,
91            "icons/upload.svg" | "icons/upload_arrow.svg" => UPLOAD,
92            "icons/folder.svg" | "icons/folder_open.svg" | "icons/folder_sync.svg" => FOLDER,
93            "icons/grid.svg" => GRID,
94            "icons/light_theme.svg" | "icons/sun.svg" => SUN,
95            "icons/dark_theme.svg" | "icons/moon.svg" => MOON,
96            "icons/list_tree.svg" => LIST_TREE,
97            "icons/minimize.svg" => MINIMIZE,
98            "icons/maximize.svg" | "icons/full_screen.svg" | "icons/fit_width.svg" => MAXIMIZE,
99            "icons/panel_open.svg" => PANEL_OPEN,
100            "icons/paste.svg" | "icons/clipboard_paste.svg" | "icons/clipboard_sync.svg" => PASTE,
101            "icons/plug.svg" | "icons/connect.svg" | "icons/network.svg" => PLUG,
102            "icons/search.svg" | "icons/find_replace.svg" => SEARCH,
103            "icons/settings.svg" => SETTINGS,
104            "icons/spinner.svg" => SPINNER,
105            // AndroidConnect design-system additions
106            "icons/home.svg" => HOME,
107            "icons/mirror.svg" => MIRROR,
108            "icons/bell.svg" => BELL,
109            "icons/bell_off.svg" => BELL_OFF,
110            "icons/chat.svg" => CHAT,
111            "icons/phone.svg" => PHONE,
112            "icons/qr.svg" => QR,
113            "icons/battery.svg" => BATTERY,
114            "icons/wifi.svg" => WIFI,
115            "icons/signal.svg" => SIGNAL,
116            "icons/bluetooth.svg" => BLUETOOTH,
117            "icons/vol.svg" | "icons/volume.svg" | "icons/speaker.svg" => VOL,
118            "icons/vol_mute.svg" | "icons/volume_mute.svg" | "icons/speaker_mute.svg" => VOL_MUTE,
119            "icons/play.svg" => PLAY,
120            "icons/pause.svg" => PAUSE,
121            "icons/prev.svg" | "icons/previous.svg" => PREV,
122            "icons/next.svg" => NEXT,
123            "icons/send.svg" => SEND,
124            "icons/back.svg" | "icons/chevron_left.svg" => BACK,
125            "icons/more.svg" => MORE,
126            "icons/refresh.svg" => REFRESH,
127            "icons/image.svg" => IMAGE,
128            "icons/video.svg" => VIDEO,
129            "icons/zip.svg" => ZIP,
130            "icons/pin.svg" => PIN,
131            "icons/logo.svg" => LOGO,
132            _ => return None,
133        })
134    }
135
136    fn generic_icon(path: &str) -> Option<&'static [u8]> {
137        (path.starts_with("icons/") && path.ends_with(".svg")).then_some(GENERIC)
138    }
139}
140
141impl AssetSource for FluentAssets {
142    fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
143        if let Some(bytes) = Self::builtin(path) {
144            return Ok(Some(Cow::Borrowed(bytes)));
145        }
146        if let Some(fallback) = &self.fallback {
147            if let Some(bytes) = fallback.load(path)? {
148                return Ok(Some(bytes));
149            }
150        }
151        if let Some(bytes) = Self::generic_icon(path) {
152            return Ok(Some(Cow::Borrowed(bytes)));
153        }
154        Ok(None)
155    }
156
157    fn list(&self, path: &str) -> Result<Vec<SharedString>> {
158        if let Some(fallback) = &self.fallback {
159            return fallback.list(path);
160        }
161        let names = if path == "icons" {
162            vec![
163                "add.svg",
164                "arrow_forward.svg",
165                "checkmark.svg",
166                "chevron_down.svg",
167                "chevron_right.svg",
168                "chevron_up.svg",
169                "clear.svg",
170                "copy.svg",
171                "cut.svg",
172                "dark_theme.svg",
173                "dismiss.svg",
174                "folder_open.svg",
175                "grid.svg",
176                "light_theme.svg",
177                "list_tree.svg",
178                "maximize.svg",
179                "minimize.svg",
180                "panel_open.svg",
181                "paste.svg",
182                "plug.svg",
183                "search.svg",
184                "settings.svg",
185                "spinner.svg",
186            ]
187        } else {
188            vec![]
189        };
190        Ok(names.into_iter().map(SharedString::from).collect())
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197
198    const CUSTOM_ICON: &[u8] = br#"<svg viewBox="0 0 1 1"><path d="M0 0h1v1H0z"/></svg>"#;
199
200    struct CustomAssets;
201
202    impl AssetSource for CustomAssets {
203        fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
204            Ok((path == "icons/custom.svg").then_some(Cow::Borrowed(CUSTOM_ICON)))
205        }
206
207        fn list(&self, _path: &str) -> Result<Vec<SharedString>> {
208            Ok(Vec::new())
209        }
210    }
211
212    #[test]
213    fn app_icon_assets_are_loaded_before_generic_icon_fallback() {
214        let assets = FluentAssets::new(Some(Arc::new(CustomAssets)));
215        let loaded = assets.load("icons/custom.svg").unwrap().unwrap();
216
217        assert_eq!(&*loaded, CUSTOM_ICON);
218    }
219
220    #[test]
221    fn unknown_icons_still_use_generic_fallback() {
222        let assets = FluentAssets::new(None);
223        let loaded = assets.load("icons/missing.svg").unwrap().unwrap();
224
225        assert_eq!(&*loaded, GENERIC);
226    }
227
228    #[test]
229    fn chevron_right_has_a_specific_builtin_icon() {
230        let assets = FluentAssets::new(None);
231        let loaded = assets.load("icons/chevron_right.svg").unwrap().unwrap();
232
233        assert_eq!(&*loaded, CHEVRON_RIGHT);
234        assert_ne!(&*loaded, GENERIC);
235    }
236}