diff --git a/crates/core/src/command_palette.rs b/crates/core/src/command_palette.rs index 2f6ea24..1fc6568 100644 --- a/crates/core/src/command_palette.rs +++ b/crates/core/src/command_palette.rs @@ -10,6 +10,7 @@ use gpui::{ ScrollHandle, SharedString, StatefulInteractiveElement as _, Styled as _, Subscription, Window, actions, canvas, div, px, relative, }; +use gpui_native_icons::{IconRole, icon}; use gpui_native_theme::{ActiveTheme as _, Size}; use crate::common::{h_flex, v_flex}; @@ -48,18 +49,27 @@ pub struct Command { pub label: SharedString, /// Gray text on the row's right edge, e.g. the keybinding. pub hint: Option, + /// Leading glyph shown before the label. + pub icon: Option, } impl Command { - /// Creates a command without a hint. + /// Creates a command without a hint or icon. pub fn new(id: impl Into, label: impl Into) -> Self { Self { id: id.into(), label: label.into(), hint: None, + icon: None, } } + /// Shows an icon before the label. + pub fn icon(mut self, role: IconRole) -> Self { + self.icon = Some(role); + self + } + /// Sets the row's right-edge hint. pub fn hint(mut self, hint: impl Into) -> Self { self.hint = Some(hint.into()); @@ -295,15 +305,21 @@ impl Render for CommandPaletteState { return div().into_any_element(); } - let (colors, radius, metrics, shadows) = { + let (colors, radius, metrics, panel_border, shadows) = { let theme = cx.theme(); ( theme.colors, theme.radius, theme.sizes.resolve(Size::default()), + if theme.borders.overlays { + theme.colors.border + } else { + theme.colors.surface + }, theme.elevations.dialog.shadows(), ) }; + let id = self.id.clone(); let state = cx.entity(); @@ -345,6 +361,9 @@ impl Render for CommandPaletteState { state.update(cx, |state, cx| state.choose_row(row_ix, window, cx)); } }) + .when_some(command.icon, |row, role| { + row.child(icon(role).size(metrics.icon_size).color(colors.foreground)) + }) .child( div() .flex_1() @@ -407,7 +426,7 @@ impl Render for CommandPaletteState { .text_color(colors.foreground) .rounded(radius.overlay) .border_1() - .border_color(colors.border) + .border_color(panel_border) .shadow(shadows) .overflow_hidden() .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 8681693..ce7b09a 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -419,8 +419,6 @@ impl SizeTable { }; let metrics = |f: &ClassFactors| { - // Standard controls carry 16px glyphs on every platform, fixed - // against the root font the way the platforms fix them. let icon_size = if f.floor_icon { (rem_size * f.icon).max(px(16.)) } else { diff --git a/docs/CHECKLIST.md b/docs/CHECKLIST.md index baec1a0..758587e 100644 --- a/docs/CHECKLIST.md +++ b/docs/CHECKLIST.md @@ -14,10 +14,6 @@ is the first downstream consumer; its screens drive the order. Platform metric adoption continues one component at a time; each stop goes through the review gate and a gallery pass. -- [ ] **Combobox platform chrome** — popup border behind - `Theme::borders.overlays`; the trigger follows the input stop's field - treatment. -- [ ] **Command palette platform chrome** — overlay border gate. - [ ] **Switch platform chrome** — stated track and knob dims (`switch_height` / `switch_width` / `switch_knob`, inset and travel derived); borderless knob. @@ -143,11 +139,10 @@ Gaps surfaced by the signed comparison; ordered by signed's usage. (`.w(...)`) set the width; the empty field keeps its placeholder while focused (`Input::placeholder_when_focused`) and clicking into it opens the options right away, with no keystroke needed -- [x] Combobox platform chrome — the popup border behind +- [x] Command palette platform chrome — the panel border behind `Theme::borders.overlays` (and dark mode), taking the surface color - where the platform paints none; the trigger is a plain `Input`, so it - already carries the full field treatment, and the chevron recenters on - the field's full height, border included + where the platform paints none; it rides the dialog elevation, so dark + mode's no-shadow rule applies to it too - [x] Button, Checkbox, Switch, Radio + RadioGroup, Toggle, Link, Slider, Progress, Avatar (image + seeded pixel-art fallback) - [x] Spinner — painted ring + rotating-icon variant, indeterminate and @@ -166,7 +161,8 @@ Gaps surfaced by the signed comparison; ordered by signed's usage. screen-reader label - [x] Combobox — type-to-filter picker with a caller-owned matcher, create / keep / revert blur modes -- [x] Command palette — filtered actions; the matcher stays caller-owned +- [x] Command palette — filtered actions; the matcher stays caller-owned, + and commands may carry a leading icon - [x] Scrollbar — overlay thumbs for gpui's `list` and `uniform_list`, platform autohide with a `visibility` override - [x] Clipboard button — copies a value, confirms with a brief check mark diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 6744019..186c2e3 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -205,8 +205,10 @@ fn gallery_commands() -> Vec { Command::new("open-settings", "Open Settings").hint("Cmd+,"), Command::new("toggle-theme", "Toggle Theme").hint("Shift+Cmd+T"), Command::new("save-all", "Save All").hint("Cmd+Alt+S"), - Command::new("close-tab", "Close Tab").hint("Cmd+W"), - Command::new("reload-window", "Reload Window"), + Command::new("close-tab", "Close Tab") + .hint("Cmd+W") + .icon(IconRole::Close), + Command::new("reload-window", "Reload Window").icon(IconRole::Loader), ] }