diff --git a/crates/data/src/list.rs b/crates/data/src/list.rs index dcdb06b..ed76103 100644 --- a/crates/data/src/list.rs +++ b/crates/data/src/list.rs @@ -7,7 +7,7 @@ use gpui::{ Styled as _, Window, div, }; use gpui_native_icons::{IconRole, icon}; -use gpui_native_theme::{ActiveTheme as _, Theme}; +use gpui_native_theme::ActiveTheme as _; use gpui_native_utils::{nav_key, paint_selectable_row, selectable_label_color}; /// The change reporter, fired with the next selection by pointer and keys. @@ -144,7 +144,7 @@ impl RenderOnce for List { .enumerate() .map(|(index, item)| { render_row( - theme, + cx, index, items.len(), item, @@ -185,7 +185,7 @@ impl RenderOnce for List { } fn render_row( - theme: &Theme, + cx: &App, index: usize, count: usize, item: &ListItem, @@ -193,7 +193,8 @@ fn render_row( on_change: &Option, focus: &FocusHandle, ) -> AnyElement { - let label_color = selectable_label_color(theme, selected, item.disabled); + let theme = cx.theme(); + let label_color = selectable_label_color(cx, selected, item.disabled); let disabled = item.disabled; div() @@ -208,7 +209,7 @@ fn render_row( .aria_selected(selected) .aria_position_in_set(index + 1) .aria_size_of_set(count) - .map(|row| paint_selectable_row(theme, row, selected, disabled)) + .map(|row| paint_selectable_row(cx, row, selected, disabled)) .when(!disabled, |row| { row.cursor_pointer().on_click({ let on_change = on_change.clone(); diff --git a/crates/data/src/sidebar.rs b/crates/data/src/sidebar.rs index 1aa7bc8..69c055d 100644 --- a/crates/data/src/sidebar.rs +++ b/crates/data/src/sidebar.rs @@ -10,7 +10,7 @@ use gpui::{ Styled as _, Window, div, }; use gpui_native_icons::{IconRole, icon}; -use gpui_native_theme::{ActiveTheme as _, Theme}; +use gpui_native_theme::ActiveTheme as _; use gpui_native_utils::{nav_key, paint_selectable_row, selectable_label_color}; /// The change reporter, fired with the next selected row by pointer and keys. @@ -167,8 +167,8 @@ impl RenderOnce for Sidebar { .map(|(index, _)| index) .collect(); - // Keyboard focus rides the container across frames. let focus = gpui_native_utils::focus_handle(&self.id, window, cx); + let theme = cx.theme(); let label = self .a11y_label @@ -176,14 +176,12 @@ impl RenderOnce for Sidebar { .unwrap_or_else(|| SharedString::from(self.id.to_string())); // Entry paint reads the theme through this borrow. - let theme = cx.theme(); - let children: Vec = entries .iter() .enumerate() .map(|(index, entry)| { render_entry( - theme, + cx, entry, index, entries.len(), @@ -223,7 +221,7 @@ impl RenderOnce for Sidebar { } fn render_entry( - theme: &Theme, + cx: &App, entry: &SidebarEntry, index: usize, count: usize, @@ -231,6 +229,7 @@ fn render_entry( on_change: &Option, focus: &FocusHandle, ) -> AnyElement { + let theme = cx.theme(); match entry { SidebarEntry::Header { label } => div() .px_2() @@ -247,7 +246,7 @@ fn render_entry( detail, disabled: row_disabled, } => { - let label_color = selectable_label_color(theme, selected, *row_disabled); + let label_color = selectable_label_color(cx, selected, *row_disabled); let mut row = div() .id(("sidebar-row", index)) @@ -261,7 +260,7 @@ fn render_entry( .aria_selected(selected) .aria_position_in_set(index + 1) .aria_size_of_set(count) - .map(|row| paint_selectable_row(theme, row, selected, *row_disabled)); + .map(|row| paint_selectable_row(cx, row, selected, *row_disabled)); if !*row_disabled { row = row.cursor_pointer().on_click({ diff --git a/crates/data/src/tabs.rs b/crates/data/src/tabs.rs index e6ce3fa..71b626a 100644 --- a/crates/data/src/tabs.rs +++ b/crates/data/src/tabs.rs @@ -7,7 +7,7 @@ use gpui::{ Styled as _, Window, div, }; use gpui_native_icons::{IconRole, icon}; -use gpui_native_theme::{ActiveTheme as _, Theme}; +use gpui_native_theme::ActiveTheme as _; use gpui_native_utils::adjacent_selectable; /// The change reporter, fired with the next selected tab by pointer and keys. @@ -133,7 +133,7 @@ impl RenderOnce for Tabs { .enumerate() .map(|(index, tab)| { render_tab( - theme, + cx, tab, index, self.selected == index, @@ -181,7 +181,7 @@ impl RenderOnce for Tabs { } fn render_tab( - theme: &Theme, + cx: &App, tab: &Tab, index: usize, selected: bool, @@ -189,6 +189,7 @@ fn render_tab( on_change: &Option, focus: &gpui::FocusHandle, ) -> AnyElement { + let theme = cx.theme(); let label_color = if tab.disabled { theme.colors.muted_foreground } else if selected { @@ -229,7 +230,7 @@ fn render_tab( ) }), ) - .child(strip_tab_label(tab.label.clone(), selected, theme)); + .child(strip_tab_label(tab.label.clone(), selected, cx)); if !tab.disabled { strip_tab = strip_tab.cursor_pointer().on_click({ @@ -249,7 +250,8 @@ fn render_tab( strip_tab.into_any_element() } -fn strip_tab_label(label: SharedString, selected: bool, theme: &Theme) -> AnyElement { +fn strip_tab_label(label: SharedString, selected: bool, cx: &App) -> AnyElement { + let theme = cx.theme(); div() .text_color(if selected { theme.ramps.primary.base diff --git a/crates/data/src/tree.rs b/crates/data/src/tree.rs index 3e1acb6..884ac5b 100644 --- a/crates/data/src/tree.rs +++ b/crates/data/src/tree.rs @@ -7,7 +7,7 @@ use gpui::{ Styled as _, Window, div, }; use gpui_native_icons::{IconRole, icon}; -use gpui_native_theme::{ActiveTheme as _, Theme}; +use gpui_native_theme::ActiveTheme as _; use gpui_native_utils::{nav_key, paint_selectable_row, selectable_label_color}; /// The change reporter, fired with the next selection by pointer and keys. @@ -158,22 +158,20 @@ impl RenderOnce for Tree { .map(|(index, _)| index) .collect(); - // Keyboard focus rides the container across frames. let focus = gpui_native_utils::focus_handle(&self.id, window, cx); + let theme = cx.theme(); let label = self .a11y_label .clone() .unwrap_or_else(|| SharedString::from(self.id.to_string())); - let theme = cx.theme(); - let rows: Vec = items .iter() .enumerate() .map(|(index, _item)| { render_row( - theme, + cx, &items, index, self.selected == Some(index), @@ -231,7 +229,7 @@ impl RenderOnce for Tree { } fn render_row( - theme: &Theme, + cx: &App, items: &[TreeItem], index: usize, selected: bool, @@ -239,9 +237,10 @@ fn render_row( on_disclosure: &Option, focus: &FocusHandle, ) -> AnyElement { + let theme = cx.theme(); let item = &items[index]; let count = items.len(); - let label_color = selectable_label_color(theme, selected, item.disabled); + let label_color = selectable_label_color(cx, selected, item.disabled); let disabled = item.disabled; // One indent step per depth level, on the theme's horizontal padding. @@ -261,7 +260,7 @@ fn render_row( .aria_level(item.depth + 1) .aria_size_of_set(count) .when(item.expandable, |row| row.aria_expanded(item.expanded)) - .map(|row| paint_selectable_row(theme, row, selected, disabled)) + .map(|row| paint_selectable_row(cx, row, selected, disabled)) .when(!disabled, |row| { row.cursor_pointer().on_click({ let on_change = on_change.clone(); @@ -275,11 +274,11 @@ fn render_row( }) }) .child(render_disclosure( + cx, item, index, disabled, selected, - theme, on_disclosure, )) .when_some(item.icon, |row, role| { @@ -300,13 +299,14 @@ fn render_row( /// The disclosure slot. fn render_disclosure( + cx: &App, item: &TreeItem, index: usize, disabled: bool, selected: bool, - theme: &Theme, on_disclosure: &Option, ) -> AnyElement { + let theme = cx.theme(); let glyph_color = if disabled { theme.colors.muted_foreground } else if selected { diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 88e7aca..114c2d5 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -3,7 +3,7 @@ use gpui::{ App, BoxShadow, Div, ElementId, FocusHandle, Hsla, IntoElement, StatefulInteractiveElement, Styled, Window, div, px, }; -use gpui_native_theme::Theme; +use gpui_native_theme::ActiveTheme as _; /// A row whose children center on the cross axis. pub fn h_flex() -> Div { @@ -71,7 +71,8 @@ where } /// A selectable row's label and icon color. -pub fn selectable_label_color(theme: &Theme, selected: bool, disabled: bool) -> Hsla { +pub fn selectable_label_color(cx: &App, selected: bool, disabled: bool) -> Hsla { + let theme = cx.theme(); if disabled { theme.colors.muted_foreground } else if selected { @@ -82,10 +83,11 @@ pub fn selectable_label_color(theme: &Theme, selected: bool, disabled: bool) -> } /// A selectable row's background. -pub fn paint_selectable_row(theme: &Theme, row: R, selected: bool, disabled: bool) -> R +pub fn paint_selectable_row(cx: &App, row: R, selected: bool, disabled: bool) -> R where R: IntoElement + Styled + StatefulInteractiveElement, { + let theme = cx.theme(); row.map(|row| { if disabled { row diff --git a/docs/PLAN.md b/docs/PLAN.md index 43f867d..9a32f89 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -94,6 +94,7 @@ One-way, no cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`, - Each component has a named native reference per platform (NSButton / WinUI Button / Adwaita / Breeze) recorded on its gallery page; "native feel" is reviewed against those references. - Naming seams per AGENTS.md: `is_`/`has_` readers, `with_` builders, `set_` mutation, `on_` callbacks, `State`/`Delegate` suffixes, stable domain-derived `ElementId`s. - **Motion is inline and per component (D21).** Each component animates with gpui's own primitives (`with_animation`, `with_spring`, the gpui easings, `with_max_fps`) directly in its own file, a few lines at most, using theme durations where they're shared (`Motion.overlay`). No shared motion module, no ghost panels, no hover-wash plumbing; overlay exit animations and hover washes are dropped until a genuinely simple approach exists. +- **Theme tokens are read via `cx.theme()`, held when a render can hold it** — a component's own render does `let theme = cx.theme();` (tooltip-style) and reads `theme.x` at the paint sites, provided nothing after it needs `&mut cx`; helpers passed `cx: &App` hold their own borrow the same way. Never pass `&Theme` between functions or re-bundle theme values into local structs. Compute repeatedly-needed allocations (shadow lists) once per render where cheap. ### 4.4 Icons (`crates/icons`)