diff --git a/Cargo.toml b/Cargo.toml index f015351..3f6ffc2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,7 @@ path = "examples/gallery/main.rs" [[example]] name = "docs_hello" path = "examples/docs/hello.rs" + +[[example]] +name = "docs_start" +path = "examples/docs/start.rs" diff --git a/examples/docs/start.rs b/examples/docs/start.rs new file mode 100644 index 0000000..c4c6e1f --- /dev/null +++ b/examples/docs/start.rs @@ -0,0 +1,69 @@ +use ely_gpui_component::{ + Assets, + buttons::Button, + primitives::FocusScope, + theme::{ActiveTheme, Mode, Theme}, + typography::{Heading, Paragraph}, +}; +use gpui::{ + App, AppContext, Bounds, Context, FocusHandle, IntoElement, ParentElement, Render, Styled, + TitlebarOptions, Window, WindowBounds, WindowOptions, px, size, +}; + +struct Start { + focus: FocusHandle, +} + +impl Render for Start { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let theme = cx.theme(); + let next = if theme.is_dark() { + Mode::Light + } else { + Mode::Dark + }; + FocusScope::new(&self.focus) + .root() + .size_full() + .flex() + .flex_col() + .gap_4() + .p_8() + .bg(theme.colors.bg) + .child(Heading::h1("Start")) + .child(Paragraph::new( + "Every Ely component reads the theme, so one call turns them all.", + )) + .child( + Button::new("mode", format!("{next:?} mode")) + .on_click(move |_, _, cx| Theme::set_mode(next, cx)), + ) + } +} + +fn main() { + gpui_platform::application() + .with_assets(Assets) + .run(|cx: &mut App| { + ely_gpui_component::init(cx); + Theme::set_mode_now(Mode::Dark, cx); + let bounds = Bounds::centered(None, size(px(640.0), px(400.0)), cx); + let options = WindowOptions { + window_bounds: Some(WindowBounds::Windowed(bounds)), + titlebar: Some(TitlebarOptions { + title: Some("Start".into()), + ..Default::default() + }), + ..Default::default() + }; + cx.open_window(options, |window, cx| { + cx.new(|cx| { + let focus = cx.focus_handle(); + window.focus(&focus, cx); + Start { focus } + }) + }) + .expect("window failed to open"); + cx.activate(true); + }); +} diff --git a/frontend/src/docs/getting-started.md b/frontend/src/docs/getting-started.md new file mode 100644 index 0000000..6e63561 --- /dev/null +++ b/frontend/src/docs/getting-started.md @@ -0,0 +1,25 @@ +# Getting started + +An Ely app is a GPUI app with three more parts: Ely's assets, one call to `init`, and a root `FocusScope` around each window's content. + +## Four steps + +| Step | Why | +| --- | --- | +| `gpui_platform::application().with_assets(Assets)` | Ely embeds its icons and fonts. `init` panics without them. | +| `ely_gpui_component::init(cx)` | Call once, before any window. It registers the fonts, sets the theme, and binds Tab, Shift-Tab and the keys of text fields, the code editor, documents and, natively, the terminal. | +| A root `FocusScope` | `FocusScope::new(&handle).root()` owns Tab for the window and takes focus back when the focused element leaves the tree. Focus its handle when the window opens. | +| `cx.theme()` | Colors, sizes and radii come from the theme. Components read it; an app's own boxes read it too. | + +## A window that follows the theme + +The example opens a titled window in dark mode. Its button cross-fades every component to the other mode. + +```rust example=start +``` + +`Theme::set_mode_now` before the window opens makes the first frame dark. `Theme::set_mode` fades the palette and repaints every window as it goes, so the button needs no `notify`. `Heading` and `Paragraph` take their size, weight and color from the theme. + +## Changing state from a press + +`Button::on_click` takes a closure over `&ClickEvent`, `&mut Window` and `&mut App`. To change a view's own state, capture the view with `cx.entity()` in `render`, update it in the closure, and call `cx.notify()`. The [first window](/docs/#a-first-window) counts presses that way. A change made outside `render` without `cx.notify()` draws nothing. diff --git a/frontend/src/docs/guides.ts b/frontend/src/docs/guides.ts index d96b645..766d884 100644 --- a/frontend/src/docs/guides.ts +++ b/frontend/src/docs/guides.ts @@ -6,7 +6,7 @@ import { Marked, type Tokens } from "marked"; const at = (relative: string) => fileURLToPath(new URL(relative, import.meta.url)); /** The guides in reading order, by file name. */ -const ORDER = ["introduction", "installation"]; +const ORDER = ["introduction", "installation", "getting-started"]; export interface Guide { /** The path segment; the introduction has none. */