diff --git a/Cargo.lock b/Cargo.lock index ccfec1c4..25baa906 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,16 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +[[package]] +name = "app_assets" +version = "0.2.0" +dependencies = [ + "anyhow", + "gpui", + "gpui-component", + "rust-embed", +] + [[package]] name = "arbitrary" version = "1.4.1" @@ -8760,6 +8770,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "window_title" +version = "0.2.0" +dependencies = [ + "anyhow", + "gpui", + "gpui-component", +] + [[package]] name = "windows" version = "0.57.0" diff --git a/Cargo.toml b/Cargo.toml index 318cc10d..dcada295 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/hello_world", "crates/macros", "crates/story", "crates/ui"] +members = ["examples/hello_world", "examples/window_title", "crates/macros", "crates/story", "crates/ui", "examples/window_title", "examples/app_assets", "examples/app_assets"] default-members = ["crates/story"] resolver = "2" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..224aa7d0 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,18 @@ +# GPUI Component basic examples + +This folder contains basic examples of how to use the GPUI Component library. Each example demonstrates a specific feature or functionality of the library. + +Unlike the examples in the `story` folder, these examples focus on 1 example for 1 feature, making it easier to understand and implement specific functionalities in your own projects. + +## Contributing + +Feel free to contribute more examples to this folder! + +If you have a specific use case or feature you'd like to demonstrate, please create a new example file and submit a pull request. We will happy to merge it into the repository. + +When creating a new example, please follow these guidelines: + +1. Keep 1 example just doing 1 thing for more clarity. +2. Testing the example to ensure it works as expected. +3. Write some comment at some key parts of the code to explain what it does. +4. Following the code style and name style used in the existing examples or in entire of GPUI Component. diff --git a/examples/app_assets/Cargo.toml b/examples/app_assets/Cargo.toml new file mode 100644 index 00000000..f49ab66e --- /dev/null +++ b/examples/app_assets/Cargo.toml @@ -0,0 +1,15 @@ +[package] +edition = "2021" +name = "app_assets" +description = "Example to load icons or images from assets folder." +publish = false +version = "0.2.0" + +[dependencies] +anyhow.workspace = true +gpui.workspace = true +gpui-component = { workspace = true } +rust-embed = { version = "8", features = ["interpolate-folder-path"] } + +[lints] +workspace = true diff --git a/examples/app_assets/README.md b/examples/app_assets/README.md new file mode 100644 index 00000000..1e346120 --- /dev/null +++ b/examples/app_assets/README.md @@ -0,0 +1,62 @@ +## Icon assets in GPUI Component + +The [IconName](https://github.com/longbridge/gpui-component/blob/6998708b817024c2ac0f1ea164d74ddfc024e124/crates/ui/src/icon.rs#L9) is a enum that defined a bunch of icon names, because some internal components in GPUI Component will use them. + +You can see, we have a lot of svg icon files in the `assets/icons` folder, but we are not embed all of the icon files in the library by default. This for keep the library size small. + +So you must have your own icon files to use the `Icon` component in GPUI Component. + +You can download the icon files from [here](https://lucide.dev/) or use your own icon files as you wish, just use the same filename as the icon name (match with the `IconName` defined) you want to use. + +For example your assets folder: + +``` +app_root + assets + icons + close.svg + menu.svg + ... + src + main.rs + Cargo.toml +``` + +You also can just copy the svg files you want from the `assets/icons` folder in GPUI Component repo to your own assets folder. + +## How to use + +You need define a `Assets` struct with rust-embed to register assets to GPUI application. + +```rs +use anyhow::anyhow; +use gpui::*; +use rust_embed::RustEmbed; +use std::borrow::Cow; + +#[derive(RustEmbed)] +#[folder = "./assets"] +#[include = "icons/**/*.svg"] +pub struct Assets; + +impl AssetSource for Assets { + fn load(&self, path: &str) -> Result>> { + 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()) + } +} + +fn main() { + // Call with_assets to register assets + let app = Application::new().with_assets(Assets); + + // ... +} +``` diff --git a/examples/app_assets/assets/icons/bot.svg b/examples/app_assets/assets/icons/bot.svg new file mode 100644 index 00000000..efcd1bbf --- /dev/null +++ b/examples/app_assets/assets/icons/bot.svg @@ -0,0 +1,14 @@ + diff --git a/examples/app_assets/assets/icons/inbox.svg b/examples/app_assets/assets/icons/inbox.svg new file mode 100644 index 00000000..17a45bb2 --- /dev/null +++ b/examples/app_assets/assets/icons/inbox.svg @@ -0,0 +1,14 @@ + diff --git a/examples/app_assets/src/main.rs b/examples/app_assets/src/main.rs new file mode 100644 index 00000000..aaabc151 --- /dev/null +++ b/examples/app_assets/src/main.rs @@ -0,0 +1,64 @@ +use anyhow::anyhow; +use gpui::*; +use gpui_component::{v_flex, IconName, Root}; +use rust_embed::RustEmbed; +use std::borrow::Cow; + +/// An asset source that loads assets from the `./assets` folder. +#[derive(RustEmbed)] +#[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 { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + v_flex() + .gap_2() + .size_full() + .items_center() + .justify_center() + .text_center() + .child(IconName::Inbox) + .child(IconName::Bot) + } +} + +fn main() { + // Register Assets to GPUI application. + let app = Application::new().with_assets(Assets); + + app.run(move |cx| { + // We must initialize gpui_component before using it. + gpui_component::init(cx); + + cx.spawn(async move |cx| { + cx.open_window(WindowOptions::default(), |window, cx| { + let view = cx.new(|_| Example); + // The first level on the window must be Root. + cx.new(|cx| Root::new(view.into(), window, cx)) + })?; + + Ok::<_, anyhow::Error>(()) + }) + .detach(); + }); +} diff --git a/crates/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml similarity index 100% rename from crates/hello_world/Cargo.toml rename to examples/hello_world/Cargo.toml diff --git a/crates/hello_world/src/main.rs b/examples/hello_world/src/main.rs similarity index 79% rename from crates/hello_world/src/main.rs rename to examples/hello_world/src/main.rs index 30821917..75f16a2a 100644 --- a/crates/hello_world/src/main.rs +++ b/examples/hello_world/src/main.rs @@ -29,6 +29,7 @@ fn main() { let app = Application::new(); app.run(move |cx| { + // We must initialize gpui_component before using it. gpui_component::init(cx); cx.activate(true); @@ -57,19 +58,11 @@ fn main() { ..Default::default() }; - let window = cx - .open_window(options, |window, cx| { - let view = cx.new(|_| HelloWorld); - cx.new(|cx| Root::new(view.into(), window, cx)) - }) - .expect("failed to open window"); - - window - .update(cx, |_, window, _| { - window.activate_window(); - window.set_window_title("Example"); - }) - .expect("failed to update window"); + cx.open_window(options, |window, cx| { + let view = cx.new(|_| HelloWorld); + // The first level on the window must be Root. + cx.new(|cx| Root::new(view.into(), window, cx)) + })?; Ok::<_, anyhow::Error>(()) }) diff --git a/examples/window_title/Cargo.toml b/examples/window_title/Cargo.toml new file mode 100644 index 00000000..6d80e2d4 --- /dev/null +++ b/examples/window_title/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2021" +name = "window_title" +description = "An example of using gpui-component to create a window with a custom title bar." +publish = false +version = "0.2.0" + +[dependencies] +anyhow.workspace = true +gpui.workspace = true +gpui-component = { workspace = true } + +[lints] +workspace = true diff --git a/examples/window_title/src/main.rs b/examples/window_title/src/main.rs new file mode 100644 index 00000000..6e9c79b4 --- /dev/null +++ b/examples/window_title/src/main.rs @@ -0,0 +1,60 @@ +use gpui::*; +use gpui_component::{ + button::{Button, ButtonVariants}, + v_flex, Root, TitleBar, +}; + +pub struct Example; +impl Render for Example { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + v_flex() + .size_full() + .child( + // Render custom title bar on top of Root view. + TitleBar::new() + .justify_between() + .pr_2() + .child("App with Custom title bar") + .child("Right Item"), + ) + .child( + div() + .id("window-body") + .p_5() + .size_full() + .items_center() + .justify_center() + .child("Hello, World!") + .child( + Button::new("ok") + .primary() + .label("Let's Go!") + .on_click(|_, _, _| println!("Clicked!")), + ), + ) + } +} + +fn main() { + let app = Application::new(); + + app.run(move |cx| { + gpui_component::init(cx); + + cx.spawn(async move |cx| { + let window_options = WindowOptions { + // Setup GPUI to use custom title bar + titlebar: Some(TitleBar::title_bar_options()), + ..Default::default() + }; + + cx.open_window(window_options, |window, cx| { + let view = cx.new(|_| Example); + cx.new(|cx| Root::new(view.into(), window, cx)) + })?; + + Ok::<_, anyhow::Error>(()) + }) + .detach(); + }); +}