# GPUI Asset Loading Guide
## Overview
This document explains how to correctly load SVG icons and other assets in GPUI applications.
## Core Concepts
### AssetSource
GPUI uses the `AssetSource` trait to load assets. `svg().path()` does not directly read the file system, but instead calls `AssetSource::load()` to get the asset content.
```rust
pub trait AssetSource {
fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>>;
fn list(&self, path: &str) -> Result<Vec<SharedString>>;
}
```
### Workflow
```
svg().path("icons/arrow.svg")
↓
cx.asset_source().load("icons/arrow.svg")
↓
Returns SVG byte data
↓
usvg/resvg renders to pixels
↓
Displayed on screen
```
## Two Implementation Approaches
### Approach A: Embedded Assets (Recommended, used by Zed)
#### Advantages
- ✅ Resources bundled into binary, no additional files needed
- ✅ Simple deployment, single executable file
- ✅ Fast loading speed
- ✅ Cross-platform consistent
#### Implementation Steps
**1. Add Dependencies**
```toml
[dependencies]
rust-embed = "8"
```
**2. Create Directory Structure**
```
your-project/
├── assets/
│ ├── icons/
│ │ ├── arrow-down.svg
│ │ ├── check.svg
│ │ └── ...
│ ├── images/
│ └── fonts/
└── src/
├── assets.rs
└── ...
```
**3. Implement AssetSource**
```rust
// src/assets.rs
use gpui::{AssetSource, Result, SharedString};
use rust_embed::RustEmbed;
use std::borrow::Cow;
#[derive(RustEmbed)]
#[folder = "assets"]
#[include = "icons/**/*"]
#[include = "images/**/*"]
#[include = "fonts/**/*"]
pub struct Assets;
impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
Ok(Self::get(path).map(|f| f.data))
}
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
Ok(Self::iter()
.filter(|p| p.starts_with(path))
.map(SharedString::from)
.collect())
}
}
```
**4. Register AssetSource**
```rust
// src/main.rs or examples/xxx.rs
use gpui::*;
fn main() {
let app = Application::new()
.with_assets(your_crate::Assets); // Key!
app.run(|cx| {
cx.open_window(window_options, |window, cx| {
cx.new(|cx| YourView::new(window, cx))
}).unwrap();
});
}
```
**5. Use Assets**
```rust
svg().path("icons/arrow-down.svg")
img("images/logo.png")
```
### Approach B: Filesystem Loading (For Development)
#### Advantages
- ✅ Update assets without recompiling
- ✅ Good for development and debugging
#### Disadvantages
- ❌ Need to distribute asset files
- ❌ Complex path management
#### Implementation Steps
**1. Implement Filesystem AssetSource**
```rust
// src/assets.rs
use gpui::{AssetSource, Result, SharedString};
use std::{borrow::Cow, fs, path::{Path, PathBuf}};
pub struct FsAssets {
base: PathBuf,
}
impl FsAssets {
pub fn new() -> Self {
Self {
base: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
}
}
}
impl AssetSource for FsAssets {
fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
let p = Path::new(path);
let full = if p.is_absolute() {
p.to_path_buf()
} else {
self.base.join("assets").join(p)
};
match fs::read(full) {
Ok(data) => Ok(Some(Cow::Owned(data))),
Err(_) => Ok(None),
}
}
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
let dir = self.base.join("assets").join(path);
let entries = fs::read_dir(dir)?
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.map(SharedString::from)
.collect();
Ok(entries)
}
}
```
**2. Register**
```rust
fn main() {
let app = Application::new()
.with_assets(your_crate::FsAssets::new());
app.run(|cx| {
// ...
});
}
```
## Common Questions
### Q: Why isn't my SVG displaying?
**A:** Check the following:
1. **Is AssetSource registered?**
```rust
Application::new().with_assets(Assets)
```
2. **Is the path correct?**
```rust
svg().path("icons/arrow.svg") svg().path("assets/icons/arrow.svg") ```
3. **Does the file exist?**
```bash
ls assets/icons/arrow.svg
```
4. **Is it included in the embed rules?**
```rust
#[derive(RustEmbed)]
#[folder = "assets"]
#[include = "icons/**/*"] ```
### Q: How to debug asset loading?
**A:** Add debug code:
```rust
// After application startup
if let Some(data) = cx.asset_source().load("icons/arrow.svg")? {
println!("✅ Icon loaded: {} bytes", data.len());
} else {
println!("❌ Icon not found");
}
```
### Q: Can I mix embedded and filesystem assets?
**A:** Yes, implement a combined AssetSource:
```rust
pub struct HybridAssets {
embedded: Assets,
fs: FsAssets,
}
impl AssetSource for HybridAssets {
fn load(&self, path: &str) -> Result<Option<Cow<'static, [u8]>>> {
// Try filesystem first (for development)
if let Some(data) = self.fs.load(path)? {
return Ok(Some(data));
}
// Fall back to embedded assets (for production)
self.embedded.load(path)
}
// ...
}
```
### Q: How to add new icons?
**A:**
1. Place SVG file in `assets/icons/`
2. Add new item to `IconName` enum
3. Add mapping in `path()` method
4. Recompile (for embedded approach) or use directly (for filesystem approach)
## Performance Considerations
### Embedded Assets
- **Compile Time**: Slower initial compile, fast incremental compile
- **Binary Size**: Increases (each SVG ~1-5KB)
- **Runtime Performance**: Fastest, assets in memory
### Filesystem
- **Compile Time**: Fast
- **Binary Size**: Small
- **Runtime Performance**: Requires I/O, slightly slower
## Best Practices
1. **Use Embedded Assets for Production**
- Simple deployment
- Best performance
2. **Optional Filesystem for Development**
- Fast iteration
- No recompilation needed
3. **Use Conditional Compilation**
```rust
#[cfg(debug_assertions)]
let assets = FsAssets::new();
#[cfg(not(debug_assertions))]
let assets = Assets;
Application::new().with_assets(assets)
```
4. **Organize Asset Directories**
```
assets/
├── icons/ # SVG icons
├── images/ # PNG/JPG images
├── fonts/ # Font files
└── data/ # Other data
```
## References
- [rust-embed Documentation](https://docs.rs/rust-embed/)
- [GPUI Examples](https://github.com/zed-industries/zed/tree/main/crates/gpui/examples)
- [Zed Asset Implementation](https://github.com/zed-industries/zed/blob/main/crates/zed/src/zed.rs)