From 37d1678b6f1897096d48ff02e860a71794521be3 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Fri, 14 Nov 2025 15:03:26 +0800 Subject: [PATCH] assets: Add `gpui-component-assets` crate. (#1601) --- Cargo.lock | 15 ++++++- Cargo.toml | 7 +++- crates/assets/Cargo.toml | 20 +++++++++ crates/assets/README.md | 7 ++++ .../src/assets.rs => assets/src/lib.rs} | 10 +++++ crates/story/Cargo.toml | 2 +- crates/story/examples/brush.rs | 2 +- crates/story/examples/dock.rs | 5 ++- crates/story/examples/editor.rs | 3 +- crates/story/examples/html.rs | 2 +- crates/story/examples/large-text.rs | 2 +- crates/story/examples/markdown.rs | 3 +- crates/story/examples/tiles.rs | 3 +- crates/story/examples/webview.rs | 3 +- crates/story/src/lib.rs | 2 - crates/story/src/main.rs | 1 + docs/docs/assets.md | 42 +++++++++++++++++-- docs/docs/getting-started.md | 12 +++++- examples/app_assets/README.md | 20 +++++++++ examples/dialog_overlay/Cargo.toml | 3 +- examples/dialog_overlay/src/main.rs | 3 +- examples/input/Cargo.toml | 3 +- examples/input/src/main.rs | 3 +- examples/window_title/Cargo.toml | 4 +- .../assets/icons/window-close.svg | 9 ---- .../assets/icons/window-maximize.svg | 9 ---- .../assets/icons/window-minimize.svg | 9 ---- .../assets/icons/window-restore.svg | 10 ----- examples/window_title/src/main.rs | 28 +------------ 29 files changed, 151 insertions(+), 91 deletions(-) create mode 100644 crates/assets/Cargo.toml create mode 100644 crates/assets/README.md rename crates/{story/src/assets.rs => assets/src/lib.rs} (69%) delete mode 100644 examples/window_title/assets/icons/window-close.svg delete mode 100644 examples/window_title/assets/icons/window-maximize.svg delete mode 100644 examples/window_title/assets/icons/window-minimize.svg delete mode 100644 examples/window_title/assets/icons/window-restore.svg diff --git a/Cargo.lock b/Cargo.lock index 9e3168d9..0d965053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1610,6 +1610,7 @@ dependencies = [ "anyhow", "gpui", "gpui-component", + "gpui-component-assets", ] [[package]] @@ -2893,6 +2894,15 @@ dependencies = [ "zed-sum-tree", ] +[[package]] +name = "gpui-component-assets" +version = "0.4.0-preview2" +dependencies = [ + "anyhow", + "gpui", + "rust-embed", +] + [[package]] name = "gpui-component-macros" version = "0.4.0-preview2" @@ -2913,6 +2923,7 @@ dependencies = [ "fake", "gpui", "gpui-component", + "gpui-component-assets", "gtk", "itertools 0.14.0", "lsp-types 0.97.0", @@ -2920,7 +2931,6 @@ dependencies = [ "raw-window-handle", "regex", "reqwest_client", - "rust-embed", "serde", "serde_json", "smol", @@ -3677,6 +3687,7 @@ dependencies = [ "anyhow", "gpui", "gpui-component", + "gpui-component-assets", ] [[package]] @@ -8813,7 +8824,7 @@ dependencies = [ "anyhow", "gpui", "gpui-component", - "rust-embed", + "gpui-component-assets", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 22855294..7b714dc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,14 @@ [workspace] default-members = [ "crates/ui", - "crates/story" + "crates/story", + "crates/assets" ] members = [ "crates/macros", "crates/story", "crates/ui", + "crates/assets", "examples/app_assets", "examples/hello_world", "examples/input", @@ -16,8 +18,9 @@ members = [ resolver = "2" [workspace.dependencies] -gpui-component = { path = "crates/ui" } +gpui-component = { path = "crates/ui", version = "0.4.0-preview2" } gpui-component-macros = { path = "crates/macros", version = "0.4.0-preview2" } +gpui-component-assets = { path = "crates/assets", version = "0.4.0-preview2" } story = { path = "crates/story" } gpui = "0.2.2" diff --git a/crates/assets/Cargo.toml b/crates/assets/Cargo.toml new file mode 100644 index 00000000..f2cf5cca --- /dev/null +++ b/crates/assets/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "gpui-component-assets" +description = "Default bundled assets for GPUI Component." +edition = "2021" +keywords = ["desktop", "gpui", "shadcn", "ui", "uikit"] +license = "Apache-2.0" +documentation = "https://docs.rs/gpui-component" +homepage = "https://longbridge.github.io/gpui-component" +repository = "https://github.com/longbridge/gpui-component" +publish = true +readme = "README.md" +version = "0.4.0-preview2" + +[lib] +doctest = false + +[dependencies] +anyhow.workspace = true +gpui.workspace = true +rust-embed = { version = "8.7.2", features = ["interpolate-folder-path"] } diff --git a/crates/assets/README.md b/crates/assets/README.md new file mode 100644 index 00000000..878057c2 --- /dev/null +++ b/crates/assets/README.md @@ -0,0 +1,7 @@ +# GPUI Component Assets + +The default assets bundle for [GPUI Component](https://github.com/longbridge/gpui-component). + +## License + +Apache-2.0 diff --git a/crates/story/src/assets.rs b/crates/assets/src/lib.rs similarity index 69% rename from crates/story/src/assets.rs rename to crates/assets/src/lib.rs index 6eef4f02..cb37e0e8 100644 --- a/crates/story/src/assets.rs +++ b/crates/assets/src/lib.rs @@ -3,6 +3,16 @@ use gpui::{AssetSource, Result, SharedString}; use rust_embed::RustEmbed; use std::borrow::Cow; +/// Embed application assets for GPUI Component. +/// +/// This assets provides icons svg files for [IconName](https://docs.rs/gpui-component/latest/gpui_component/enum.IconName.html). +/// +/// ``` +/// use gpui::*; +/// use gpui_component_assets::Assets; +/// +/// let app = Application::new().with_assets(Assets); +/// ``` #[derive(RustEmbed)] #[folder = "$CARGO_MANIFEST_DIR/../../assets"] #[include = "icons/**/*.svg"] diff --git a/crates/story/Cargo.toml b/crates/story/Cargo.toml index 92b3c4c7..764c9d3a 100644 --- a/crates/story/Cargo.toml +++ b/crates/story/Cargo.toml @@ -14,6 +14,7 @@ gpui-component = { workspace = true, features = [ "tree-sitter-languages", "webview", ] } +gpui-component-assets.workspace = true reqwest_client = { path = "../reqwest_client" } autocorrect = "2.14.2" @@ -25,7 +26,6 @@ lsp-types.workspace = true rand = "0.8" raw-window-handle = { version = "0.6", features = ["std"] } regex = "1" -rust-embed = { version = "8.7.2", features = ["interpolate-folder-path"] } serde = "1" serde_json = "1" smol.workspace = true diff --git a/crates/story/examples/brush.rs b/crates/story/examples/brush.rs index 79cc661a..f01ac701 100644 --- a/crates/story/examples/brush.rs +++ b/crates/story/examples/brush.rs @@ -10,7 +10,7 @@ use gpui_component::{ slider::{Slider, SliderState}, v_flex, }; -use gpui_component_story::Assets; +use gpui_component_assets::Assets; pub struct BrushStory { focus_handle: gpui::FocusHandle, diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index 336755a6..0522f372 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -7,9 +7,10 @@ use gpui_component::{ menu::DropdownMenu, }; +use gpui_component_assets::Assets; use gpui_component_story::{ - AccordionStory, AppState, AppTitleBar, Assets, ButtonStory, CalendarStory, DialogStory, - FormStory, IconStory, ImageStory, InputStory, LabelStory, ListStory, NotificationStory, Open, + AccordionStory, AppState, AppTitleBar, ButtonStory, CalendarStory, DialogStory, FormStory, + IconStory, ImageStory, InputStory, LabelStory, ListStory, NotificationStory, Open, PopoverStory, ProgressStory, ResizableStory, ScrollableStory, SelectStory, SidebarStory, StoryContainer, SwitchStory, TableStory, TooltipStory, WebViewStory, }; diff --git a/crates/story/examples/editor.rs b/crates/story/examples/editor.rs index 26c2f2d4..5da3221e 100644 --- a/crates/story/examples/editor.rs +++ b/crates/story/examples/editor.rs @@ -23,7 +23,8 @@ use gpui_component::{ tree::{TreeItem, TreeState, tree}, v_flex, }; -use gpui_component_story::{Assets, Open}; +use gpui_component_assets::Assets; +use gpui_component_story::Open; use lsp_types::{ CodeAction, CodeActionKind, CompletionContext, CompletionItem, CompletionResponse, CompletionTextEdit, InsertReplaceEdit, TextEdit, WorkspaceEdit, diff --git a/crates/story/examples/html.rs b/crates/story/examples/html.rs index 4337ab0b..132cfa8f 100644 --- a/crates/story/examples/html.rs +++ b/crates/story/examples/html.rs @@ -5,7 +5,7 @@ use gpui_component::{ resizable::h_resizable, text::TextView, }; -use gpui_component_story::Assets; +use gpui_component_assets::Assets; pub struct Example { input_state: Entity, diff --git a/crates/story/examples/large-text.rs b/crates/story/examples/large-text.rs index 93abf6a2..3a6d63ef 100644 --- a/crates/story/examples/large-text.rs +++ b/crates/story/examples/large-text.rs @@ -6,7 +6,7 @@ use gpui_component::{ input::{self, Input, InputEvent, InputState, TabSize}, v_flex, }; -use gpui_component_story::Assets; +use gpui_component_assets::Assets; pub struct Example { editor: Entity, diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index 216ee1d4..cb3a9713 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -5,7 +5,8 @@ use gpui_component::{ resizable::{h_resizable, resizable_panel}, text::TextView, }; -use gpui_component_story::{Assets, Open}; +use gpui_component_assets::Assets; +use gpui_component_story::Open; pub struct Example { input_state: Entity, diff --git a/crates/story/examples/tiles.rs b/crates/story/examples/tiles.rs index a570d5d3..6afb36e5 100644 --- a/crates/story/examples/tiles.rs +++ b/crates/story/examples/tiles.rs @@ -9,7 +9,8 @@ use gpui_component::{ input::{Input, InputState}, scroll::ScrollbarShow, }; -use gpui_component_story::{Assets, ButtonStory, IconStory, StoryContainer}; +use gpui_component_assets::Assets; +use gpui_component_story::{ButtonStory, IconStory, StoryContainer}; use serde::{Deserialize, Serialize}; use std::{sync::Arc, time::Duration}; diff --git a/crates/story/examples/webview.rs b/crates/story/examples/webview.rs index 0383b296..3db0a553 100644 --- a/crates/story/examples/webview.rs +++ b/crates/story/examples/webview.rs @@ -1,5 +1,6 @@ use gpui::*; -use gpui_component_story::{Assets, WebViewStory}; +use gpui_component_assets::Assets; +use gpui_component_story::WebViewStory; pub struct Example { root: Entity, diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 11452711..01dfceae 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -1,7 +1,6 @@ mod accordion_story; mod alert_story; mod app_menus; -mod assets; mod avatar_story; mod badge_story; mod button_story; @@ -51,7 +50,6 @@ mod virtual_list_story; mod webview_story; mod welcome_story; -pub use assets::Assets; use gpui::{ Action, AnyElement, AnyView, App, AppContext, Bounds, Context, Div, Entity, EventEmitter, Focusable, Global, Hsla, InteractiveElement, IntoElement, KeyBinding, ParentElement, Pixels, diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 2eb826f7..e1dbb502 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -6,6 +6,7 @@ use gpui_component::{ sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, v_flex, }; +use gpui_component_assets::Assets; use gpui_component_story::*; pub struct Gallery { diff --git a/docs/docs/assets.md b/docs/docs/assets.md index 231759c6..bceaddca 100644 --- a/docs/docs/assets.md +++ b/docs/docs/assets.md @@ -7,14 +7,45 @@ order: -4 The [IconName] and [Icon] in GPUI Component provide a comprehensive set of icons and assets that can be easily integrated into your GPUI applications. -But for minimal size applications, **we have not embedded any icon assets by default**. You can choose to include only the icons you need. +But for minimal size applications, **we have not embedded any icon assets by default** in `gpui-component` crate. + +We split the icon assets into a separate crate [gpui-component-assets] to allow developers to choose whether to include the icon assets in their applications or if you don't need the icons at all, you can build your own assets. + +## Use default bundled assets + +The [gpui-component-assets] crate provides a default bundled assets implementation that includes all the icon files in the `assets/icons` folder. + +To use the default bundled assets, you need to add the `gpui-component-assets` crate as a dependency in your `Cargo.toml`: + +```toml +[dependencies] +gpui-component = "*" +gpui-component-assets = "*" +``` + +Then we need call the `with_assets` method when creating the GPUI application to register the asset source: + +```rs +use gpui::*; +use gpui_component_assets::Assets; + +let app = Application::new().with_assets(Assets); +``` + +Now, we can use `IconName` and `Icon` in our application as usual, the all icon assets are loaded from the default bundled assets. + +Continue [Use the icons](#use-the-icons) section to see how to use the icons in your application. + +## Build you own assets + +You may have a specific set of icons that you want to use in your application, or you may want to reduce the size of your application binary by including only the icons you need. + +In this case, you can build your own assets by following these steps. The [assets](https://github.com/longbridge/gpui-component/tree/main/assets) folder in source code contains all the available icons in SVG format, every file is that GPUI Component support, it matched with the [IconName] enum. You can download the SVG files you need from the [assets] folder, or you can use your own SVG files by following the [IconName] naming convention. -## Usage - In GPUI application, we can use the [rust-embed] crate to embed the SVG files into the application binary. And GPUI Application providers an `AssetSource` trait to load the assets. @@ -76,6 +107,8 @@ fn main() { } ``` +## Use the icons + Now we can use the icons in our application: ```rs @@ -97,9 +130,10 @@ impl Render for Example { ## Resources -- [Lucide Icons](https://lucide.dev/) - The icon set used in GPUI Component is based on the open-source [Lucide Icons](https://lucide.dev/) library, which provides a wide range of customizable SVG icons. +- [Lucide Icons](https://lucide.dev/) - The icon set used in GPUI Component is based on the open-source Lucide Icons library, which provides a wide range of customizable SVG icons. [rust-embed]: https://docs.rs/rust-embed/latest/rust_embed/ [IconName]: https://docs.rs/gpui_component/latest/gpui_component/icon/enum.IconName.html [Icon]: https://docs.rs/gpui_component/latest/gpui_component/icon/struct.Icon.html [assets]: https://github.com/longbridge/gpui-component/tree/main/assets +[gpui-component-assets]: https://crates.io/crates/gpui-component-assets diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 3e3ea697..586a808c 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -14,8 +14,18 @@ Add dependencies to your `Cargo.toml`: [dependencies] gpui = "0.2" gpui-component = "0.4.0-preview2" +# Optional, for default bundled assets +gpui-component-assets = "0.4.0-preview2" ``` +:::tip +The `gpui-component-assets` crate is optional. + +It provides a default set of icon assets. If you want to manage your own assets, you can skip adding this dependency. + +See [Icons & Assets](./assets.md) for more details. +::: + ## Quick Start Here's a simple example to get you started: @@ -45,7 +55,7 @@ impl Render for HelloWorld { } fn main() { - let app = Application::new(); + let app = Application::new().with_assets(gpui_component_assets::Assets); app.run(move |cx| { // This must be called before using any GPUI Component features. diff --git a/examples/app_assets/README.md b/examples/app_assets/README.md index 1e346120..b8f617c3 100644 --- a/examples/app_assets/README.md +++ b/examples/app_assets/README.md @@ -60,3 +60,23 @@ fn main() { // ... } ``` + +## Use default bundled assets. + +The `gpui-component-assets` crate provide a default bundled assets implementation that include all the icon files in the `assets/icons` folder. + +If you don't want to manage your own icon files, you can just use the default bundled assets. + +Just add `gpui-component-assets` as a dependency in your `Cargo.toml`: + +```toml +[dependencies] +gpui-component = "*" +gpui-component-assets = "*" +``` + +And then use it in your application: + +```rs +let app = Application::new().with_assets(gpui_component_assets::Assets); +``` diff --git a/examples/dialog_overlay/Cargo.toml b/examples/dialog_overlay/Cargo.toml index f52ddb44..4c64d2a0 100644 --- a/examples/dialog_overlay/Cargo.toml +++ b/examples/dialog_overlay/Cargo.toml @@ -8,7 +8,8 @@ version = "0.4.0-preview2" [dependencies] anyhow.workspace = true gpui.workspace = true -gpui-component = { workspace = true } +gpui-component.workspace = true +gpui-component-assets.workspace = true [lints] workspace = true diff --git a/examples/dialog_overlay/src/main.rs b/examples/dialog_overlay/src/main.rs index 11b55dbb..c8e9c425 100644 --- a/examples/dialog_overlay/src/main.rs +++ b/examples/dialog_overlay/src/main.rs @@ -1,5 +1,6 @@ use gpui::*; use gpui_component::{button::*, menu::ContextMenuExt, *}; +use gpui_component_assets::Assets; actions!(class_menu, [Open, Delete, Export, Info]); @@ -78,7 +79,7 @@ impl Render for HelloWorld { } fn main() { - let app = Application::new(); + let app = Application::new().with_assets(Assets); app.run(move |cx| { gpui_component::init(cx); diff --git a/examples/input/Cargo.toml b/examples/input/Cargo.toml index abdc3012..8de1ff8e 100644 --- a/examples/input/Cargo.toml +++ b/examples/input/Cargo.toml @@ -7,7 +7,8 @@ edition = "2024" [dependencies] anyhow.workspace = true gpui.workspace = true -gpui-component = { workspace = true } +gpui-component.workspace = true +gpui-component-assets.workspace = true [lints] workspace = true diff --git a/examples/input/src/main.rs b/examples/input/src/main.rs index 72a4f161..f7d5ad98 100644 --- a/examples/input/src/main.rs +++ b/examples/input/src/main.rs @@ -3,6 +3,7 @@ use gpui_component::{ input::{Input, InputEvent, InputState}, *, }; +use gpui_component_assets::Assets; pub struct Example { input_state: Entity, @@ -53,7 +54,7 @@ impl Render for Example { } fn main() { - let app = Application::new(); + let app = Application::new().with_assets(Assets); app.run(move |cx| { // This must be called before using any GPUI Component features. diff --git a/examples/window_title/Cargo.toml b/examples/window_title/Cargo.toml index fc8cc54e..c2c9bfab 100644 --- a/examples/window_title/Cargo.toml +++ b/examples/window_title/Cargo.toml @@ -8,8 +8,8 @@ version = "0.4.0-preview2" [dependencies] anyhow.workspace = true gpui.workspace = true -gpui-component = { workspace = true } -rust-embed = { version = "8", features = ["interpolate-folder-path"] } +gpui-component.workspace = true +gpui-component-assets.workspace = true [lints] workspace = true diff --git a/examples/window_title/assets/icons/window-close.svg b/examples/window_title/assets/icons/window-close.svg deleted file mode 100644 index 62b6b0c7..00000000 --- a/examples/window_title/assets/icons/window-close.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/window_title/assets/icons/window-maximize.svg b/examples/window_title/assets/icons/window-maximize.svg deleted file mode 100644 index 9da4cd7e..00000000 --- a/examples/window_title/assets/icons/window-maximize.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/window_title/assets/icons/window-minimize.svg b/examples/window_title/assets/icons/window-minimize.svg deleted file mode 100644 index 708e27be..00000000 --- a/examples/window_title/assets/icons/window-minimize.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/examples/window_title/assets/icons/window-restore.svg b/examples/window_title/assets/icons/window-restore.svg deleted file mode 100644 index 896b6b60..00000000 --- a/examples/window_title/assets/icons/window-restore.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/examples/window_title/src/main.rs b/examples/window_title/src/main.rs index 44f6c515..25bbebf1 100644 --- a/examples/window_title/src/main.rs +++ b/examples/window_title/src/main.rs @@ -1,34 +1,8 @@ -use anyhow::anyhow; use gpui::*; use gpui_component::{ button::{Button, ButtonVariants}, h_flex, v_flex, Root, TitleBar, }; -use rust_embed::Embed; -use std::borrow::Cow; - -#[derive(Embed)] -#[folder = "./assets"] -#[include = "icons/**/*.svg"] -pub struct Assets; - -impl AssetSource for Assets { - fn load(&self, path: &str) -> Result>> { - if path.is_empty() { - return Ok(None); - } - - Self::get(path) - .map(|f| Some(f.data)) - .ok_or_else(|| anyhow!("could not find asset at path \"{path}\"")) - } - - fn list(&self, path: &str) -> Result> { - Ok(Self::iter() - .filter_map(|p| p.starts_with(path).then(|| p.into())) - .collect()) - } -} pub struct Example; impl Render for Example { @@ -65,7 +39,7 @@ impl Render for Example { } fn main() { - let app = Application::new().with_assets(Assets); + let app = Application::new().with_assets(gpui_component_assets::Assets); app.run(move |cx| { gpui_component::init(cx);