diff --git a/.claude/skills/gpui-component-dev/SKILL.md b/.claude/skills/gpui-component-dev/SKILL.md new file mode 100644 index 0000000000..cf91dab02c --- /dev/null +++ b/.claude/skills/gpui-component-dev/SKILL.md @@ -0,0 +1,13 @@ +--- +name: gpui-component-dev +description: Contributing to the gpui-component library: creating new UI components, writing component stories, writing component documentation, and writing GitHub PR descriptions. Use when adding a new component to crates/ui, writing a story in crates/story/src/stories, creating docs in the docs/ folder, or writing a PR description for this project. +--- + +## Navigation + +| Topic | File | When to load | +|-------|------|--------------| +| Creating a new component | [new-component.md](references/new-component.md) | Adding new components to `crates/ui/src` | +| Writing stories | [story.md](references/story.md) | Creating examples in `crates/story/src/stories` | +| Writing documentation | [documentation.md](references/documentation.md) | Writing docs in `docs/docs/components/` | +| Writing PR descriptions | [pr-description.md](references/pr-description.md) | GitHub PR descriptions and breaking changes format | diff --git a/.claude/skills/generate-component-documentation/SKILL.md b/.claude/skills/gpui-component-dev/references/documentation.md similarity index 76% rename from .claude/skills/generate-component-documentation/SKILL.md rename to .claude/skills/gpui-component-dev/references/documentation.md index 5fb8dce02c..5cf66f37d0 100644 --- a/.claude/skills/generate-component-documentation/SKILL.md +++ b/.claude/skills/gpui-component-dev/references/documentation.md @@ -1,7 +1,3 @@ ---- -name: generate-component-documentation -description: Generate documentation for new components. Use when writing docs, documenting components, or creating component documentation. ---- ## Instructions diff --git a/.claude/skills/new-component/SKILL.md b/.claude/skills/gpui-component-dev/references/new-component.md similarity index 96% rename from .claude/skills/new-component/SKILL.md rename to .claude/skills/gpui-component-dev/references/new-component.md index d6eef3e513..2c03e3e09f 100644 --- a/.claude/skills/new-component/SKILL.md +++ b/.claude/skills/gpui-component-dev/references/new-component.md @@ -1,7 +1,6 @@ ---- -name: new-component -description: Create new GPUI components. Use when building components, writing UI elements, or creating new component implementations. ---- +# Creating New Components + +**Contents:** [Instructions](#instructions) · [Component Types](#component-types) · [Implementation Steps](#implementation-steps) · [Common Patterns](#common-patterns) ## Instructions diff --git a/.claude/skills/github-pull-request-description/SKILL.md b/.claude/skills/gpui-component-dev/references/pr-description.md similarity index 92% rename from .claude/skills/github-pull-request-description/SKILL.md rename to .claude/skills/gpui-component-dev/references/pr-description.md index ed282931db..ed6ebf2941 100644 --- a/.claude/skills/github-pull-request-description/SKILL.md +++ b/.claude/skills/gpui-component-dev/references/pr-description.md @@ -1,7 +1,3 @@ ---- -name: github-pull-request-description -description: Write a description to description GitHub Pull Request. ---- ## Description diff --git a/.claude/skills/generate-component-story/SKILL.md b/.claude/skills/gpui-component-dev/references/story.md similarity index 76% rename from .claude/skills/generate-component-story/SKILL.md rename to .claude/skills/gpui-component-dev/references/story.md index 00be463fa4..1e8c031d23 100644 --- a/.claude/skills/generate-component-story/SKILL.md +++ b/.claude/skills/gpui-component-dev/references/story.md @@ -1,7 +1,3 @@ ---- -name: generate-component-story -description: Create story examples for components. Use when writing stories, creating examples, or demonstrating component usage. ---- ## Instructions diff --git a/.claude/skills/gpui-context/SKILL.md b/.claude/skills/gpui-context/SKILL.md deleted file mode 100644 index dc9e27983f..0000000000 --- a/.claude/skills/gpui-context/SKILL.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -name: gpui-context -description: Context management in GPUI including App, Window, and AsyncApp. Use when working with contexts, entity updates, or window operations. Different context types provide different capabilities for UI rendering, entity management, and async operations. ---- - -## Overview - -GPUI uses different context types for different scenarios: - -**Context Types:** -- **`App`**: Global app state, entity creation -- **`Window`**: Window-specific operations, painting, layout -- **`Context`**: Entity-specific context for component `T` -- **`AsyncApp`**: Async context for foreground tasks -- **`AsyncWindowContext`**: Async context with window access - -## Quick Start - -### Context - Component Context - -```rust -impl MyComponent { - fn update_state(&mut self, cx: &mut Context) { - self.value = 42; - cx.notify(); // Trigger re-render - - // Spawn async task - cx.spawn(async move |cx| { - // Async work - }).detach(); - - // Get current entity - let entity = cx.entity(); - } -} -``` - -### App - Global Context - -```rust -fn main() { - let app = Application::new(); - app.run(|cx: &mut App| { - // Create entities - let entity = cx.new(|cx| MyState::default()); - - // Open windows - cx.open_window(WindowOptions::default(), |window, cx| { - cx.new(|cx| Root::new(view, window, cx)) - }); - }); -} -``` - -### Window - Window Context - -```rust -impl Render for MyView { - fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { - // Window operations - let is_focused = window.is_window_focused(); - let bounds = window.bounds(); - - div().child("Content") - } -} -``` - -### AsyncApp - Async Context - -```rust -cx.spawn(async move |cx: &mut AsyncApp| { - let data = fetch_data().await; - - entity.update(cx, |state, inner_cx| { - state.data = data; - inner_cx.notify(); - }).ok(); -}).detach(); -``` - -## Common Operations - -### Entity Operations - -```rust -// Create entity -let entity = cx.new(|cx| MyState::default()); - -// Update entity -entity.update(cx, |state, cx| { - state.value = 42; - cx.notify(); -}); - -// Read entity -let value = entity.read(cx).value; -``` - -### Notifications and Events - -```rust -// Trigger re-render -cx.notify(); - -// Emit event -cx.emit(MyEvent::Updated); - -// Observe entity -cx.observe(&entity, |this, observed, cx| { - // React to changes -}).detach(); - -// Subscribe to events -cx.subscribe(&entity, |this, source, event, cx| { - // Handle event -}).detach(); -``` - -### Window Operations - -```rust -// Window state -let focused = window.is_window_focused(); -let bounds = window.bounds(); -let scale = window.scale_factor(); - -// Close window -window.remove_window(); -``` - -### Async Operations - -```rust -// Spawn foreground task -cx.spawn(async move |cx| { - // Async work with entity access -}).detach(); - -// Spawn background task -cx.background_spawn(async move { - // Heavy computation -}).detach(); -``` - -## Context Hierarchy - -``` -App (Global) - └─ Window (Per-window) - └─ Context (Per-component) - └─ AsyncApp (In async tasks) - └─ AsyncWindowContext (Async + Window) -``` - -## Reference Documentation - -- **API Reference**: See [api-reference.md](references/api-reference.md) - - Complete context API, methods, conversions - - Entity operations, window operations - - Async contexts, best practices diff --git a/.claude/skills/gpui-style-guide/SKILL.md b/.claude/skills/gpui-style-guide/SKILL.md deleted file mode 100644 index 6f34777934..0000000000 --- a/.claude/skills/gpui-style-guide/SKILL.md +++ /dev/null @@ -1,555 +0,0 @@ ---- -name: gpui-style-guide -description: GPUI Component project style guide based on gpui-component code patterns. Use when writing new components, reviewing code, or ensuring consistency with existing gpui-component implementations. Covers component structure, trait implementations, naming conventions, and API patterns observed in the actual codebase. ---- - -## Overview - -Code style guide derived from gpui-component implementation patterns. - -**Based on**: Analysis of Button, Checkbox, Input, Select, and other components in crates/ui - -## Component Structure - -### Basic Component Pattern - -```rust -use gpui::{ - div, prelude::FluentBuilder as _, AnyElement, App, Div, ElementId, - InteractiveElement, IntoElement, ParentElement, RenderOnce, - StatefulInteractiveElement, StyleRefinement, Styled, Window, -}; - -#[derive(IntoElement)] -pub struct MyComponent { - id: ElementId, - base: Div, - style: StyleRefinement, - - // Configuration fields - size: Size, - disabled: bool, - selected: bool, - - // Content fields - label: Option, - children: Vec, - - // Callbacks (use Rc for Clone) - on_click: Option>, -} - -impl MyComponent { - pub fn new(id: impl Into) -> Self { - Self { - id: id.into(), - base: div(), - style: StyleRefinement::default(), - size: Size::default(), - disabled: false, - selected: false, - label: None, - children: Vec::new(), - on_click: None, - } - } - - // Builder methods - pub fn label(mut self, label: impl Into) -> Self { - self.label = Some(label.into()); - self - } - - pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self { - self.on_click = Some(Rc::new(handler)); - self - } -} - -impl InteractiveElement for MyComponent { - fn interactivity(&mut self) -> &mut gpui::Interactivity { - self.base.interactivity() - } -} - -impl StatefulInteractiveElement for MyComponent {} - -impl Styled for MyComponent { - fn style(&mut self) -> &mut StyleRefinement { - &mut self.style - } -} - -impl RenderOnce for MyComponent { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - // Implementation - self.base - } -} -``` - -### Stateful Component Pattern - -```rust -#[derive(IntoElement)] -pub struct Select { - state: Entity, - style: StyleRefinement, - size: Size, - // ... -} - -pub struct SelectState { - open: bool, - selected_index: Option, - // ... -} - -impl Select { - pub fn new(state: &Entity) -> Self { - Self { - state: state.clone(), - size: Size::default(), - style: StyleRefinement::default(), - } - } -} -``` - -## Trait Implementations - -### Sizable - -```rust -impl Sizable for MyComponent { - fn with_size(mut self, size: impl Into) -> Self { - self.size = size.into(); - self - } -} -``` - -### Selectable - -```rust -impl Selectable for MyComponent { - fn selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - fn is_selected(&self) -> bool { - self.selected - } -} -``` - -### Disableable - -```rust -impl Disableable for MyComponent { - fn disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } - - fn is_disabled(&self) -> bool { - self.disabled - } -} -``` - -## Variant Patterns - -### Enum Variants - -```rust -#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] -pub enum ButtonVariant { - Primary, - #[default] - Secondary, - Danger, - Success, - Warning, - Ghost, - Link, -} -``` - -### Trait-Based Variant API - -```rust -pub trait ButtonVariants: Sized { - fn with_variant(self, variant: ButtonVariant) -> Self; - - /// With the primary style for the Button. - fn primary(self) -> Self { - self.with_variant(ButtonVariant::Primary) - } - - /// With the danger style for the Button. - fn danger(self) -> Self { - self.with_variant(ButtonVariant::Danger) - } - - // ... more variants -} -``` - -### Custom Variant Pattern - -```rust -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct ButtonCustomVariant { - color: Hsla, - foreground: Hsla, - border: Hsla, - hover: Hsla, - active: Hsla, - shadow: bool, -} - -impl ButtonCustomVariant { - pub fn new(cx: &App) -> Self { - Self { - color: cx.theme().transparent, - foreground: cx.theme().foreground, - // ... - shadow: false, - } - } - - pub fn color(mut self, color: Hsla) -> Self { - self.color = color; - self - } - - // ... more builder methods -} -``` - -## Action and Keybinding Patterns - -### Context Constant - -```rust -const CONTEXT: &str = "Select"; -``` - -### Init Function - -```rust -pub(crate) fn init(cx: &mut App) { - cx.bind_keys([ - KeyBinding::new("up", SelectUp, Some(CONTEXT)), - KeyBinding::new("down", SelectDown, Some(CONTEXT)), - KeyBinding::new("enter", Confirm { secondary: false }, Some(CONTEXT)), - KeyBinding::new("escape", Cancel, Some(CONTEXT)), - ]) -} -``` - -### Action Usage - -```rust -use crate::actions::{Cancel, Confirm, SelectDown, SelectUp}; - -div() - .key_context(CONTEXT) - .on_action(cx.listener(Self::on_action_select_up)) - .on_action(cx.listener(Self::on_action_confirm)) -``` - -## Trait Definitions - -### Item Traits - -```rust -pub trait SelectItem: Clone { - type Value: Clone; - - fn title(&self) -> SharedString; - - fn display_title(&self) -> Option { - None - } - - fn render(&self, _: &mut Window, _: &mut App) -> impl IntoElement { - self.title().into_element() - } - - fn value(&self) -> &Self::Value; - - fn matches(&self, query: &str) -> bool { - self.title().to_lowercase().contains(&query.to_lowercase()) - } -} -``` - -### Implement for Common Types - -```rust -impl SelectItem for String { - type Value = Self; - - fn title(&self) -> SharedString { - SharedString::from(self.to_string()) - } - - fn value(&self) -> &Self::Value { - &self - } -} - -impl SelectItem for SharedString { /* ... */ } -impl SelectItem for &'static str { /* ... */ } -``` - -## Icon Pattern - -### IconNamed Trait - -```rust -pub trait IconNamed { - fn path(self) -> SharedString; -} - -impl From for Icon { - fn from(value: T) -> Self { - Icon::build(value) - } -} -``` - -### IconName Enum - -```rust -#[derive(IntoElement, Clone)] -pub enum IconName { - ArrowDown, - ArrowUp, - Check, - Close, - // ... all icon names -} -``` - -## Documentation Patterns - -### Component Documentation - -```rust -/// A Checkbox element. -#[derive(IntoElement)] -pub struct Checkbox { } -``` - -### Method Documentation - -```rust -impl Checkbox { - /// Create a new Checkbox with the given id. - pub fn new(id: impl Into) -> Self { } - - /// Set the label for the checkbox. - pub fn label(mut self, label: impl Into) -> Self { } - - /// Set the click handler for the checkbox. - /// - /// The `&bool` parameter indicates the new checked state after the click. - pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { } -} -``` - -## Import Organization Pattern - -```rust -// 1. External crate imports -use std::rc::Rc; - -// 2. Crate imports -use crate::{ - ActiveTheme, Disableable, FocusableExt, Icon, IconName, - Selectable, Sizable, Size, StyledExt, -}; - -// 3. GPUI imports -use gpui::{ - div, prelude::FluentBuilder as _, px, relative, rems, - AnyElement, App, Div, ElementId, InteractiveElement, - IntoElement, ParentElement, RenderOnce, - StatefulInteractiveElement, StyleRefinement, Styled, Window, -}; -``` - -## Field Organization - -```rust -pub struct Component { - // 1. Identity - id: ElementId, - base: Div, - style: StyleRefinement, - - // 2. Configuration - size: Size, - disabled: bool, - selected: bool, - tab_stop: bool, - tab_index: isize, - - // 3. Content/children - label: Option, - children: Vec, - prefix: Option, - suffix: Option, - - // 4. Callbacks (last) - on_click: Option>, -} -``` - -## Common Patterns - -### Optional Elements - -```rust -pub fn prefix(mut self, prefix: impl IntoElement) -> Self { - self.prefix = Some(prefix.into_any_element()); - self -} -``` - -### Callback Patterns - -```rust -// Pattern 1: Event parameter first -pub fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self { - self.on_click = Some(Rc::new(handler)); - self -} - -// Pattern 2: State parameter -pub fn on_change(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { - self.on_change = Some(Rc::new(handler)); - self -} -``` - -### Static Handler Functions - -```rust -fn handle_click( - on_click: &Option>, - checked: bool, - window: &mut Window, - cx: &mut App, -) { - let new_checked = !checked; - if let Some(f) = on_click { - (f)(&new_checked, window, cx); - } -} -``` - -### Boolean Methods - -```rust -// Enable/disable patterns -pub fn cleanable(mut self, cleanable: bool) -> Self { - self.cleanable = cleanable; - self -} - -// Toggle methods (no parameter) -pub fn mask_toggle(mut self) -> Self { - self.mask_toggle = true; - self -} -``` - -## Size Methods - -### Size Trait - -```rust -impl Sizable for Component { - fn with_size(mut self, size: impl Into) -> Self { - self.size = size.into(); - self - } -} -``` - -### Convenience Size Methods (from StyleSized trait) - -Components get `.xsmall()`, `.small()`, `.medium()`, `.large()` automatically via `StyleSized` trait. - -## Rendering Patterns - -### RenderOnce Pattern - -```rust -impl RenderOnce for MyComponent { - fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let (width, height) = self.size.input_size(); - - self.base - .id(self.id) - .flex() - .items_center() - .gap(px(8.)) - .min_w(width) - .h(height) - .when(self.disabled, |this| { - this.opacity(0.5).cursor_not_allowed() - }) - .children(self.children) - } -} -``` - -## Theme Usage - -```rust -// Access theme colors -cx.theme().surface -cx.theme().foreground -cx.theme().border -cx.theme().primary -cx.theme().transparent - -// Use in components -div() - .bg(cx.theme().surface) - .text_color(cx.theme().foreground) - .border_color(cx.theme().border) -``` - -## Reference Documentation - -- **Component Examples**: See [component-examples.md](references/component-examples.md) - - Full component implementations - - Common patterns in action - -- **Trait Patterns**: See [trait-patterns.md](references/trait-patterns.md) - - Detailed trait implementation guides - - Custom trait design patterns - -## Quick Checklist - -When creating a new component in crates/ui: - -- [ ] `#[derive(IntoElement)]` on struct -- [ ] Include `id: ElementId`, `base: Div`, `style: StyleRefinement` -- [ ] Implement `InteractiveElement`, `StatefulInteractiveElement`, `Styled` -- [ ] Implement `RenderOnce` trait -- [ ] Implement `Sizable` if component has sizes -- [ ] Implement `Selectable` if component can be selected -- [ ] Implement `Disableable` if component can be disabled -- [ ] Use `Rc` for callbacks -- [ ] Use `Option` for optional child elements -- [ ] Import `prelude::FluentBuilder as _` -- [ ] Use theme colors via `cx.theme()` -- [ ] Follow field organization pattern diff --git a/CLAUDE.md b/CLAUDE.md index cbc6b06e68..d5cfa3d4ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -205,30 +205,11 @@ CI runs full test suite on each platform. ## Skills Reference -This project has custom Claude Code skills in `.claude/skills/` to assist with common development tasks: +This project has custom Claude Code skills to assist with common development tasks: -### Component Development Skills - -- **new-component** - Creating new GPUI components with proper structure and patterns -- **generate-component-story** - Creating story examples for components in the gallery -- **generate-component-documentation** - Generating documentation for components - -### GPUI Framework Skills - -- **gpui-action** - Working with actions and keyboard shortcuts -- **gpui-async** - Async operations and background tasks -- **gpui-context** - Context management (App, Window, AsyncApp) -- **gpui-element** - Implementing custom elements using low-level Element API -- **gpui-entity** - Entity management and state handling -- **gpui-event** - Event handling and subscriptions -- **gpui-focus-handle** - Focus management and keyboard navigation -- **gpui-global** - Global state management -- **gpui-layout-and-style** - Layout and styling systems -- **gpui-test** - Writing tests for GPUI applications - -### Other Skills - -- **github-pull-request-description** - Writing PR descriptions +- **gpui** (`skills/`) - GPUI framework knowledge: actions/keybindings, async, context, custom elements, entity state, events, focus, global state, layout/styling, testing +- **gpui-component** (`skills/`) - How to use gpui-component: setup, stateless/stateful patterns, common component APIs, theming +- **gpui-component-dev** (`.claude/skills/`) - Contributing to gpui-component: creating new components, writing stories, writing documentation, writing PR descriptions When working on tasks related to these areas, Claude Code will automatically use the appropriate skill to provide specialized guidance and patterns. diff --git a/docs/skills.vue b/docs/skills.vue index 89fbd04247..cee55ca366 100644 --- a/docs/skills.vue +++ b/docs/skills.vue @@ -5,7 +5,7 @@
diff --git a/skills/gpui-component/SKILL.md b/skills/gpui-component/SKILL.md new file mode 100644 index 0000000000..62b2df2d65 --- /dev/null +++ b/skills/gpui-component/SKILL.md @@ -0,0 +1,124 @@ +--- +name: gpui-component +description: How to use the gpui-component UI library in GPUI applications. Use when building UIs with gpui-component components (Button, Input, Select, Dialog, Tabs, Sidebar, List, Table, etc.), setting up the library, handling component state, theming, or finding the right component for a given UI need. +--- + +## Documentation + +- **Full reference**: fetch `https://longbridge.github.io/gpui-component/llms-full.txt` +- **Per-component API**: fetch `https://longbridge.github.io/gpui-component/docs/components/{name}.md` + - e.g. `button.md`, `input.md`, `select.md`, `dialog.md`, `data-table.md` +- **Any site page** can be fetched as Markdown by appending `.md` to the URL + +## Quick Reference + +**Setup** — always required: +```rust +gpui_component::init(cx); // in app.run(), must be first +Root::new(view, window, cx) // first-level view in every window +``` + +**Stateless** — use directly in render: +```rust +Button::new("id").primary().label("OK").on_click(|_, _, _| {}) +``` + +**Stateful** — hold `Entity` in struct, pass ref in render: +```rust +// in new(): let input = cx.new(|cx| InputState::new(window, cx)); +// in render: Input::new(&self.input) +``` + +**Sizes**: `.xsmall()` `.small()` `.medium()` (default) `.large()` + +**Theme**: `cx.theme().primary` · `.background` · `.foreground` · `.border` · `.muted` + +## Component Catalog + +When you need a component, find it here. For full API, fetch its `.md` doc. + +### Input & Form +| Component | Import | Notes | +|-----------|--------|-------| +| `Input` | `input::{Input, InputState}` | Stateful. Text, password, mask, validation | +| `NumberInput` | `number_input::{NumberInput, NumberInputState}` | Stateful. Numeric with step | +| `OtpInput` | `otp_input::{OtpInput, OtpInputState}` | Stateful. One-time password | +| `Select` | `select::{Select, SelectState}` | Stateful. Dropdown picker | +| `Combobox` | `combobox::{Combobox, ComboboxState}` | Stateful. Searchable select | +| `Checkbox` | `checkbox::Checkbox` | Stateless. `on_click(|&bool, ...|)` | +| `Switch` | `switch::Switch` | Stateless. Toggle | +| `Radio` | `radio::{Radio, RadioGroup}` | Stateless. | +| `Slider` | `slider::{Slider, SliderState}` | Stateful. | +| `Toggle` | `toggle::Toggle` | Stateless. | +| `Rating` | `rating::Rating` | Stateless. | +| `Stepper` | `stepper::Stepper` | Stateless. Increment/decrement | +| `ColorPicker` | `color_picker::{ColorPicker, ColorPickerState}` | Stateful. | +| `DatePicker` | `time::date_picker::{DatePicker, DatePickerState}` | Stateful. | +| `Form` | `form::{v_form, h_form, field}` | Layout container for form fields | + +### Display & Feedback +| Component | Import | Notes | +|-----------|--------|-------| +| `Button` | `button::{Button, ButtonGroup}` | Stateless. Primary UI action | +| `Icon` | `{Icon, IconName}` | Stateless. Lucide icons | +| `Badge` | `badge::Badge` | Stateless. | +| `Tag` | `tag::Tag` | Stateless. Closable tags | +| `Avatar` | `avatar::Avatar` | Stateless. | +| `Label` | `label::Label` | Stateless. Form label | +| `Kbd` | `kbd::Kbd` | Stateless. Keyboard key display | +| `Alert` | `alert::Alert` | Stateless. Info/success/warning/error | +| `Spinner` | `spinner::Spinner` | Stateless. Loading indicator | +| `Skeleton` | `skeleton::Skeleton` | Stateless. Loading placeholder | +| `Progress` | `progress::{ProgressBar, ProgressCircle}` | Stateless. | +| `Tooltip` | `tooltip::Tooltip` | Via `.tooltip()` on elements | +| `HoverCard` | `hover_card::{HoverCard, HoverCardState}` | Stateful. | +| `Image` | `image::Image` | Stateless. | +| `Clipboard` | `clipboard::Clipboard` | Stateless. Copy button | + +### Overlay & Popups +| Component | Import | Notes | +|-----------|--------|-------| +| `Dialog` | `dialog::Dialog` + `WindowExt` | Via `window.open_modal(...)` | +| `AlertDialog` | `WindowExt` | Via `window.open_alert_dialog(...)` | +| `Sheet` | `sheet::Sheet` + `WindowExt` | Side panel, via `window.open_sheet(...)` | +| `Notification` | `notification::Notification` + `WindowExt` | Via `window.push_notification(...)` | +| `Popover` | `popover::Popover` | Floating overlay | +| `Menu` | `menu::{PopupMenu, DropdownMenu}` | Context menus | +| `DropdownButton` | `button::DropdownButton` | Button with dropdown menu | + +### Navigation & Layout +| Component | Import | Notes | +|-----------|--------|-------| +| `Tabs` / `TabBar` | `tab::{Tab, TabBar}` | Tabbed interface | +| `Sidebar` | `sidebar::{Sidebar, SidebarMenu, ...}` | App navigation panel | +| `TitleBar` | `title_bar::TitleBar` | Window title bar | +| `Breadcrumb` | `breadcrumb::Breadcrumb` | Navigation breadcrumb | +| `Pagination` | `pagination::Pagination` | Page navigation | +| `Accordion` | `accordion::Accordion` | Collapsible sections | +| `Collapsible` | `collapsible::Collapsible` | Single collapsible | +| `GroupBox` | `group_box::GroupBox` | Labeled container | +| `Resizable` | `resizable::Resizable` | Draggable split panes | +| `Scrollable` | `scroll::Scrollbar` | Custom scrollbar | +| `FocusTrap` | `focus_trap::FocusTrap` | Keyboard trap for modals | + +### Data Display +| Component | Import | Notes | +|-----------|--------|-------| +| `DataTable` | `table::{DataTable, TableState, TableDelegate}` | Stateful. Full-featured table | +| `Table` | `table::{Table, ...}` | Simpler table | +| `VirtualList` | `{v_virtual_list, h_virtual_list}` | High-perf large lists | +| `List` | `list::{List, ListState, ListDelegate}` | Stateful. Searchable list | +| `Tree` | `tree::{Tree, TreeState, TreeDelegate}` | Stateful. Hierarchy | +| `DescriptionList` | `description_list::DescriptionList` | Key-value pairs | +| `Settings` | `settings::Settings` | Settings panel | + +### Charts +| Component | Import | Notes | +|-----------|--------|-------| +| `Chart` | `chart::Chart` | Bar, line, area, pie charts | +| `Plot` | `plot::Plot` | `#[derive(IntoPlot)]` for data | + +## Reference Files + +- [usage.md](references/usage.md) — setup patterns, component types, common examples +- [style-guide.md](references/style-guide.md) — code style for contributors diff --git a/skills/gpui-component/references/style-guide.md b/skills/gpui-component/references/style-guide.md new file mode 100644 index 0000000000..40b797c7a5 --- /dev/null +++ b/skills/gpui-component/references/style-guide.md @@ -0,0 +1,364 @@ +# GPUI Component Code Style Guide + +Based on analysis of `Button`, `Checkbox`, `Input`, `Select`, and other components in `crates/ui/src`. + +**Contents:** [Component Structure](#component-structure) · [Required Traits](#required-trait-implementations) · [Optional Traits](#optional-traits) · [Variants Pattern](#variants-pattern) · [Callback Signatures](#callback-signatures) · [Import Organization](#import-organization) · [Doc Comments](#doc-comments) · [Applying User Style Overrides](#applying-user-style-overrides) · [FluentBuilder Conditionals](#fluentbuilder-for-conditionals) · [Theme Colors](#theme-colors) · [Size Handling](#size-handling) · [Checklist](#checklist-for-new-components) + +## Component Structure + +### Standard Stateless Component + +```rust +use std::rc::Rc; + +use crate::{ActiveTheme, Disableable, Sizable, Size, StyledExt as _, /* ... */}; +use gpui::{ + AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, + ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, + StyleRefinement, Styled, Window, div, prelude::FluentBuilder as _, +}; + +/// A MyComponent element. +#[derive(IntoElement)] +pub struct MyComponent { + // 1. Identity + id: ElementId, + base: Div, + style: StyleRefinement, + + // 2. Configuration + size: Size, + disabled: bool, + selected: bool, + tab_stop: bool, + tab_index: isize, + + // 3. Content + label: Option, + children: Vec, + + // 4. Callbacks (last) + on_click: Option>, +} + +impl MyComponent { + /// Create a new MyComponent with the given id. + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + base: div(), + style: StyleRefinement::default(), + size: Size::default(), + disabled: false, + selected: false, + tab_stop: true, + tab_index: 0, + label: None, + children: Vec::new(), + on_click: None, + } + } + + /// Set the label. + pub fn label(mut self, label: impl Into) -> Self { + self.label = Some(label.into()); + self + } + + /// Set the click handler. + pub fn on_click(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { + self.on_click = Some(Rc::new(handler)); + self + } +} +``` + +### Stateful Component (Interactive, Needs `.id()`) + +Components with mouse interactions (hover, click tracking) use `Stateful
`: + +```rust +use gpui::{Stateful, StatefulInteractiveElement as _, /* ... */}; + +#[derive(IntoElement)] +pub struct Button { + id: ElementId, + base: Stateful
, // Not Div — needs stateful for interaction tracking + // ... +} + +impl Button { + pub fn new(id: impl Into) -> Self { + let id = id.into(); + Self { + id: id.clone(), + base: div().flex_shrink_0().id(id), // .id() makes it Stateful
+ // ... + } + } +} + +impl InteractiveElement for Button { + fn interactivity(&mut self) -> &mut Interactivity { + self.base.interactivity() + } +} +``` + +--- + +## Required Trait Implementations + +```rust +// All components that accept children +impl ParentElement for MyComponent { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements) + } +} + +// All components with styleable outer div +impl Styled for MyComponent { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.style + } +} + +// For interactive components (mouse events, hover, click) +impl InteractiveElement for MyComponent { + fn interactivity(&mut self) -> &mut Interactivity { + self.base.interactivity() + } +} + +// Required if InteractiveElement is implemented +impl StatefulInteractiveElement for MyComponent {} + +// Rendering +impl RenderOnce for MyComponent { + fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { + self.base + .id(self.id) + // Apply user style overrides last + .refine_style(&self.style) + .children(self.children) + } +} +``` + +--- + +## Optional Traits + +```rust +impl Disableable for MyComponent { + fn disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +impl Selectable for MyComponent { + fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + fn is_selected(&self) -> bool { + self.selected + } +} + +impl Sizable for MyComponent { + fn with_size(mut self, size: impl Into) -> Self { + self.size = size.into(); + self + } +} +``` + +Implementing `Sizable` gives `.xsmall()`, `.small()`, `.medium()`, `.large()` for free via `StyleSized`. + +--- + +## Variants Pattern + +Use a `Variants` trait with default method impls: + +```rust +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +pub enum AlertVariant { + #[default] + Info, + Success, + Warning, + Error, +} + +pub trait AlertVariants: Sized { + fn with_variant(self, variant: AlertVariant) -> Self; + + fn info(self) -> Self { self.with_variant(AlertVariant::Info) } + fn success(self) -> Self { self.with_variant(AlertVariant::Success) } + fn warning(self) -> Self { self.with_variant(AlertVariant::Warning) } + fn error(self) -> Self { self.with_variant(AlertVariant::Error) } +} + +impl AlertVariants for MyAlert { + fn with_variant(mut self, variant: AlertVariant) -> Self { + self.variant = variant; + self + } +} +``` + +--- + +## Callback Signatures + +```rust +// Click event (ClickEvent first) +on_click: Option> + +// State change (state value first) +on_change: Option> +on_change: Option> +on_change: Option> +``` + +Always `Rc` — components are cloned and called multiple times. + +--- + +## Import Organization + +```rust +// 1. std +use std::rc::Rc; + +// 2. crate imports (project internals) +use crate::{ + ActiveTheme, Disableable, Icon, IconName, + Selectable, Sizable, Size, StyledExt as _, + h_flex, v_flex, +}; + +// 3. gpui imports +use gpui::{ + AnyElement, App, Div, ElementId, InteractiveElement, IntoElement, + ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, + StyleRefinement, Styled, Window, div, + prelude::FluentBuilder as _, + px, rems, relative, +}; +``` + +--- + +## Doc Comments + +```rust +/// A Checkbox element. ← struct: one-line with capital, period +#[derive(IntoElement)] +pub struct Checkbox { ... } + +impl Checkbox { + /// Create a new Checkbox with the given id. ← constructor + pub fn new(id: impl Into) -> Self { ... } + + /// Set the label for the checkbox. ← setter + pub fn label(mut self, label: impl Into) -> Self { ... } + + /// Set the click handler for the checkbox. + /// + /// The `&bool` parameter indicates the new checked state after the click. + pub fn on_click(mut self, ...) -> Self { ... } +} +``` + +- Struct doc: `/// A {Name} element.` +- Constructor: `/// Create a new {Name} with the given id.` +- Setters: `/// Set the {field}.` +- No redundant comments — only document non-obvious behavior + +--- + +## Applying User Style Overrides + +Use `refine_style` to merge user's `Styled` calls onto the root element: + +```rust +impl RenderOnce for MyComponent { + fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement { + div() + .flex() + .items_center() + // Apply component defaults first, then user overrides + .refine_style(&self.style) + .children(self.children) + } +} +``` + +--- + +## FluentBuilder for Conditionals + +```rust +div() + .when(self.disabled, |this| this.opacity(0.5).cursor_not_allowed()) + .when(self.selected, |this| this.bg(cx.theme().primary)) + .when_some(self.label.as_ref(), |this, label| { + this.child(div().child(label.clone())) + }) +``` + +Always `use gpui::prelude::FluentBuilder as _;` for `.when()` / `.when_some()`. + +--- + +## Theme Colors + +```rust +// In render, access via cx.theme() (requires ActiveTheme import) +use crate::ActiveTheme; + +div() + .bg(cx.theme().surface) + .text_color(cx.theme().foreground) + .border_color(cx.theme().border) + .when(is_active, |el| el.bg(cx.theme().primary)) +``` + +--- + +## Size Handling + +```rust +// Get pixel values based on Size +let (width, height) = self.size.input_size(); + +// Or use match +let font_size = match self.size { + Size::XSmall => rems(0.75), + Size::Small => rems(0.875), + Size::Medium | Size::Size(_) => rems(1.0), + Size::Large => rems(1.125), +}; +``` + +--- + +## Checklist for New Components + +- [ ] `#[derive(IntoElement)]` +- [ ] Fields: `id: ElementId`, `base: Div` (or `Stateful
`), `style: StyleRefinement` +- [ ] `impl RenderOnce` — calls `.refine_style(&self.style)` on root element +- [ ] `impl Styled` returning `&mut self.style` +- [ ] `impl ParentElement` if accepts children +- [ ] `impl InteractiveElement` + `StatefulInteractiveElement` if interactive +- [ ] `impl Sizable` if has size variants +- [ ] `impl Disableable` if can be disabled +- [ ] `impl Selectable` if can be selected +- [ ] Callbacks as `Option>` +- [ ] Doc comment on struct and public methods +- [ ] Import `prelude::FluentBuilder as _` diff --git a/skills/gpui-component/references/usage.md b/skills/gpui-component/references/usage.md new file mode 100644 index 0000000000..b13c6f9d61 --- /dev/null +++ b/skills/gpui-component/references/usage.md @@ -0,0 +1,372 @@ +# gpui-component Usage Guide + +**Contents:** [Setup](#setup) · [Component Types](#component-types) · [Common Components](#common-components) (Button, Input, Select, Checkbox, Icon, Dialog, Notification, Tabs, Tooltip, Form, List) · [Theming](#theming) · [Layout Helpers](#layout-helpers) · [Overlay Layers](#overlay-layers-dialogs-sheets-notifications) · [Shared Traits](#shared-traits) + +## Setup + +### 1. Cargo.toml + +```toml +[dependencies] +gpui = { git = "https://github.com/zed-industries/zed" } +gpui_platform = { git = "https://github.com/zed-industries/zed", features = ["font-kit"] } +gpui-component = { git = "https://github.com/longbridge/gpui-component" } +gpui-component-assets = { git = "https://github.com/longbridge/gpui-component" } # optional icons +``` + +### 2. Initialization + +```rust +fn main() { + gpui_platform::application() + .with_assets(gpui_component_assets::Assets) + .run(move |cx| { + gpui_component::init(cx); // MUST be first + + cx.spawn(async move |cx| { + cx.open_window(WindowOptions::default(), |window, cx| { + let view = cx.new(|_| MyApp); + cx.new(|cx| Root::new(view, window, cx)) // Root wraps first view + }).expect("Failed to open window"); + }).detach(); + }); +} +``` + +**`Root` is required** as the first-level child of every window — it enables dialogs, sheets, and notifications. + +--- + +## Component Types + +### Stateless (most components) + +Used directly in `render`, no stored state: + +```rust +use gpui_component::button::Button; + +impl Render for MyView { + fn render(&mut self, _: &mut Window, _: &mut Context) -> impl IntoElement { + Button::new("btn").primary().label("Submit") + .on_click(|_, _, _| println!("clicked")) + } +} +``` + +### Stateful (Input, Select, Combobox, etc.) + +Require an `Entity` stored in your view: + +```rust +use gpui_component::input::{Input, InputState}; + +struct MyView { + name: Entity, +} + +impl MyView { + fn new(window: &mut Window, cx: &mut Context) -> Self { + Self { + name: cx.new(|cx| InputState::new(window, cx).placeholder("Your name")), + } + } +} + +impl Render for MyView { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + Input::new(&self.name) + } +} +``` + +--- + +## Common Components + +### Button + +```rust +use gpui_component::button::{Button, ButtonGroup}; + +// Variants +Button::new("btn").label("Default") +Button::new("btn").primary().label("Primary") +Button::new("btn").danger().label("Delete") +Button::new("btn").warning().label("Warning") +Button::new("btn").success().label("Success") +Button::new("btn").ghost().label("Ghost") +Button::new("btn").link().label("Link") + +// States +Button::new("btn").label("Text").disabled(true) +Button::new("btn").label("Text").loading(true) +Button::new("btn").label("Text").selected(true) + +// With icon +Button::new("btn").icon(IconName::Plus).label("Add") + +// Sizes +Button::new("btn").xsmall().label("XS") +Button::new("btn").small().label("S") +Button::new("btn").large().label("L") + +// Group +ButtonGroup::new("group") + .child(Button::new("a").label("A")) + .child(Button::new("b").label("B")) + .on_click(|indices, _, _| { /* selected indices */ }) +``` + +### Input + +```rust +use gpui_component::input::{Input, InputState}; + +// State setup (in new/init) +let input = cx.new(|cx| InputState::new(window, cx) + .placeholder("Enter text...") + .default_value("Hello") +); + +// Render +Input::new(&input) +Input::new(&input).cleanable(true) // clear button +Input::new(&input).disabled(true) +Input::new(&input).prefix(Icon::new(IconName::Search).small()) +Input::new(&input).suffix(Button::new("b").ghost().icon(IconName::X).xsmall()) +Input::new(&input).mask_toggle() // password reveal toggle +Input::new(&input).appearance(false) // remove default border/bg + +// Reading value +let value = input.read(cx).value(); + +// Events +cx.subscribe_in(&input, window, |view, state, event, window, cx| { + match event { + InputEvent::Change => { let v = state.read(cx).value(); } + InputEvent::PressEnter { .. } => { /* submit */ } + InputEvent::Focus | InputEvent::Blur => {} + } +}); +``` + +### Select + +```rust +use gpui_component::select::{Select, SelectState}; + +// Simple string list +let state = cx.new(|cx| { + SelectState::new(vec!["Apple", "Orange", "Banana"], Some(IndexPath::default()), window, cx) +}); + +// Render +Select::new(&state) +Select::new(&state).placeholder("Pick one") + +// Reading selection +let selected = state.read(cx).selected_item(); +``` + +### Checkbox / Switch / Radio + +```rust +use gpui_component::{Checkbox, Switch}; + +// Stateless (controlled) +Checkbox::new("cb").checked(self.checked) + .on_click(|checked, _, cx| { /* &bool */ }) + +Switch::new("sw").checked(self.enabled) + .on_click(|checked, _, cx| {}) +``` + +### Icon + +```rust +use gpui_component::{Icon, IconName}; + +Icon::new(IconName::Check) +Icon::new(IconName::Search).small() +Icon::new(IconName::Plus).large().text_color(cx.theme().primary) +``` + +### Dialog + +```rust +use gpui_component::dialog::Dialog; + +// Open from window context +window.open_modal(cx, |modal, _, cx| { + modal + .title("Confirm") + .child(div().child("Are you sure?")) + .footer(|this, _, cx| { + this.child(Button::new("cancel").label("Cancel")) + .child(Button::new("ok").primary().label("OK") + .on_click(|_, window, cx| { window.close_modal(cx); })) + }) +}); +``` + +### Notification + +```rust +// Simple string message +window.push_notification("Saved successfully!", cx); + +// With type variant +window.push_notification( + Notification::new("Upload complete").info().message("File uploaded"), + cx, +); +``` + +### Tabs + +```rust +use gpui_component::tab::{Tab, TabBar}; + +TabBar::new("tabs") + .child(Tab::new("tab1").child("Overview")) + .child(Tab::new("tab2").child("Settings")) + .child(Tab::new("tab3").child("Logs")) +``` + +### Tooltip + +```rust +// On any element with .id(), add .tooltip(): +div() + .id("my-btn") + .tooltip(|window, cx| Tooltip::new("Delete item").build(window, cx)) + .child("Delete") + +// Or on a Button directly: +Button::new("btn").icon(IconName::Trash).tooltip("Delete") +``` + +### Form + +```rust +use gpui_component::form::{v_form, h_form, field}; + +// Vertical form +v_form() + .child(field().label("Name").child(Input::new(&self.name))) + .child(field().label("Email").child(Input::new(&self.email))) + .child(Button::new("submit").primary().label("Submit")) + +// Horizontal label alignment +h_form() + .child(field().label("Username").child(Input::new(&self.username))) +``` + +### List (searchable, virtualized) + +```rust +use gpui_component::list::{List, ListState, ListDelegate, ListItem, ListEvent}; + +// Implement ListDelegate for your data type, then: +let list_state = cx.new(|cx| ListState::new(MyDelegate::new(), window, cx)); + +// Render +List::new(&list_state) +// Events +cx.subscribe(&list_state, |this, _, event, cx| { + if let ListEvent::Select(index_path) = event { + // handle selection + } +}); +``` + +--- + +## Theming + +```rust +use gpui_component::ActiveTheme as _; + +// Access colors +cx.theme().primary +cx.theme().background +cx.theme().foreground +cx.theme().border +cx.theme().surface +cx.theme().muted +cx.theme().destructive + +// Use in styles +div() + .bg(cx.theme().surface) + .text_color(cx.theme().foreground) + .border_color(cx.theme().border) +``` + +### Switch Theme + +```rust +use gpui_component::Theme; + +// Toggle light/dark +cx.update_global::(|theme, cx| { + theme.toggle_mode(cx); +}); + +// Load a named theme +Theme::global_mut(cx).apply_config(&theme_config); +``` + +--- + +## Layout Helpers + +gpui-component extends GPUI with convenient layout methods: + +```rust +h_flex() // div().flex().flex_row().items_center() +v_flex() // div().flex().flex_col() + +// Common patterns +h_flex().gap_2().items_center() + .child(Icon::new(IconName::User)) + .child(label("Username")) + +v_flex().gap_4().p_4() + .child(Input::new(&self.name)) + .child(Input::new(&self.email)) + .child(Button::new("submit").primary().label("Submit")) +``` + +--- + +## Overlay Layers (Dialogs, Sheets, Notifications) + +To render overlays, add these to your first-level view's render: + +```rust +impl Render for MyApp { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .size_full() + .child(self.main_content(window, cx)) + .children(Root::render_dialog_layer(cx)) + .children(Root::render_sheet_layer(cx)) + .children(Root::render_notification_layer(cx)) + } +} +``` + +--- + +## Shared Traits + +All components follow the builder pattern `Component::new("id").method().method()`: +- `Sizable`: `.xsmall()` / `.small()` / `.medium()` (default) / `.large()` +- `Disableable`: `.disabled(bool)` +- `Selectable`: `.selected(bool)` +- `Styled`: any GPUI style methods (`.w()`, `.bg()`, `.p_2()`, etc.) + +For any component not covered here, fetch its doc from: +`https://longbridge.github.io/gpui-component/docs/components/{name}.md` diff --git a/skills/gpui/SKILL.md b/skills/gpui/SKILL.md new file mode 100644 index 0000000000..5504e620f9 --- /dev/null +++ b/skills/gpui/SKILL.md @@ -0,0 +1,43 @@ +--- +name: gpui +description: GPUI framework knowledge covering actions/keybindings, async/background tasks, context management (App/Window/Context/AsyncApp), custom elements (low-level Element trait), entity state management, event system, focus handling, global state, layout/styling (flexbox/CSS-like), and testing. Use when working with any GPUI framework concept, building GPUI applications, or needing guidance on GPUI-specific APIs and patterns. +--- + +## Navigation + +Load the relevant reference file based on the task: + +| Topic | File | When to load | +|-------|------|--------------| +| Actions & keybindings | [action.md](references/action.md) | `actions!`, `bind_keys`, `on_action`, `key_context` | +| Async & background tasks | [async.md](references/async.md) | `cx.spawn`, `background_spawn`, `Task`, async I/O | +| Context management | [context.md](references/context.md) | `App`, `Window`, `Context`, `AsyncApp` | +| Custom elements (low-level) | [element.md](references/element.md) | `Element` trait, `request_layout`, `prepaint`, `paint` | +| Entity state | [entity.md](references/entity.md) | `Entity`, `WeakEntity`, state management | +| Events & subscriptions | [event.md](references/event.md) | `cx.emit`, `cx.subscribe`, `cx.observe` | +| Focus & keyboard nav | [focus-handle.md](references/focus-handle.md) | `FocusHandle`, `track_focus`, Tab navigation | +| Global state | [global.md](references/global.md) | `Global` trait, `cx.set_global`, app-wide config | +| Layout & styling | [layout-style.md](references/layout-style.md) | `div()`, `h_flex()`, `v_flex()`, flexbox, overflow, positioning | +| ElementId | [element-id.md](references/element-id.md) | `ElementId`, `.id()`, uniqueness rules, stateful elements | +| Testing | [test.md](references/test.md) | `#[gpui::test]`, `TestAppContext`, `VisualTestContext` | + +## Extended References + +For deep-dive topics, additional reference files are available: + +**Element trait:** +- [element-api.md](references/element-api.md) — complete API, hitbox system, event handling +- [element-patterns.md](references/element-patterns.md) — text, interactive, container, composite patterns +- [element-examples.md](references/element-examples.md) — full examples: text, interactive, complex elements +- [element-best-practices.md](references/element-best-practices.md) — performance, state, common pitfalls +- [element-advanced.md](references/element-advanced.md) — masonry/circular layouts, async updates, virtual lists + +**Entity management:** +- [entity-api.md](references/entity-api.md) — complete Entity API, methods, lifecycle +- [entity-patterns.md](references/entity-patterns.md) — model-view, cross-entity communication, observer +- [entity-best-practices.md](references/entity-best-practices.md) — memory, performance, lifecycle +- [entity-advanced.md](references/entity-advanced.md) — collections, registry, debounce, state machines + +**Testing:** +- [test-examples.md](references/test-examples.md) — testing examples and patterns +- [test-reference.md](references/test-reference.md) — complete testing API reference diff --git a/.claude/skills/gpui-action/SKILL.md b/skills/gpui/references/action.md similarity index 90% rename from .claude/skills/gpui-action/SKILL.md rename to skills/gpui/references/action.md index 4414bfcab4..fd61e240b0 100644 --- a/.claude/skills/gpui-action/SKILL.md +++ b/skills/gpui/references/action.md @@ -1,7 +1,6 @@ ---- -name: gpui-action -description: Action definitions and keyboard shortcuts in GPUI. Use when implementing actions, keyboard shortcuts, or key bindings. ---- +# Actions & Keybindings + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Key Formats](#key-formats) · [Action Naming](#action-naming) · [Context-Aware Bindings](#context-aware-bindings) · [Best Practices](#best-practices) ## Overview @@ -172,9 +171,3 @@ impl MyComponent { div().on_action(cx.listener(Self::on_action_save)) ``` -## Reference Documentation - -- **Complete Guide**: See [reference.md](references/reference.md) - - Action definition, keybinding, dispatch - - Focus-based routing, best practices - - Performance, accessibility diff --git a/.claude/skills/gpui-async/SKILL.md b/skills/gpui/references/async.md similarity index 74% rename from .claude/skills/gpui-async/SKILL.md rename to skills/gpui/references/async.md index d047bb46c6..c55bf9f8e6 100644 --- a/.claude/skills/gpui-async/SKILL.md +++ b/skills/gpui/references/async.md @@ -1,7 +1,6 @@ ---- -name: gpui-async -description: Async operations and background tasks in GPUI. Use when working with async, spawn, background tasks, or concurrent operations. Essential for handling async I/O, long-running computations, and coordinating between foreground UI updates and background work. ---- +# Async & Background Tasks + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Core Patterns](#core-patterns) · [Common Pitfalls](#common-pitfalls) ## Overview @@ -17,16 +16,16 @@ GPUI provides integrated async runtime for foreground UI updates and background ### Foreground Tasks (UI Updates) +When spawned from `Context`, the closure receives `(WeakEntity, &mut AsyncApp)`: + ```rust impl MyComponent { fn fetch_data(&mut self, cx: &mut Context) { - let entity = cx.entity().downgrade(); - - cx.spawn(async move |cx| { + cx.spawn(async move |this, cx: &mut AsyncApp| { // Runs on UI thread, can await and update entities let data = fetch_from_api().await; - entity.update(cx, |state, cx| { + this.update(cx, |state, cx| { state.data = Some(data); cx.notify(); }).ok(); @@ -35,6 +34,33 @@ impl MyComponent { } ``` +When spawned from `&mut App` (not inside an entity), the closure receives only `(cx: &mut AsyncApp)`: + +```rust +cx.spawn(async move |cx: &mut AsyncApp| { + // No entity reference +}).detach(); +``` + +### Spawn with Window Context (spawn_in) + +Use `spawn_in` when the task also needs window access (`update_in`): + +```rust +impl MyComponent { + fn animate(&mut self, window: &mut Window, cx: &mut Context) { + cx.spawn_in(window, async move |this, cx| { + // cx here is AsyncWindowContext + this.update_in(cx, |state, window, cx| { + // Can access window here + state.frame += 1; + cx.notify(); + }).ok(); + }).detach(); + } +} +``` + ### Background Tasks (Heavy Work) ```rust @@ -68,13 +94,11 @@ struct MyView { impl MyView { fn new(cx: &mut Context) -> Self { - let entity = cx.entity().downgrade(); - - let _task = cx.spawn(async move |cx| { + let _task = cx.spawn(async move |this, cx: &mut AsyncApp| { // Task automatically cancelled when dropped loop { tokio::time::sleep(Duration::from_secs(1)).await; - entity.update(cx, |state, cx| { + this.update(cx, |state, cx| { state.tick(); cx.notify(); }).ok(); @@ -88,12 +112,12 @@ impl MyView { ## Core Patterns -### 1. Async Data Fetching +### 1. Async Data Fetching (from Context) ```rust -cx.spawn(async move |cx| { +cx.spawn(async move |this, cx: &mut AsyncApp| { let data = fetch_data().await?; - entity.update(cx, |state, cx| { + this.update(cx, |state, cx| { state.data = Some(data); cx.notify(); })?; @@ -107,8 +131,8 @@ cx.spawn(async move |cx| { cx.background_spawn(async move { heavy_work() }) -.then(cx.spawn(move |result, cx| { - entity.update(cx, |state, cx| { +.then(cx.spawn(move |this, cx: &mut AsyncApp| { + this.update(cx, |state, cx| { state.result = result; cx.notify(); }).ok(); @@ -119,10 +143,13 @@ cx.background_spawn(async move { ### 3. Periodic Tasks ```rust -cx.spawn(async move |cx| { +cx.spawn(async move |this, cx: &mut AsyncApp| { loop { tokio::time::sleep(Duration::from_secs(5)).await; - // Update every 5 seconds + this.update(cx, |state, cx| { + state.tick(); + cx.notify(); + }).ok(); } }).detach(); ``` @@ -195,20 +222,3 @@ cx.background_spawn(async move { data }) .detach(); ``` -## Reference Documentation - -### Complete Guides - -- **API Reference**: See [api-reference.md](references/api-reference.md) - - Task types, spawning methods, contexts - - Executors, cancellation, error handling - -- **Patterns**: See [patterns.md](references/patterns.md) - - Data fetching, background processing - - Polling, debouncing, parallel tasks - - Pattern selection guide - -- **Best Practices**: See [best-practices.md](references/best-practices.md) - - Error handling, cancellation - - Performance optimization, testing - - Common pitfalls and solutions diff --git a/skills/gpui/references/context.md b/skills/gpui/references/context.md new file mode 100644 index 0000000000..37beefe74d --- /dev/null +++ b/skills/gpui/references/context.md @@ -0,0 +1,258 @@ +# Context Management + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Common Operations](#common-operations) · [Context Hierarchy](#context-hierarchy) · [cx.listener](#cxlistener--binding-callbacks-to-self) · [subscribe_in](#subscribe_in--subscribe-with-window-access) · [observe_window_activation](#observe_window_activation) · [observe_global](#observe_global) · [defer / defer_in](#defer-and-defer_in) · [Naming Convention](#context-naming-convention) + +## Overview + +GPUI uses different context types for different scenarios: + +**Context Types:** +- **`App`**: Global app state, entity creation +- **`Window`**: Window-specific operations, painting, layout +- **`Context`**: Entity-specific context for component `T` +- **`AsyncApp`**: Async context for foreground tasks +- **`AsyncWindowContext`**: Async context with window access + +## Quick Start + +### Context - Component Context + +```rust +impl MyComponent { + fn update_state(&mut self, cx: &mut Context) { + self.value = 42; + cx.notify(); // Trigger re-render + + // Spawn async task + cx.spawn(async move |cx| { + // Async work + }).detach(); + + // Get current entity + let entity = cx.entity(); + } +} +``` + +### App - Global Context + +```rust +fn main() { + let app = Application::new(); + app.run(|cx: &mut App| { + // Create entities + let entity = cx.new(|cx| MyState::default()); + + // Open windows + cx.open_window(WindowOptions::default(), |window, cx| { + cx.new(|cx| Root::new(view, window, cx)) + }); + }); +} +``` + +### Window - Window Context + +```rust +impl Render for MyView { + fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { + // Window operations + let is_focused = window.is_window_focused(); + let bounds = window.bounds(); + + div().child("Content") + } +} +``` + +### AsyncApp - Async Context + +```rust +cx.spawn(async move |cx: &mut AsyncApp| { + let data = fetch_data().await; + + entity.update(cx, |state, inner_cx| { + state.data = data; + inner_cx.notify(); + }).ok(); +}).detach(); +``` + +## Common Operations + +### Entity Operations + +```rust +// Create entity +let entity = cx.new(|cx| MyState::default()); + +// Update entity +entity.update(cx, |state, cx| { + state.value = 42; + cx.notify(); +}); + +// Read entity +let value = entity.read(cx).value; +``` + +### Notifications and Events + +```rust +// Trigger re-render +cx.notify(); + +// Emit event +cx.emit(MyEvent::Updated); + +// Observe entity +cx.observe(&entity, |this, observed, cx| { + // React to changes +}).detach(); + +// Subscribe to events +cx.subscribe(&entity, |this, source, event, cx| { + // Handle event +}).detach(); +``` + +### Window Operations + +```rust +// Window state +let focused = window.is_window_focused(); +let bounds = window.bounds(); +let scale = window.scale_factor(); + +// Close window +window.remove_window(); +``` + +### Async Operations + +```rust +// Spawn foreground task +cx.spawn(async move |cx| { + // Async work with entity access +}).detach(); + +// Spawn background task +cx.background_spawn(async move { + // Heavy computation +}).detach(); +``` + +## Context Hierarchy + +``` +App (Global) + └─ Window (Per-window) + └─ Context (Per-component) + └─ AsyncApp (In async tasks) + └─ AsyncWindowContext (Async + Window) +``` + +## cx.listener — Binding Callbacks to Self + +`cx.listener` creates a callback that borrows `&mut self` (the current entity). Use it for `on_click`, `on_action`, and other element event handlers: + +```rust +impl Render for MyView { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .on_action(cx.listener(Self::on_save)) + .child( + Button::new("btn") + .on_click(cx.listener(|this, _event, _window, cx| { + this.count += 1; + cx.notify(); + })) + ) + } +} + +impl MyView { + fn on_save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context) { + cx.notify(); + } +} +``` + +`cx.listener(Self::method)` is equivalent to creating a closure that calls `self.method(...)`. + +## subscribe_in — Subscribe with Window Access + +Use `subscribe_in` (instead of `subscribe`) when the callback needs `&mut Window`: + +```rust +let _subscription = cx.subscribe_in(&input, window, |this, state, event, window, cx| { + match event { + InputEvent::Change => { + let val = state.read(cx).value(); + this.on_input_change(val, window, cx); + } + _ => {} + } +}); +// Store _subscription in struct to keep it alive +``` + +`subscribe` vs `subscribe_in`: +- `subscribe(&entity, |this, source, event, cx|)` — no window access +- `subscribe_in(&entity, window, |this, source, event, window, cx|)` — has window access + +## observe_window_activation + +React when the window gains or loses focus: + +```rust +let _sub = cx.observe_window_activation(window, |this, window, cx| { + if window.is_window_active() { + this.resume(cx); + } else { + this.pause(cx); + } +}); +``` + +## observe_global + +React when a global value changes: + +```rust +cx.observe_global::(|cx| { + // Theme changed — react + cx.notify(); +}); +``` + +## defer and defer_in + +Schedule work after the current update completes: + +```rust +// defer: runs after current App update, no window access +cx.defer(|cx| { + // Runs after current entity update is done +}); + +// defer_in: runs after update, with window access +cx.defer_in(window, |this, window, cx| { + // Can access window here + // CAUTION: never call entity.update(cx) on *this same entity* inside defer_in + // — it re-enters the lock and panics. Use the &mut self reference directly. + this.some_method(window, cx); +}); +``` + +## Context Naming Convention + +Always name contexts `cx` regardless of type: + +```rust +fn new(window: &mut Window, cx: &mut App) {} // cx = App +impl Render for View { + fn render(&mut self, window: &mut Window, cx: &mut Context) {} // cx = Context +} +cx.spawn(async move |this, cx: &mut AsyncApp| {}) // cx = AsyncApp +``` diff --git a/.claude/skills/gpui-element/references/advanced-patterns.md b/skills/gpui/references/element-advanced.md similarity index 98% rename from .claude/skills/gpui-element/references/advanced-patterns.md rename to skills/gpui/references/element-advanced.md index 36e3d152a0..80886abe77 100644 --- a/.claude/skills/gpui-element/references/advanced-patterns.md +++ b/skills/gpui/references/element-advanced.md @@ -1,6 +1,6 @@ # Advanced Element Patterns -Advanced techniques and patterns for implementing sophisticated GPUI elements. +**Contents:** [Custom Layout Algorithms](#custom-layout-algorithms) · [Element Composition with Traits](#element-composition-with-traits) · [Async Element Updates](#async-element-updates) · [Element Memoization](#element-memoization) · [Virtual List Pattern](#virtual-list-pattern) ## Custom Layout Algorithms diff --git a/.claude/skills/gpui-element/references/api-reference.md b/skills/gpui/references/element-api.md similarity index 96% rename from .claude/skills/gpui-element/references/api-reference.md rename to skills/gpui/references/element-api.md index c20e244ca7..bf82a845ba 100644 --- a/.claude/skills/gpui-element/references/api-reference.md +++ b/skills/gpui/references/element-api.md @@ -1,6 +1,6 @@ # Element API Reference -Complete API documentation for GPUI's low-level Element trait. +**Contents:** [Element Trait Structure](#element-trait-structure) · [Associated Types](#associated-types) · [Methods](#methods) · [IntoElement Integration](#intoelement-integration) · [Layout System Integration](#layout-system-integration) · [Hitbox System](#hitbox-system) · [Event Handling](#event-handling) · [Cursor Styles](#cursor-styles) ## Element Trait Structure diff --git a/.claude/skills/gpui-element/references/best-practices.md b/skills/gpui/references/element-best-practices.md similarity index 97% rename from .claude/skills/gpui-element/references/best-practices.md rename to skills/gpui/references/element-best-practices.md index 93b14d3036..87708f727d 100644 --- a/.claude/skills/gpui-element/references/best-practices.md +++ b/skills/gpui/references/element-best-practices.md @@ -1,6 +1,6 @@ # Element Best Practices -Guidelines and best practices for implementing high-quality GPUI elements. +**Contents:** [State Management](#state-management) · [Performance Considerations](#performance-considerations) · [Interaction Handling](#interaction-handling) · [Layout Strategies](#layout-strategies) · [Error Handling](#error-handling) · [Testing Element Implementations](#testing-element-implementations) · [Common Pitfalls](#common-pitfalls) · [Performance Checklist](#performance-checklist) ## State Management diff --git a/.claude/skills/gpui-element/references/examples.md b/skills/gpui/references/element-examples.md similarity index 100% rename from .claude/skills/gpui-element/references/examples.md rename to skills/gpui/references/element-examples.md diff --git a/skills/gpui/references/element-id.md b/skills/gpui/references/element-id.md new file mode 100644 index 0000000000..291d191474 --- /dev/null +++ b/skills/gpui/references/element-id.md @@ -0,0 +1,95 @@ +# ElementId + +`ElementId` is a unique identifier for a GPUI element. It is required for elements that need: +- Mouse event handling (`on_click`, `on_hover`, etc.) +- State storage via `window.use_keyed_state` +- Interaction tracking + +## Making an Element Stateful + +Call `.id()` on a `div()` to create a `Stateful
`: + +```rust +div().id("my-element") // ElementId from &str +div().id(42usize) // ElementId from usize +div().id(ElementId::from(idx)) // Explicit +``` + +Without `.id()`, a div cannot receive mouse events or store state. + +## Accepted Types + +```rust +impl Into for &str // "my-id" +impl Into for String // String::from("my-id") +impl Into for usize // 0, 1, 2, ... +impl Into for u64 +impl Into for SharedString +``` + +## Uniqueness Rules + +IDs must be unique within the same **stateful parent's scope** — not globally. GPUI builds a `GlobalElementId` by chaining parent IDs: + +```rust +div().id("app").child( + div().id("list1").children(vec![ + div().id(1usize).child("Item 1"), // GlobalId: ["app", "list1", 1] + div().id(2usize).child("Item 2"), // GlobalId: ["app", "list1", 2] + ]) +).child( + div().id("list2").children(vec![ + div().id(1usize).child("Item 1"), // GlobalId: ["app", "list2", 1] — no conflict + ]) +) +``` + +Items in different parent scopes can reuse simple IDs (integers, short strings). + +## In Component Structs + +Components always store `id: ElementId` and pass it in `new()`: + +```rust +#[derive(IntoElement)] +pub struct Button { + id: ElementId, + base: Stateful
, + // ... +} + +impl Button { + pub fn new(id: impl Into) -> Self { + let id = id.into(); + Self { + id: id.clone(), + base: div().id(id), // id applied to base + // ... + } + } +} + +impl RenderOnce for Button { + fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement { + self.base // already has .id() applied + .on_click(/* ... */) + } +} +``` + +## Usage at Call Sites + +```rust +// Use unique string IDs for named components +Button::new("save-btn").label("Save") +Button::new("cancel-btn").label("Cancel") + +// Use index-based IDs in lists +for (i, item) in items.iter().enumerate() { + div().id(i) // unique within this parent +} + +// Use descriptive IDs for debugging +Input::new("search-input") +Select::new("country-select") +``` diff --git a/.claude/skills/gpui-element/references/patterns.md b/skills/gpui/references/element-patterns.md similarity index 97% rename from .claude/skills/gpui-element/references/patterns.md rename to skills/gpui/references/element-patterns.md index a7411c7670..31c9e2fa55 100644 --- a/.claude/skills/gpui-element/references/patterns.md +++ b/skills/gpui/references/element-patterns.md @@ -1,6 +1,6 @@ # Common Element Patterns -Reusable patterns for implementing common element types in GPUI. +**Contents:** [Text Rendering Elements](#text-rendering-elements) · [Container Elements](#container-elements) · [Interactive Elements](#interactive-elements) · [Composite Elements](#composite-elements) · [Scrollable Elements](#scrollable-elements) · [Pattern Selection Guide](#pattern-selection-guide) ## Text Rendering Elements diff --git a/.claude/skills/gpui-element/SKILL.md b/skills/gpui/references/element.md similarity index 70% rename from .claude/skills/gpui-element/SKILL.md rename to skills/gpui/references/element.md index 60284e8fb3..257f7d25a1 100644 --- a/.claude/skills/gpui-element/SKILL.md +++ b/skills/gpui/references/element.md @@ -1,7 +1,3 @@ ---- -name: gpui-element -description: Implementing custom elements using GPUI's low-level Element API (vs. high-level Render/RenderOnce APIs). Use when you need maximum control over layout, prepaint, and paint phases for complex, performance-critical custom UI components that cannot be achieved with Render/RenderOnce traits. ---- ## When to Use @@ -101,26 +97,8 @@ State flows in one direction through associated types, passed as mutable referen ## Reference Documentation ### Complete API Documentation -- **Element Trait API**: See [api-reference.md](references/api-reference.md) - - Associated types, methods, parameters, return values - - Hitbox system, event handling, cursor styles - -### Implementation Guides -- **Examples**: See [examples.md](references/examples.md) - - Simple text element with highlighting - - Interactive element with selection - - Complex element with child management - -- **Best Practices**: See [best-practices.md](references/best-practices.md) - - State management, performance optimization - - Interaction handling, layout strategies - - Error handling, testing, common pitfalls - -- **Common Patterns**: See [patterns.md](references/patterns.md) - - Text rendering, container, interactive, composite, scrollable patterns - - Pattern selection guide - -- **Advanced Patterns**: See [advanced-patterns.md](references/advanced-patterns.md) - - Custom layout algorithms (masonry, circular) - - Element composition with traits - - Async updates, memoization, virtual lists +- **API**: See [element-api.md](element-api.md) — associated types, hitbox system, event handling, cursor styles +- **Examples**: See [element-examples.md](element-examples.md) — text, interactive, complex elements +- **Patterns**: See [element-patterns.md](element-patterns.md) — text, container, interactive, composite, scrollable +- **Best Practices**: See [element-best-practices.md](element-best-practices.md) — performance, state, common pitfalls +- **Advanced**: See [element-advanced.md](element-advanced.md) — masonry/circular layouts, memoization, virtual lists diff --git a/.claude/skills/gpui-entity/references/advanced.md b/skills/gpui/references/entity-advanced.md similarity index 96% rename from .claude/skills/gpui-entity/references/advanced.md rename to skills/gpui/references/entity-advanced.md index 02c5c99743..29b8b6e514 100644 --- a/.claude/skills/gpui-entity/references/advanced.md +++ b/skills/gpui/references/entity-advanced.md @@ -1,6 +1,6 @@ # Advanced Entity Patterns -Advanced techniques for sophisticated entity management scenarios. +**Contents:** [Entity Collections Management](#entity-collections-management) · [Conditional Update Patterns](#conditional-update-patterns) · [Entity State Machine Pattern](#entity-state-machine-pattern) · [Entity Proxy Pattern](#entity-proxy-pattern) · [Cascading Updates Pattern](#cascading-updates-pattern) · [Entity Snapshot Pattern](#entity-snapshot-pattern) · [Entity Transaction Pattern](#entity-transaction-pattern) · [Entity Pool Pattern](#entity-pool-pattern) ## Entity Collections Management diff --git a/.claude/skills/gpui-entity/references/api-reference.md b/skills/gpui/references/entity-api.md similarity index 95% rename from .claude/skills/gpui-entity/references/api-reference.md rename to skills/gpui/references/entity-api.md index ae9ce80571..cf8a85fc6e 100644 --- a/.claude/skills/gpui-entity/references/api-reference.md +++ b/skills/gpui/references/entity-api.md @@ -1,6 +1,6 @@ # Entity API Reference -Complete API documentation for GPUI's entity system. +**Contents:** [Entity Types](#entity-types) · [Entity Creation](#entity-creation) · [Entity Operations](#entity-operations) · [Context Methods](#context-methods-for-entities) · [Async Operations](#async-operations) · [Entity Lifecycle](#entity-lifecycle) · [EntityId](#entityid) · [Error Handling](#error-handling) · [Type Conversions](#type-conversions) ## Entity Types diff --git a/.claude/skills/gpui-entity/references/best-practices.md b/skills/gpui/references/entity-best-practices.md similarity index 97% rename from .claude/skills/gpui-entity/references/best-practices.md rename to skills/gpui/references/entity-best-practices.md index 5e71f18630..460c0c914f 100644 --- a/.claude/skills/gpui-entity/references/best-practices.md +++ b/skills/gpui/references/entity-best-practices.md @@ -1,6 +1,6 @@ # Entity Best Practices -Guidelines and best practices for effective entity management in GPUI. +**Contents:** [Avoiding Common Pitfalls](#avoiding-common-pitfalls) · [Performance Optimization](#performance-optimization) · [Entity Lifecycle Management](#entity-lifecycle-management) · [Entity Observation Best Practices](#entity-observation-best-practices) · [Async Best Practices](#async-best-practices) · [Testing Best Practices](#testing-best-practices) · [Performance Checklist](#performance-checklist) ## Avoiding Common Pitfalls diff --git a/.claude/skills/gpui-entity/references/patterns.md b/skills/gpui/references/entity-patterns.md similarity index 98% rename from .claude/skills/gpui-entity/references/patterns.md rename to skills/gpui/references/entity-patterns.md index 4157d35af1..7b3e2f8065 100644 --- a/.claude/skills/gpui-entity/references/patterns.md +++ b/skills/gpui/references/entity-patterns.md @@ -1,6 +1,6 @@ # Entity Patterns -Common patterns and use cases for entity management in GPUI. +**Contents:** [Application Scenarios](#application-scenarios) · [Common Patterns](#common-patterns) · [Pattern Selection Guide](#pattern-selection-guide) ## Application Scenarios diff --git a/.claude/skills/gpui-entity/SKILL.md b/skills/gpui/references/entity.md similarity index 82% rename from .claude/skills/gpui-entity/SKILL.md rename to skills/gpui/references/entity.md index a39598105a..1741b92c21 100644 --- a/.claude/skills/gpui-entity/SKILL.md +++ b/skills/gpui/references/entity.md @@ -1,7 +1,6 @@ ---- -name: gpui-entity -description: Entity management and state handling in GPUI. Use when working with entities, managing component state, coordinating between components, handling async operations with state updates, or implementing reactive patterns. Entities provide safe concurrent access to application state. ---- +# Entity State Management + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Core Principles](#core-principles) · [Common Use Cases](#common-use-cases) · [Extended References](#reference-documentation) ## Overview @@ -70,16 +69,16 @@ impl MyComponent { ### Async Operations +When calling `cx.spawn` from `Context`, the closure receives `(WeakEntity, &mut AsyncApp)`: + ```rust impl MyComponent { fn fetch_data(&mut self, cx: &mut Context) { - let weak_self = cx.entity().downgrade(); - - cx.spawn(async move |cx| { + cx.spawn(async move |this, cx: &mut AsyncApp| { let data = fetch_from_api().await; - // Update entity safely - let _ = weak_self.update(cx, |state, cx| { + // Update entity safely via the weak reference + let _ = this.update(cx, |state, cx| { state.data = Some(data); cx.notify(); }); @@ -213,26 +212,7 @@ fn render_item(&mut self, ix: IndexPath, window: &mut Window, cx: &mut Context, +} + +impl MyComponent { + fn new(input: &Entity, window: &mut Window, cx: &mut Context) -> Self { + let _subscriptions = vec![ + cx.subscribe_in(input, window, |this, state, event, window, cx| { + match event { + InputEvent::PressEnter { .. } => this.on_submit(window, cx), + InputEvent::Change => { + let val = state.read(cx).value(); + this.on_change(val, cx); + } + _ => {} + } + }), + ]; + Self { _subscriptions } + } +} +``` + +`subscribe` vs `subscribe_in`: +- `cx.subscribe(&entity, |this, source, event, cx|)` — no window +- `cx.subscribe_in(&entity, window, |this, source, event, window, cx|)` — window access + +## observe_window_activation + +```rust +let _sub = cx.observe_window_activation(window, |this, window, cx| { + if window.is_window_active() { + this.start_polling(cx); + } else { + this.stop_polling(cx); + } +}); +``` + +## observe_global + +```rust +cx.observe_global::(|cx| { + cx.notify(); // Re-render when theme changes +}); +``` + +## Subscription Lifetime + +Subscriptions are cancelled when dropped. Two ways to keep alive: + +```rust +// 1. .detach() — lives until entity is dropped +cx.subscribe(&entity, |this, _, event, cx| { + // ... +}).detach(); + +// 2. Store in struct — cancelled when struct drops +struct MyView { + _subscriptions: Vec, +} +// _subscriptions.push(cx.subscribe(...)); +``` + +Use `.detach()` for permanent subscriptions; store in struct for subscriptions that should stop when the component unmounts. + ## Best Practices ### ✅ Detach Subscriptions @@ -165,12 +236,3 @@ entity2.subscribe(entity1) → emits event → infinite loop! ## Reference Documentation -- **API Reference**: See [api-reference.md](references/api-reference.md) - - Event definition, emission, subscriptions - - Observations, global events - - Subscription lifecycle - -- **Patterns**: See [patterns.md](references/patterns.md) - - Event-driven architectures - - Communication patterns - - Best practices and pitfalls diff --git a/.claude/skills/gpui-focus-handle/SKILL.md b/skills/gpui/references/focus-handle.md similarity index 91% rename from .claude/skills/gpui-focus-handle/SKILL.md rename to skills/gpui/references/focus-handle.md index 7bfaeb9c6a..24c47a0c5b 100644 --- a/.claude/skills/gpui-focus-handle/SKILL.md +++ b/skills/gpui/references/focus-handle.md @@ -1,7 +1,6 @@ ---- -name: gpui-focus-handle -description: Focus management and keyboard navigation in GPUI. Use when handling focus, focus handles, or keyboard navigation. Enables keyboard-driven interfaces with proper focus tracking and navigation between focusable elements. ---- +# Focus & Keyboard Navigation + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Focus Events](#focus-events) · [Keyboard Navigation](#keyboard-navigation) · [Common Patterns](#common-patterns) · [Best Practices](#best-practices) ## Overview @@ -224,9 +223,3 @@ div() .on_action(cx.listener(Self::on_enter)) ``` -## Reference Documentation - -- **API Reference**: See [api-reference.md](references/api-reference.md) - - FocusHandle API, focus management - - Events, keyboard navigation - - Best practices diff --git a/.claude/skills/gpui-global/SKILL.md b/skills/gpui/references/global.md similarity index 91% rename from .claude/skills/gpui-global/SKILL.md rename to skills/gpui/references/global.md index a8c83b2339..aedc277960 100644 --- a/.claude/skills/gpui-global/SKILL.md +++ b/skills/gpui/references/global.md @@ -1,7 +1,6 @@ ---- -name: gpui-global -description: Global state management in GPUI. Use when implementing global state, app-wide configuration, or shared resources. ---- +# Global State + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Common Use Cases](#common-use-cases) · [Best Practices](#best-practices) · [When to Use](#when-to-use) ## Overview @@ -196,9 +195,3 @@ let user_entity = cx.new(|_| UserState { ... }); - State that changes frequently - State that needs notifications -## Reference Documentation - -- **API Reference**: See [api-reference.md](references/api-reference.md) - - Global trait, set_global, update_global - - Interior mutability patterns - - Best practices and anti-patterns diff --git a/.claude/skills/gpui-layout-and-style/SKILL.md b/skills/gpui/references/layout-style.md similarity index 50% rename from .claude/skills/gpui-layout-and-style/SKILL.md rename to skills/gpui/references/layout-style.md index 91a9bbebcf..897f038a9b 100644 --- a/.claude/skills/gpui-layout-and-style/SKILL.md +++ b/skills/gpui/references/layout-style.md @@ -1,7 +1,6 @@ ---- -name: gpui-layout-and-style -description: Layout and styling in GPUI. Use when styling components, layout systems, or CSS-like properties. ---- +# Layout & Styling + +**Contents:** [Overview](#overview) · [Quick Start](#quick-start) · [Common Patterns](#common-patterns) · [Styling Methods](#styling-methods) · [h_flex / v_flex](#h_flex--v_flex-helpers) · [Tailwind Shorthands](#tailwind-style-shorthand) · [Overflow & Scroll](#overflow-and-scroll) · [Absolute Positioning](#absolute-positioning) · [Z-index](#z-index-and-stacking) · [Theme Integration](#theme-integration) · [Conditional Styling](#conditional-styling) · [Text Styling](#text-styling) ## Overview @@ -145,6 +144,86 @@ div() .flex_grow() // Grow to fill space ``` +## h_flex / v_flex Helpers + +gpui-component provides shorthand helpers (import from `gpui_component`): + +```rust +use gpui_component::{h_flex, v_flex}; + +// h_flex() = div().flex().flex_row().items_center() +h_flex() + .gap_2() + .child(icon) + .child(label) + +// v_flex() = div().flex().flex_col() +v_flex() + .gap_4() + .p_4() + .child(input1) + .child(input2) + .child(submit_btn) +``` + +These are the standard layout primitives in gpui-component — prefer them over raw `div().flex()`. + +## Tailwind-style Shorthand + +GPUI provides Tailwind-style spacing/sizing shorthands: + +```rust +// Spacing (0=0, 1=4px, 2=8px, 3=12px, 4=16px, ...) +.p_2() // padding: 8px +.px_4() // padding x: 16px +.py_3() // padding y: 12px +.m_2() // margin: 8px +.gap_3() // gap: 12px + +// Size +.size_full() // width: 100%, height: 100% +.size_4() // width: 16px, height: 16px +.w_full() // width: 100% +.h_full() // height: 100% +.flex_1() // flex: 1 1 0 (fill remaining space) +.flex_shrink_0() // prevent shrinking +``` + +## Overflow and Scroll + +```rust +div() + .overflow_hidden() // clip content + .overflow_x_hidden() // clip horizontal + .overflow_y_scrollbar() // show scrollbar on y axis + .overflow_scroll() // scroll both axes +``` + +## Absolute Positioning + +```rust +div() + .relative() // position: relative (container) + .child( + div() + .absolute() // position: absolute + .top_0() + .right_0() + .child("badge") + ) + +// Inset helpers +div().absolute().inset_0() // top/right/bottom/left: 0 (fill parent) +div().absolute().top(px(8.)).left(px(8.)) +``` + +## Z-index and Stacking + +```rust +div().z_index(10).child(overlay) +div().z_index(20).child(modal) // higher = on top +``` + ## Theme Integration ```rust @@ -160,18 +239,23 @@ div() ## Conditional Styling ```rust +use gpui::prelude::FluentBuilder as _; + div() - .when(is_active, |el| { - el.bg(cx.theme().primary) - }) - .when_some(optional_color, |el, color| { - el.bg(color) - }) + .when(is_active, |el| el.bg(cx.theme().primary)) + .when(!is_active, |el| el.opacity(0.5)) + .when_some(optional_color.as_ref(), |el, color| el.bg(*color)) ``` -## Reference Documentation +## Text Styling -- **Complete Guide**: See [reference.md](references/reference.md) - - All styling methods - - Layout strategies - - Theming, responsive design +```rust +div() + .text_sm() // font-size: small + .text_base() // font-size: base + .text_lg() // font-size: large + .font_bold() // font-weight: bold + .line_height_snug() // tighter line height + .truncate() // overflow: ellipsis, single line + .whitespace_nowrap() +``` diff --git a/.claude/skills/gpui-test/examples.md b/skills/gpui/references/test-examples.md similarity index 100% rename from .claude/skills/gpui-test/examples.md rename to skills/gpui/references/test-examples.md diff --git a/.claude/skills/gpui-test/reference.md b/skills/gpui/references/test-reference.md similarity index 97% rename from .claude/skills/gpui-test/reference.md rename to skills/gpui/references/test-reference.md index 4aecf439ae..2187a925b0 100644 --- a/.claude/skills/gpui-test/reference.md +++ b/skills/gpui/references/test-reference.md @@ -1,3 +1,7 @@ +# Test Reference + +**Contents:** [Testing Patterns](#testing-patterns) · [Testing Crash-Free State Management (Re-entrancy)](#testing-crash-free-state-management-re-entrancy) · [Property Testing](#property-testing) · [Distributed Systems Testing](#distributed-systems-testing) · [Mocking and Isolation](#mocking-and-isolation) + ## Testing Patterns ### Basic Entity Testing diff --git a/.claude/skills/gpui-test/SKILL.md b/skills/gpui/references/test.md similarity index 81% rename from .claude/skills/gpui-test/SKILL.md rename to skills/gpui/references/test.md index ffe3840af5..fe91848a79 100644 --- a/.claude/skills/gpui-test/SKILL.md +++ b/skills/gpui/references/test.md @@ -1,7 +1,3 @@ ---- -name: gpui-test -description: Writing tests for GPUI applications. Use when testing components, async operations, or UI behavior. ---- ## Overview @@ -90,6 +86,5 @@ fn test_with_window(cx: &mut TestAppContext) { ## Additional Resources -- For detailed testing patterns and examples, see [reference.md](reference.md) - - Includes **"Testing Crash-Free State Management (Re-entrancy)"** — patterns for catching entity re-entrancy panics (`cannot update … while it is already being updated`) before they reach users. -- For best practices and running tests, see [examples.md](examples.md) +- For detailed patterns (including re-entrancy crash-free testing), see [test-reference.md](test-reference.md) +- For examples and best practices, see [test-examples.md](test-examples.md)