diff --git a/README.md b/README.md index f7ee922..77eed40 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,11 @@ fn main() { } ``` -`init` registers the fonts and the theme. It returns an error if `Assets` is not wired into the application or the asset source fails. +`init` registers the fonts and the theme. It returns an error if the application's asset source cannot load one of Ely's files. + +## With GPUI Kit + +Ely runs beside Longbridge's [GPUI Kit](https://gpui-kit.com) in one window, on the GPUI snapshot Kit 0.7.1 pins. Pass `Assets::before(gpui_kit::assets::Assets)` as the one asset source, call both `init`s, open the window with `gpui_kit::open_window`, and wrap the view in Ely's root `FocusScope`. The [guide](https://elygpui.com/docs/gpui-kit) covers themes, focus and the inspector; `compat/kit` holds a whole app and the tests behind each claim. ## Gallery diff --git a/compat/kit/examples/start.rs b/compat/kit/examples/start.rs new file mode 100644 index 0000000..780c8bb --- /dev/null +++ b/compat/kit/examples/start.rs @@ -0,0 +1,89 @@ +use ely_gpui_component::{ + Assets, + buttons::Button, + primitives::FocusScope, + theme::{ActiveTheme, Mode, Theme}, + typography::{Heading, Paragraph}, +}; +use gpui_kit::{ + App, AppContext, Context, Entity, FocusHandle, IntoElement, ParentElement, Render, Styled, + Window, WindowOptions, + component::{ + WindowExt, + button::Button as KitButton, + input::{Input, InputState}, + theme::{Theme as KitTheme, ThemeMode}, + }, + div, px, +}; + +struct Start { + focus: FocusHandle, + name: Entity, +} + +impl Render for Start { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + let theme = cx.theme(); + let dark = theme.is_dark(); + FocusScope::new(&self.focus) + .root() + .size_full() + .p_8() + .bg(theme.colors.bg) + .child(column(dark, &self.name)) + } +} + +fn column(dark: bool, name: &Entity) -> impl IntoElement { + div() + .w(px(480.)) + .flex() + .flex_col() + .gap_4() + .child(Heading::h1("Ely and GPUI Kit")) + .child(Paragraph::new( + "One window, one focus order, one asset source.", + )) + .child(Input::new(name)) + .child( + KitButton::new("kit-dialog") + .label("Kit dialog") + .on_click(|_, window, cx| { + window.open_dialog(cx, |dialog, _, _| dialog.title("From GPUI Kit")); + }), + ) + .child( + Button::new("mode", if dark { "Light mode" } else { "Dark mode" }).on_click( + move |_, window, cx| { + let (ely, kit) = if dark { + (Mode::Light, ThemeMode::Light) + } else { + (Mode::Dark, ThemeMode::Dark) + }; + Theme::set_mode(ely, cx); + KitTheme::change(kit, Some(window), cx); + }, + ), + ) +} + +fn main() { + gpui_kit::application() + .with_assets(Assets::before(gpui_kit::assets::Assets)) + .run(|cx: &mut App| { + gpui_kit::init(cx); + ely_gpui_component::init(cx).expect("Ely failed to start"); + gpui_kit::open_window(WindowOptions::default(), cx, |window, cx| { + cx.new(|cx| { + let focus = cx.focus_handle(); + window.focus(&focus, cx); + Start { + focus, + name: cx.new(|cx| InputState::new(window, cx).placeholder("Name")), + } + }) + }) + .expect("window failed to open"); + }); +} diff --git a/frontend/src/docs/gpui-kit.md b/frontend/src/docs/gpui-kit.md new file mode 100644 index 0000000..27c3a6a --- /dev/null +++ b/frontend/src/docs/gpui-kit.md @@ -0,0 +1,46 @@ +# GPUI Kit + +Ely runs beside Longbridge's [GPUI Kit](https://gpui-kit.com) in one app and one window. Both build on one GPUI: Ely pins `gpui-pre =0.3.8`, the snapshot Kit 0.7.1 pins. + +## Add the dependencies + +```toml +[dependencies] +gpui-kit = "0.7" +ely-gpui-component = { git = "https://github.com/ZacharyZhang-NY/Ely-GPUI-Components" } +``` + +Kit brings GPUI and puts it at its root, so the app writes `use gpui_kit::*` and lists no `gpui` line of its own. Ely follows Kit's default GPUI. + +In an app, Cargo picks the newest Kit that shares Ely's GPUI. A Kit release on another `gpui-pre` waits until Ely's pin follows. Ely's CI asks for Kit's newest release on every push and fails on such a release, naming `gpui-pre`. + +## Start both + +| Step | Why | +| --- | --- | +| `gpui_kit::application().with_assets(Assets::before(gpui_kit::assets::Assets))` | GPUI takes one asset source. Ely's files load from Ely, every other path from Kit. `ely_gpui_component::init` returns an error if one of Ely's files does not load. | +| `gpui_kit::init(cx)` and `ely_gpui_component::init(cx)?` | In either order. | +| `gpui_kit::open_window(options, cx, build)` | Kit's `Root` hosts Kit's dialogs, sheets and notifications. | +| `FocusScope::new(&handle).root()` around the view, `handle` focused | Ely's Tab, focus return and overlays live in the scope. | + +## Focus + +Tab and Shift-Tab run through Ely's and Kit's stops in one order. Ely binds them under `ElyFocus`, the key context of its scopes, so Kit's code editor keeps Tab for indenting whichever `init` ran last. A Kit dialog opened from an Ely button keeps Tab inside and hands focus back when it closes; so does an Ely dialog over Kit's fields. + +## Two themes + +Each library keeps its own theme. A switch turns both: + +| Ely | Kit | +| --- | --- | +| `Theme::set_mode(Mode::Dark, cx)` | `Theme::change(ThemeMode::Dark, Some(window), cx)` | + +Locales work the same way: Ely reads its `I18n` global, Kit its `gpui_kit::component::set_locale`. + +## Inspector + +In debug builds Kit's `init` installs Kit's inspector. Call `ely_gpui_component::tooling::install_inspector(cx)` after it to draw Ely's. + +## A whole app + +[`compat/kit/examples/start.rs`](https://github.com/ZacharyZhang-NY/Ely-GPUI-Components/blob/main/compat/kit/examples/start.rs) puts Ely's heading and button beside Kit's field and dialog, and one button turns both themes. From a clone, run it with `cargo run --manifest-path compat/kit/Cargo.toml --example start`. The crate's tests hold each claim on this page. diff --git a/frontend/src/docs/guides.ts b/frontend/src/docs/guides.ts index 169b884..693c778 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", "getting-started", "theme", "focus", "motion", "icons", "overlays", "rendering", "i18n", "webassembly", "testing"]; +const ORDER = ["introduction", "installation", "getting-started", "gpui-kit", "theme", "focus", "motion", "icons", "overlays", "rendering", "i18n", "webassembly", "testing"]; export interface Guide { /** The path segment; the introduction has none. */ diff --git a/tasks/kit.md b/tasks/kit.md index 7e57c2f..e9ef2c0 100644 --- a/tasks/kit.md +++ b/tasks/kit.md @@ -7,7 +7,7 @@ Started 2026-10-07. Ely and Longbridge's GPUI Kit (`gpui-kit` on crates.io) shar - Ely binds Tab with no context, which gpui ranks at the deepest level. In a Kit code editor inside an Ely scope, Tab ties with Kit's indent, and the later `init` wins. - An app has one asset source. Of Ely's 306 icons, Kit's source holds 290 paths, 289 with the same drawing; `credit-card` differs by one stroke (Lucide 1.48 against 1.43). Ely's `init` checked one icon, so Kit's source alone passed and Ely's other files went missing later. - Kit's `init` installs its inspector in debug builds; Ely's `install_inspector` replaces it when called after. -- Kit can also run on `gpui-fast`; at the owner's word, Ely follows Kit's default gpui alone. +- Kit's main branch adds a `gpui-fast` engine, unreleased in 0.7.1; at the owner's word, Ely follows Kit's default gpui alone. Rules: the work order follows dependencies, since the PRD has no chapter for it. Each item passes `scripts/check.sh` and a codex review (`gpt-6.1-sol`, effort `high`) in a herdr split, five rounds at most, then commits and pushes. @@ -17,7 +17,7 @@ Rules: the work order follows dependencies, since the PRD has no chapter for it. - [x] K02 Tab and Shift-Tab bound under `FocusScope`'s own context, so a deeper context's Tab wins whatever the order of `init` - [x] K03 Assets: `init` checks every embedded file through the app's source; one source serves Ely's files and another's - [x] K04 `compat/kit`: a crate on the newest Kit that runs both libraries in one window, in `scripts/check.sh` and CI -- [ ] K05 Docs: README, the site's guide, AGENTS.md +- [x] K05 Docs: README, the site's guide, AGENTS.md - [ ] K06 Acceptance table ## Log @@ -27,3 +27,4 @@ One line per item: what changed, what stayed and why, review rounds. - K02: `FocusScope` sets the key context `ElyFocus`, and `init` binds Tab and Shift-Tab under it. A host's Tab in a deeper context wins whatever the order of `init` (a test binds the host's first, the order that lost: 0 indents before, 1 after); Ely's terminal, editor, traps and the inspector's scope behave as before. Review: 1 round. - K03: `init` loads every file Ely bundles (318, Plex among them) through the app's source and fails naming the first it cannot; an icons-only source like Kit's passed the old one-icon check and fails now. `Assets::before(other)` serves Ely's paths first and the rest from `other`, its errors passing through. Docs and AGENTS.md say so. Review: 1 round. - K04: `compat/kit`, its own workspace, opens Ely's root scope inside Kit's `Root` and tests Tab through both libraries in each `init` order (red with K02 undone), Kit's dialog from an Ely button by key and by pointer, Ely's dialog over Kit's fields, and one asset source for both. `check.sh` resolves it afresh and holds `gpui-kit` to the newest release, so a Kit release on another gpui fails resolution naming `gpui-pre` (shown with Kit 0.7.0). Review: 2 rounds. +- K05: the site's GPUI Kit guide (`/docs/gpui-kit`): dependencies, the start in four steps, focus, two themes, the inspector; the README points to it. `compat/kit/examples/start.rs` is a whole app, compiled by `check.sh` and run on Windows, where both libraries draw in one window. The guide says an app's Cargo keeps the newest Kit that shares Ely's GPUI, and names no unreleased Kit feature. Review: 2 rounds.