From 9018f8d0b1293d40c36d7f13d167c61c2c8d1571 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 8 Jun 2026 18:38:34 +0800 Subject: [PATCH 1/3] status_bar: add StatusBar component A horizontal bar with left/center/right regions for the bottom of a window or pane, following native status bars (Windows StatusStrip, WPF StatusBar, macOS NSStatusBar). - left/right pin items to each end; child/children fill the center, whose alignment follows the pinned ends (centered with both, end-aligned with only left, start-aligned otherwise). The center is always a flex spacer so left/right reach both ends even without center content. - Regions take any impl IntoElement; StatusBar::button is a ghost xsmall button preset and StatusBar::separator a vertical divider. - Wire into the editor and dock examples (dock adds left/bottom/right dock toggles) and the gallery bottom bar, with a StatusBarStory showcasing real-world bars and layout cases. - Docs in English and Chinese. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/story/examples/dock.rs | 36 +++- crates/story/examples/editor.rs | 48 ++--- crates/story/src/gallery.rs | 41 ++++- crates/story/src/stories/mod.rs | 2 + crates/story/src/stories/status_bar_story.rs | 182 +++++++++++++++++++ crates/story/src/title_bar.rs | 6 - crates/ui/src/dock/dock.rs | 8 +- crates/ui/src/lib.rs | 1 + crates/ui/src/status_bar.rs | 127 +++++++++++++ docs/docs/components/index.md | 1 + docs/docs/components/status-bar.md | 102 +++++++++++ docs/zh-CN/docs/components/index.md | 1 + docs/zh-CN/docs/components/status-bar.md | 102 +++++++++++ 13 files changed, 615 insertions(+), 42 deletions(-) create mode 100644 crates/story/src/stories/status_bar_story.rs create mode 100644 crates/ui/src/status_bar.rs create mode 100644 docs/docs/components/status-bar.md create mode 100644 docs/zh-CN/docs/components/status-bar.md diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index 8ecbc405a3..76dfc6de0a 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -5,6 +5,7 @@ use gpui_component::{ button::{Button, ButtonVariants as _}, dock::{ClosePanel, DockArea, DockAreaState, DockEvent, DockItem, DockPlacement, ToggleZoom}, menu::DropdownMenu, + status_bar::StatusBar, }; use gpui_component_assets::Assets; @@ -513,7 +514,40 @@ impl Render for StoryWorkspace { .flex() .flex_col() .child(self.title_bar.clone()) - .child(self.dock_area.clone()) + .child(div().flex_1().min_h_0().child(self.dock_area.clone())) + .child( + StatusBar::new() + .left( + StatusBar::button("toggle-left-dock") + .icon(IconName::PanelLeft) + .tooltip("Toggle Left Dock") + .on_click(cx.listener(|this, _, window, cx| { + this.dock_area.update(cx, |area, cx| { + area.toggle_dock(DockPlacement::Left, window, cx); + }); + })), + ) + .left( + StatusBar::button("toggle-bottom-dock") + .icon(IconName::PanelBottom) + .tooltip("Toggle Bottom Dock") + .on_click(cx.listener(|this, _, window, cx| { + this.dock_area.update(cx, |area, cx| { + area.toggle_dock(DockPlacement::Bottom, window, cx); + }); + })), + ) + .child( + StatusBar::button("toggle-right-dock") + .icon(IconName::PanelRight) + .tooltip("Toggle Right Dock") + .on_click(cx.listener(|this, _, window, cx| { + this.dock_area.update(cx, |area, cx| { + area.toggle_dock(DockPlacement::Right, window, cx); + }); + })), + ), + ) .children(sheet_layer) .children(dialog_layer) .children(notification_layer) diff --git a/crates/story/examples/editor.rs b/crates/story/examples/editor.rs index d78159c18b..9ef365a09d 100644 --- a/crates/story/examples/editor.rs +++ b/crates/story/examples/editor.rs @@ -20,6 +20,7 @@ use gpui_component::{ }, list::ListItem, resizable::{h_resizable, resizable_panel}, + status_bar::StatusBar, tree::{TreeItem, TreeState, tree}, v_flex, }; @@ -972,7 +973,7 @@ impl Example { &self, _: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) -> Button { Button::new("line-number") .when(self.line_number, |this| this.icon(IconName::Check)) .label("Line Number") @@ -987,7 +988,7 @@ impl Example { })) } - fn render_soft_wrap_button(&self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render_soft_wrap_button(&self, _: &mut Window, cx: &mut Context) -> Button { Button::new("soft-wrap") .ghost() .xsmall() @@ -1006,7 +1007,7 @@ impl Example { &self, _: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) -> Button { Button::new("show-whitespace") .ghost() .xsmall() @@ -1025,7 +1026,7 @@ impl Example { &self, _: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) -> Button { Button::new("indent-guides") .ghost() .xsmall() @@ -1040,7 +1041,7 @@ impl Example { })) } - fn render_folding_button(&self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render_folding_button(&self, _: &mut Window, cx: &mut Context) -> Button { Button::new("folding") .ghost() .xsmall() @@ -1076,7 +1077,7 @@ impl Example { &self, _: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) -> Button { Button::new("scroll-beyond-last-line") .ghost() .xsmall() @@ -1097,7 +1098,7 @@ impl Example { &self, _: &mut Window, cx: &mut Context, - ) -> impl IntoElement { + ) -> Button { Button::new("cursor-surrounding-lines") .ghost() .xsmall() @@ -1114,7 +1115,7 @@ impl Example { })) } - fn render_go_to_line_button(&self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render_go_to_line_button(&self, _: &mut Window, cx: &mut Context) -> Button { let position = self.editor.read(cx).cursor_position(); let cursor = self.editor.read(cx).cursor(); @@ -1127,6 +1128,7 @@ impl Example { position.character + 1, cursor )) + .tooltip("Go to Line/Column") .on_click(cx.listener(Self::go_to_line)) } } @@ -1173,27 +1175,15 @@ impl Render for Example { ), ) .child( - h_flex() - .justify_between() - .text_sm() - .bg(cx.theme().background) - .py_1p5() - .px_4() - .border_t_1() - .border_color(cx.theme().border) - .text_color(cx.theme().muted_foreground) - .child( - h_flex() - .gap_3() - .child(self.render_line_number_button(window, cx)) - .child(self.render_soft_wrap_button(window, cx)) - .child(self.render_show_whitespaces_button(window, cx)) - .child(self.render_indent_guides_button(window, cx)) - .child(self.render_folding_button(window, cx)) - .child(self.render_scroll_beyond_last_line_button(window, cx)) - .child(self.render_cursor_surrounding_lines_button(window, cx)), - ) - .child(self.render_go_to_line_button(window, cx)), + StatusBar::new() + .left(self.render_line_number_button(window, cx)) + .left(self.render_soft_wrap_button(window, cx)) + .left(self.render_show_whitespaces_button(window, cx)) + .left(self.render_indent_guides_button(window, cx)) + .left(self.render_folding_button(window, cx)) + .left(self.render_scroll_beyond_last_line_button(window, cx)) + .left(self.render_cursor_surrounding_lines_button(window, cx)) + .right(self.render_go_to_line_button(window, cx)), ), ) } diff --git a/crates/story/src/gallery.rs b/crates/story/src/gallery.rs index adad3e59e2..28c929129f 100644 --- a/crates/story/src/gallery.rs +++ b/crates/story/src/gallery.rs @@ -1,9 +1,10 @@ use gpui::{prelude::*, *}; use gpui_component::{ - ActiveTheme as _, Icon, IconName, h_flex, + ActiveTheme as _, Icon, IconName, Sizable as _, h_flex, input::{Input, InputEvent, InputState}, resizable::{h_resizable, resizable_panel}, sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, + status_bar::StatusBar, v_flex, }; @@ -84,6 +85,7 @@ impl Gallery { StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), + StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), StoryContainer::panel::(window, cx), @@ -162,7 +164,10 @@ impl Render for Gallery { ("".into(), "".into()) }; - h_resizable("gallery-container") + let current_story = story_name.clone(); + let total_components: usize = self.stories.iter().map(|(_, items)| items.len()).sum(); + + let body = h_resizable("gallery-container") .child( resizable_panel() .size(px(255.)) @@ -300,6 +305,38 @@ impl Render for Gallery { }), ) .into_any_element(), + ); + + v_flex() + .size_full() + .child(div().flex_1().min_h_0().child(body)) + .child( + StatusBar::new() + .left( + h_flex() + .items_center() + .gap_1() + .child(Icon::new(IconName::GalleryVerticalEnd).xsmall()) + .child(format!("{total_components} components")), + ) + .left(StatusBar::separator()) + .map(|this| { + if current_story.is_empty() { + this + } else { + this.left(current_story.clone()) + } + }) + .child(cx.theme().theme_name().clone()) + .right(format!("v{}", env!("CARGO_PKG_VERSION"))) + .right( + StatusBar::button("assistant") + .icon(IconName::Github) + .tooltip("GPUI Component GitHub repository") + .on_click(|_, _, cx| { + cx.open_url("https://github.com/longbridge/gpui-component") + }), + ), ) } } diff --git a/crates/story/src/stories/mod.rs b/crates/story/src/stories/mod.rs index 7a93c73e39..2674dbbce4 100644 --- a/crates/story/src/stories/mod.rs +++ b/crates/story/src/stories/mod.rs @@ -49,6 +49,7 @@ mod sidebar_story; mod skeleton_story; mod slider_story; mod spinner_story; +mod status_bar_story; mod stepper_story; mod switch_story; mod table_story; @@ -110,6 +111,7 @@ pub use sidebar_story::SidebarStory; pub use skeleton_story::SkeletonStory; pub use slider_story::SliderStory; pub use spinner_story::SpinnerStory; +pub use status_bar_story::StatusBarStory; pub use stepper_story::StepperStory; pub use switch_story::SwitchStory; pub use table_story::TableStory; diff --git a/crates/story/src/stories/status_bar_story.rs b/crates/story/src/stories/status_bar_story.rs new file mode 100644 index 0000000000..aff9dd5e71 --- /dev/null +++ b/crates/story/src/stories/status_bar_story.rs @@ -0,0 +1,182 @@ +use gpui::{ + App, AppContext, Context, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, + Styled, Window, +}; +use gpui_component::{ + ActiveTheme as _, Icon, IconName, Sizable as _, WindowExt as _, dock::PanelControl, h_flex, + progress::Progress, status_bar::StatusBar, v_flex, +}; + +use crate::section; + +pub struct StatusBarStory { + focus_handle: gpui::FocusHandle, +} + +impl StatusBarStory { + fn new(_: &mut Window, cx: &mut Context) -> Self { + Self { + focus_handle: cx.focus_handle(), + } + } + + pub fn view(window: &mut Window, cx: &mut App) -> Entity { + cx.new(|cx| Self::new(window, cx)) + } +} + +impl super::Story for StatusBarStory { + fn title() -> &'static str { + "StatusBar" + } + + fn description() -> &'static str { + "A horizontal bar with left/center/right regions, usually placed at the bottom." + } + + fn new_view(window: &mut Window, cx: &mut App) -> Entity { + Self::view(window, cx) + } + + fn zoomable() -> Option { + None + } +} + +impl Focusable for StatusBarStory { + fn focus_handle(&self, _: &App) -> FocusHandle { + self.focus_handle.clone() + } +} + +impl Render for StatusBarStory { + fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { + v_flex() + .w_full() + .gap_4() + .child( + section("Code editor").child( + v_flex().w_full().child( + StatusBar::new() + .left( + StatusBar::button("branch") + .icon(IconName::Github) + .label("main") + .tooltip("Git branch") + .on_click(|_, window, cx| { + window.push_notification("Switch branch", cx); + }), + ) + .left(StatusBar::separator()) + .left( + h_flex() + .items_center() + .gap_2() + .child( + h_flex() + .items_center() + .gap_1() + .child( + Icon::new(IconName::CircleCheck) + .xsmall() + .text_color(cx.theme().green), + ) + .child("0"), + ) + .child( + h_flex() + .items_center() + .gap_1() + .child( + Icon::new(IconName::Info) + .xsmall() + .text_color(cx.theme().blue), + ) + .child("2"), + ), + ) + .right( + StatusBar::button("position") + .label("Ln 12, Col 34") + .tooltip("Go to Line/Column") + .on_click(|_, window, cx| { + window.push_notification("Go to Line/Column", cx); + }), + ) + .right(StatusBar::separator()) + .right(StatusBar::button("encoding").label("UTF-8").on_click( + |_, window, cx| { + window.push_notification("Select encoding", cx); + }, + )) + .right(StatusBar::button("language").label("Rust").on_click( + |_, window, cx| { + window.push_notification("Select language", cx); + }, + )), + ), + ), + ) + .child( + section("Application").child( + v_flex().w_full().child( + StatusBar::new() + .left( + h_flex() + .items_center() + .gap_1() + .child( + Icon::new(IconName::CircleCheck) + .xsmall() + .text_color(cx.theme().green), + ) + .child("Connected"), + ) + .child( + h_flex() + .items_center() + .gap_2() + .child("Syncing…") + .child(Progress::new("sync").value(60.).w_24()), + ) + .right("All changes saved") + .right( + StatusBar::button("notifications") + .icon(IconName::Bell) + .label("3") + .tooltip("3 notifications") + .on_click(|_, window, cx| { + window.push_notification("3 notifications", cx); + }), + ), + ), + ), + ) + // Layout cases for verifying the dynamic centering behavior. + .child( + section("Layout cases").child( + v_flex() + .w_full() + .gap_6() + .child(StatusBar::new().child("Center only → start-aligned")) + .child( + StatusBar::new() + .left("Left") + .child("Center → end (only left)"), + ) + .child( + StatusBar::new() + .child("Center → start (only right)") + .right("Right"), + ) + .child( + StatusBar::new() + .left("Left") + .child("Center → centered (left + right)") + .right("Right"), + ) + .child(StatusBar::new().left("Left").right("Right")), + ), + ) + } +} diff --git a/crates/story/src/title_bar.rs b/crates/story/src/title_bar.rs index 4777b06874..98c31c6a1a 100644 --- a/crates/story/src/title_bar.rs +++ b/crates/story/src/title_bar.rs @@ -9,7 +9,6 @@ use gpui_component::{ ActiveTheme as _, IconName, Side, Sizable as _, Theme, TitleBar, WindowExt as _, badge::Badge, button::{Button, ButtonVariants as _}, - label::Label, menu::{AppMenuBar, DropdownMenu as _}, scroll::ScrollbarShow, }; @@ -66,11 +65,6 @@ impl Render for AppTitleBar { .gap_2() .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation()) .child((self.child.clone())(window, cx)) - .child( - Label::new("theme:") - .secondary(cx.theme().theme_name()) - .text_sm(), - ) .child(self.font_size_selector.clone()) .child( Button::new("github") diff --git a/crates/ui/src/dock/dock.rs b/crates/ui/src/dock/dock.rs index 4d60a21489..449e90cf6b 100644 --- a/crates/ui/src/dock/dock.rs +++ b/crates/ui/src/dock/dock.rs @@ -357,13 +357,13 @@ impl Dock { }; match self.placement { DockPlacement::Left => { - let max_size = (area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size) - .max(PANEL_MIN_SIZE); + let max_size = + (area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size).max(PANEL_MIN_SIZE); self.size = size.clamp(PANEL_MIN_SIZE, max_size); } DockPlacement::Right => { - let max_size = (area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size) - .max(PANEL_MIN_SIZE); + let max_size = + (area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size).max(PANEL_MIN_SIZE); self.size = size.clamp(PANEL_MIN_SIZE, max_size); } DockPlacement::Bottom => { diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index 47d8cf74c7..d4c2f3904f 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -66,6 +66,7 @@ pub mod sidebar; pub mod skeleton; pub mod slider; pub mod spinner; +pub mod status_bar; pub mod stepper; pub mod switch; pub mod tab; diff --git a/crates/ui/src/status_bar.rs b/crates/ui/src/status_bar.rs new file mode 100644 index 0000000000..c02b44bcc4 --- /dev/null +++ b/crates/ui/src/status_bar.rs @@ -0,0 +1,127 @@ +use gpui::{ + AnyElement, App, ElementId, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, + Window, prelude::FluentBuilder as _, +}; +use smallvec::SmallVec; + +use crate::{ + ActiveTheme, Sizable as _, StyledExt, + button::{Button, ButtonVariants as _}, + h_flex, + separator::Separator, +}; + +/// A horizontal status bar, usually placed at the bottom of a window or pane. +/// +/// It is split into three regions — `left`, `center`, and `right`. This mirrors +/// the status bars found in native UI frameworks (Windows `StatusStrip`, WPF +/// `StatusBar`, macOS `NSStatusBar`): a container that holds a row of items +/// aligned to either end. +/// +/// Each region accepts any [`IntoElement`], so a string, an [`Icon`](crate::Icon), +/// a [`Button`], a custom layout, etc. can be passed directly. Use a plain +/// string for a non-interactive label, [`StatusBar::button`] for a +/// consistently-sized clickable button, and [`StatusBar::separator`] for a +/// vertical separator between items. +/// +/// `left` and `right` pin items to each end. `child`/`children` add to the +/// center region, whose alignment follows the pinned ends: centered with both +/// `left` and `right`, end-aligned with only `left`, and start-aligned +/// otherwise (only `right`, or neither — like a plain container). +/// +/// ``` +/// use gpui_component::status_bar::StatusBar; +/// +/// let _ = StatusBar::new() +/// .left("Ln 1, Col 1") +/// .right(StatusBar::button("encoding").label("UTF-8")); +/// ``` +#[derive(IntoElement)] +pub struct StatusBar { + style: StyleRefinement, + left: SmallVec<[AnyElement; 1]>, + right: SmallVec<[AnyElement; 1]>, + children: SmallVec<[AnyElement; 1]>, +} + +impl StatusBar { + /// Create a new, empty [`StatusBar`]. + pub fn new() -> Self { + Self { + style: StyleRefinement::default(), + left: SmallVec::new(), + right: SmallVec::new(), + children: SmallVec::new(), + } + } + + /// A ghost, xsmall [`Button`] preset for a status bar, so every status bar + /// button shares a consistent size. Chain `label`, `icon`, `on_click`, etc. + pub fn button(id: impl Into) -> Button { + Button::new(id).ghost().xsmall() + } + + /// A vertical separator for splitting status bar items into groups. + pub fn separator() -> Separator { + Separator::vertical().h_3() + } + + /// Append an element to the left region. Call multiple times to add more. + pub fn left(mut self, child: impl IntoElement) -> Self { + self.left.push(child.into_any_element()); + self + } + + /// Append an element to the right region. Call multiple times to add more. + pub fn right(mut self, child: impl IntoElement) -> Self { + self.right.push(child.into_any_element()); + self + } +} + +/// `child` / `children` add to the center region, so a `StatusBar` without +/// `left`/`right` items behaves like a plain container. +impl ParentElement for StatusBar { + fn extend(&mut self, elements: impl IntoIterator) { + self.children.extend(elements); + } +} + +impl Styled for StatusBar { + fn style(&mut self) -> &mut StyleRefinement { + &mut self.style + } +} + +impl RenderOnce for StatusBar { + fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement { + // The center aligns by which ends are pinned: centered with both left + // and right, end-aligned with only left, otherwise start-aligned (only + // right, or neither) — so a bar with just `child`s reads like a container. + let has_left = !self.left.is_empty(); + let has_right = !self.right.is_empty(); + let region = || h_flex().items_center().gap_1(); + + h_flex() + .items_center() + .gap_1() + .py_1() + .px_2() + .border_t_1() + .border_color(cx.theme().border) + .text_xs() + .text_color(cx.theme().muted_foreground) + .refine_style(&self.style) + .when(has_left, |this| this.child(region().children(self.left))) + // The center region is always present as a flex spacer, so `left` + // and `right` are pushed to each end even without center content. + .child( + region() + .flex_1() + .when(has_left && has_right, |this| this.justify_center()) + .when(has_left && !has_right, |this| this.justify_end()) + .children(self.children), + ) + .when(has_right, |this| this.child(region().children(self.right))) + } +} diff --git a/docs/docs/components/index.md b/docs/docs/components/index.md index c051003d06..e0ec724ff8 100644 --- a/docs/docs/components/index.md +++ b/docs/docs/components/index.md @@ -56,6 +56,7 @@ collapsed: false - [Scrollable](scrollable) - Scrollable containers - [Sheet](sheet) - Slide-in panel from edges - [Sidebar](sidebar) - Navigation sidebar +- [StatusBar](status-bar) - Bottom status bar with left/center/right regions ### Advanced Components diff --git a/docs/docs/components/status-bar.md b/docs/docs/components/status-bar.md new file mode 100644 index 0000000000..d0f7a14b4e --- /dev/null +++ b/docs/docs/components/status-bar.md @@ -0,0 +1,102 @@ +--- +title: StatusBar +description: A horizontal status bar with left, center, and right regions, usually placed at the bottom of a window or pane. +--- + +# StatusBar + +StatusBar is a horizontal bar split into three regions — `left`, `center`, and `right`. It is usually placed at the bottom of a window or pane to show contextual information and quick actions. + +The design mirrors the status bars found in native UI frameworks: Windows `StatusStrip`, WPF `StatusBar`, and macOS `NSStatusBar`. + +## Import + +```rust +use gpui_component::status_bar::StatusBar; +``` + +## Regions + +Pass any `impl IntoElement` — a string, an `Icon`, a `Button`, a custom layout, etc. — to a region. `left` and `right` pin items to each end; `child` / `children` add to the center, whose alignment follows the pinned ends — centered with both `left` and `right`, end-aligned with only `left`, start-aligned otherwise (only `right`, or neither, like a plain container). Call a method multiple times to add more. + +- For a **non-interactive label**, pass a plain string — it inherits the bar's text style and has no hover. +- For a **clickable button**, use `StatusBar::button(id)`, which returns a ghost, xsmall `Button` so every status bar button stays a consistent size. Chain `label`, `icon`, `tooltip`, `on_click`, etc. +- For a **separator**, use `StatusBar::separator()`. +- For anything else, pass the element directly. + +## Usage + +### Labels + +```rust +StatusBar::new() + .left("Ready") + .child("README.md") + .right("UTF-8") +``` + +### Buttons + +```rust +StatusBar::new() + .left( + StatusBar::button("branch") + .icon(IconName::Github) + .label("main") + .on_click(|_, window, cx| { /* ... */ }), + ) + .right( + StatusBar::button("go-to-line") + .label("Ln 1, Col 1") + .tooltip("Go to Line/Column") + .on_click(cx.listener(|this, _, window, cx| { /* ... */ })), + ) +``` + +### Separators and custom elements + +```rust +StatusBar::new() + .left(StatusBar::button("branch").icon(IconName::Github).label("main")) + .left(StatusBar::separator()) + .left( + // Any custom element works. + h_flex() + .items_center() + .gap_1() + .child(Icon::new(IconName::CircleCheck).xsmall()) + .child("0 problems"), + ) + .child(Progress::new("indexing").value(60.).w_24()) +``` + +### Custom styling + +`StatusBar` implements `Styled`, so any style method overrides the defaults. + +```rust +StatusBar::new() + .bg(cx.theme().secondary) + .border_color(cx.theme().border) + .left("Ready") +``` + +## API Reference + +### StatusBar + +| Method | Description | +| ----------------- | ---------------------------------------------------- | +| `new()` | Create a new, empty status bar | +| `button(id)` | A ghost, xsmall `Button` preset for the status bar | +| `separator()` | A vertical separator for splitting items into groups | +| `left(child)` | Append an element to the left region (call to add more) | +| `right(child)` | Append an element to the right region | +| `child(c)` / `children(cs)` | Add element(s) to the center region | + +Each region method takes `impl IntoElement`. `StatusBar` also implements `Styled`, so style methods (`bg`, `border_color`, `py`, etc.) can override the defaults. + +## Notes + +- The center (via `child` / `children`) is centered with both `left` and `right`, end-aligned with only `left`, and start-aligned otherwise (only `right`, or neither — like a plain container). +- Use a plain string (or any non-interactive element) for read-only items to avoid the button hover effect; use `StatusBar::button` only for clickable items. diff --git a/docs/zh-CN/docs/components/index.md b/docs/zh-CN/docs/components/index.md index 64776bbea9..f309407858 100644 --- a/docs/zh-CN/docs/components/index.md +++ b/docs/zh-CN/docs/components/index.md @@ -37,6 +37,7 @@ collapsed: false - [Resizable](resizable) - 可调整大小的面板 - [Scrollable](scrollable) - 可滚动容器 - [Sidebar](sidebar) - 侧边栏导航 +- [StatusBar](status-bar) - 底部状态栏,含左/中/右三区 - [Chart](chart) - 图表组件 - [DataTable](data-table) - 高性能数据表格 - [Tree](tree) - 树形结构组件 diff --git a/docs/zh-CN/docs/components/status-bar.md b/docs/zh-CN/docs/components/status-bar.md new file mode 100644 index 0000000000..c177bcfe5f --- /dev/null +++ b/docs/zh-CN/docs/components/status-bar.md @@ -0,0 +1,102 @@ +--- +title: StatusBar +description: 一个分为左、中、右三个区域的水平状态栏,通常放置在窗口或面板底部。 +--- + +# StatusBar + +StatusBar 是一个水平栏,分为 `left`、`center`、`right` 三个区域。它通常放置在窗口或面板底部,用于显示上下文信息和快捷操作。 + +其设计参考了原生 UI 框架中的状态栏:Windows 的 `StatusStrip`、WPF 的 `StatusBar` 以及 macOS 的 `NSStatusBar`。 + +## 引入 + +```rust +use gpui_component::status_bar::StatusBar; +``` + +## 区域 + +向区域传入任意 `impl IntoElement` —— 字符串、`Icon`、`Button`、自定义布局等。`left` 和 `right` 把项固定在两端;`child` / `children` 添加到中间区域,其对齐方式取决于固定了哪一端 —— 同时有 `left` 和 `right` 时居中,只有 `left` 时右对齐,否则左对齐(只有 `right`,或两者都没有,像普通容器一样)。多次调用即可追加更多。 + +- **不可交互的标签**:直接传字符串 —— 它会继承状态栏的文字样式,且没有 hover。 +- **可点击的按钮**:用 `StatusBar::button(id)`,它返回一个 ghost、xsmall 的 `Button`,保证所有状态栏按钮尺寸一致。可链式调用 `label`、`icon`、`tooltip`、`on_click` 等。 +- **分隔线**:用 `StatusBar::separator()`。 +- **其他任意内容**:直接传该元素。 + +## 用法 + +### 标签 + +```rust +StatusBar::new() + .left("Ready") + .child("README.md") + .right("UTF-8") +``` + +### 按钮 + +```rust +StatusBar::new() + .left( + StatusBar::button("branch") + .icon(IconName::Github) + .label("main") + .on_click(|_, window, cx| { /* ... */ }), + ) + .right( + StatusBar::button("go-to-line") + .label("Ln 1, Col 1") + .tooltip("Go to Line/Column") + .on_click(cx.listener(|this, _, window, cx| { /* ... */ })), + ) +``` + +### 分割线与自定义元素 + +```rust +StatusBar::new() + .left(StatusBar::button("branch").icon(IconName::Github).label("main")) + .left(StatusBar::divider()) + .left( + // 任意自定义元素都可以。 + h_flex() + .items_center() + .gap_1() + .child(Icon::new(IconName::CircleCheck).xsmall()) + .child("0 problems"), + ) + .child(Progress::new("indexing").value(60.).w_24()) +``` + +### 自定义样式 + +`StatusBar` 实现了 `Styled`,因此任意样式方法都会覆盖默认值。 + +```rust +StatusBar::new() + .bg(cx.theme().secondary) + .border_color(cx.theme().border) + .left("Ready") +``` + +## API 参考 + +### StatusBar + +| 方法 | 说明 | +| ---------------- | ------------------------------------------ | +| `new()` | 创建一个空的状态栏 | +| `button(id)` | 状态栏专用的 ghost、xsmall `Button` 预设 | +| `divider()` | 用于分隔项的竖直分割线 | +| `left(child)` | 向左侧区域追加一个元素(可多次调用) | +| `right(child)` | 向右侧区域追加一个元素 | +| `child(c)` / `children(cs)` | 向中间区域添加元素 | + +每个区域方法接受 `impl IntoElement`。`StatusBar` 同时实现了 `Styled`,样式方法(`bg`、`border_color`、`py` 等)可以覆盖默认值。 + +## 注意事项 + +- 中间区域(通过 `child` / `children`)在同时有 `left` 和 `right` 时居中,只有 `left` 时右对齐,否则左对齐(只有 `right`,或两者都没有 —— 像普通容器一样)。 +- 只读项请用纯字符串(或任意不可交互元素),以避免按钮的 hover 效果;只有可点击项才用 `StatusBar::button`。 From 0ca64c19e0cf8a4369703fb890de6f1e2c6c8ac7 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 9 Jun 2026 13:41:26 +0800 Subject: [PATCH 2/3] . --- crates/story/src/stories/status_bar_story.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/story/src/stories/status_bar_story.rs b/crates/story/src/stories/status_bar_story.rs index aff9dd5e71..b65e9ee58f 100644 --- a/crates/story/src/stories/status_bar_story.rs +++ b/crates/story/src/stories/status_bar_story.rs @@ -4,7 +4,7 @@ use gpui::{ }; use gpui_component::{ ActiveTheme as _, Icon, IconName, Sizable as _, WindowExt as _, dock::PanelControl, h_flex, - progress::Progress, status_bar::StatusBar, v_flex, + progress::ProgressCircle, status_bar::StatusBar, v_flex, }; use crate::section; @@ -125,19 +125,15 @@ impl Render for StatusBarStory { h_flex() .items_center() .gap_1() - .child( - Icon::new(IconName::CircleCheck) - .xsmall() - .text_color(cx.theme().green), - ) + .child(Icon::new(IconName::Check).xsmall()) .child("Connected"), ) .child( h_flex() .items_center() .gap_2() - .child("Syncing…") - .child(Progress::new("sync").value(60.).w_24()), + .child(ProgressCircle::new("syncing").value(45.)) + .child("Syncing…"), ) .right("All changes saved") .right( From 9deb14ad2d5ba3408ab6c6eaf09a8887c70fa0c9 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 9 Jun 2026 14:09:15 +0800 Subject: [PATCH 3/3] . --- crates/story/examples/dock.rs | 6 +-- crates/story/src/gallery.rs | 30 +++++++------- crates/story/src/stories/status_bar_story.rs | 24 +++++++----- crates/ui/src/status_bar.rs | 41 +++++--------------- crates/ui/src/theme/default-theme.json | 4 ++ crates/ui/src/theme/schema.rs | 8 ++++ crates/ui/src/theme/theme_color.rs | 4 ++ docs/docs/components/status-bar.md | 17 ++++---- docs/zh-CN/docs/components/status-bar.md | 17 ++++---- themes/everforest.json | 1 + themes/hybrid.json | 1 + themes/macos-classic.json | 1 + themes/mellifluous.json | 1 + 13 files changed, 77 insertions(+), 78 deletions(-) diff --git a/crates/story/examples/dock.rs b/crates/story/examples/dock.rs index 76dfc6de0a..b7feb53e7c 100644 --- a/crates/story/examples/dock.rs +++ b/crates/story/examples/dock.rs @@ -518,7 +518,7 @@ impl Render for StoryWorkspace { .child( StatusBar::new() .left( - StatusBar::button("toggle-left-dock") + Button::new("toggle-left-dock").ghost().xsmall() .icon(IconName::PanelLeft) .tooltip("Toggle Left Dock") .on_click(cx.listener(|this, _, window, cx| { @@ -528,7 +528,7 @@ impl Render for StoryWorkspace { })), ) .left( - StatusBar::button("toggle-bottom-dock") + Button::new("toggle-bottom-dock").ghost().xsmall() .icon(IconName::PanelBottom) .tooltip("Toggle Bottom Dock") .on_click(cx.listener(|this, _, window, cx| { @@ -538,7 +538,7 @@ impl Render for StoryWorkspace { })), ) .child( - StatusBar::button("toggle-right-dock") + Button::new("toggle-right-dock").ghost().xsmall() .icon(IconName::PanelRight) .tooltip("Toggle Right Dock") .on_click(cx.listener(|this, _, window, cx| { diff --git a/crates/story/src/gallery.rs b/crates/story/src/gallery.rs index 28c929129f..9d52832f70 100644 --- a/crates/story/src/gallery.rs +++ b/crates/story/src/gallery.rs @@ -1,8 +1,11 @@ use gpui::{prelude::*, *}; use gpui_component::{ - ActiveTheme as _, Icon, IconName, Sizable as _, h_flex, + ActiveTheme as _, Icon, IconName, Sizable as _, + button::{Button, ButtonVariants as _}, + h_flex, input::{Input, InputEvent, InputState}, resizable::{h_resizable, resizable_panel}, + separator::Separator, sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, status_bar::StatusBar, v_flex, @@ -312,25 +315,18 @@ impl Render for Gallery { .child(div().flex_1().min_h_0().child(body)) .child( StatusBar::new() - .left( - h_flex() - .items_center() - .gap_1() - .child(Icon::new(IconName::GalleryVerticalEnd).xsmall()) - .child(format!("{total_components} components")), - ) - .left(StatusBar::separator()) - .map(|this| { - if current_story.is_empty() { - this - } else { - this.left(current_story.clone()) - } + .child(Icon::new(IconName::GalleryVerticalEnd).xsmall()) + .child(format!("{total_components} components")) + .child(Separator::vertical()) + .when(!current_story.is_empty(), |this| { + this.child(current_story.clone()) }) - .child(cx.theme().theme_name().clone()) + .right(cx.theme().theme_name().clone()) .right(format!("v{}", env!("CARGO_PKG_VERSION"))) .right( - StatusBar::button("assistant") + Button::new("assistant") + .ghost() + .xsmall() .icon(IconName::Github) .tooltip("GPUI Component GitHub repository") .on_click(|_, _, cx| { diff --git a/crates/story/src/stories/status_bar_story.rs b/crates/story/src/stories/status_bar_story.rs index b65e9ee58f..26cb602236 100644 --- a/crates/story/src/stories/status_bar_story.rs +++ b/crates/story/src/stories/status_bar_story.rs @@ -3,8 +3,14 @@ use gpui::{ Styled, Window, }; use gpui_component::{ - ActiveTheme as _, Icon, IconName, Sizable as _, WindowExt as _, dock::PanelControl, h_flex, - progress::ProgressCircle, status_bar::StatusBar, v_flex, + ActiveTheme as _, Icon, IconName, Sizable as _, WindowExt as _, + button::{Button, ButtonVariants as _}, + dock::PanelControl, + h_flex, + progress::ProgressCircle, + separator::Separator, + status_bar::StatusBar, + v_flex, }; use crate::section; @@ -59,7 +65,7 @@ impl Render for StatusBarStory { v_flex().w_full().child( StatusBar::new() .left( - StatusBar::button("branch") + Button::new("branch").ghost().xsmall() .icon(IconName::Github) .label("main") .tooltip("Git branch") @@ -67,7 +73,7 @@ impl Render for StatusBarStory { window.push_notification("Switch branch", cx); }), ) - .left(StatusBar::separator()) + .left(Separator::vertical().h_3()) .left( h_flex() .items_center() @@ -96,20 +102,20 @@ impl Render for StatusBarStory { ), ) .right( - StatusBar::button("position") + Button::new("position").ghost().xsmall() .label("Ln 12, Col 34") .tooltip("Go to Line/Column") .on_click(|_, window, cx| { window.push_notification("Go to Line/Column", cx); }), ) - .right(StatusBar::separator()) - .right(StatusBar::button("encoding").label("UTF-8").on_click( + .right(Separator::vertical().h_3()) + .right(Button::new("encoding").ghost().xsmall().label("UTF-8").on_click( |_, window, cx| { window.push_notification("Select encoding", cx); }, )) - .right(StatusBar::button("language").label("Rust").on_click( + .right(Button::new("language").ghost().xsmall().label("Rust").on_click( |_, window, cx| { window.push_notification("Select language", cx); }, @@ -137,7 +143,7 @@ impl Render for StatusBarStory { ) .right("All changes saved") .right( - StatusBar::button("notifications") + Button::new("notifications").ghost().xsmall() .icon(IconName::Bell) .label("3") .tooltip("3 notifications") diff --git a/crates/ui/src/status_bar.rs b/crates/ui/src/status_bar.rs index c02b44bcc4..511599f10f 100644 --- a/crates/ui/src/status_bar.rs +++ b/crates/ui/src/status_bar.rs @@ -1,15 +1,10 @@ use gpui::{ - AnyElement, App, ElementId, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, - Window, prelude::FluentBuilder as _, + AnyElement, App, IntoElement, ParentElement, RenderOnce, StyleRefinement, Styled, Window, + prelude::FluentBuilder as _, }; use smallvec::SmallVec; -use crate::{ - ActiveTheme, Sizable as _, StyledExt, - button::{Button, ButtonVariants as _}, - h_flex, - separator::Separator, -}; +use crate::{ActiveTheme, StyledExt, h_flex}; /// A horizontal status bar, usually placed at the bottom of a window or pane. /// @@ -19,10 +14,8 @@ use crate::{ /// aligned to either end. /// /// Each region accepts any [`IntoElement`], so a string, an [`Icon`](crate::Icon), -/// a [`Button`], a custom layout, etc. can be passed directly. Use a plain -/// string for a non-interactive label, [`StatusBar::button`] for a -/// consistently-sized clickable button, and [`StatusBar::separator`] for a -/// vertical separator between items. +/// a ghost `Button`, a vertical `Separator`, a custom layout, etc. can be passed +/// directly. Use a plain string for a non-interactive label. /// /// `left` and `right` pin items to each end. `child`/`children` add to the /// center region, whose alignment follows the pinned ends: centered with both @@ -32,9 +25,7 @@ use crate::{ /// ``` /// use gpui_component::status_bar::StatusBar; /// -/// let _ = StatusBar::new() -/// .left("Ln 1, Col 1") -/// .right(StatusBar::button("encoding").label("UTF-8")); +/// let _ = StatusBar::new().left("Ln 1, Col 1").right("UTF-8"); /// ``` #[derive(IntoElement)] pub struct StatusBar { @@ -55,17 +46,6 @@ impl StatusBar { } } - /// A ghost, xsmall [`Button`] preset for a status bar, so every status bar - /// button shares a consistent size. Chain `label`, `icon`, `on_click`, etc. - pub fn button(id: impl Into) -> Button { - Button::new(id).ghost().xsmall() - } - - /// A vertical separator for splitting status bar items into groups. - pub fn separator() -> Separator { - Separator::vertical().h_3() - } - /// Append an element to the left region. Call multiple times to add more. pub fn left(mut self, child: impl IntoElement) -> Self { self.left.push(child.into_any_element()); @@ -100,21 +80,20 @@ impl RenderOnce for StatusBar { // right, or neither) — so a bar with just `child`s reads like a container. let has_left = !self.left.is_empty(); let has_right = !self.right.is_empty(); - let region = || h_flex().items_center().gap_1(); + let region = || h_flex().overflow_hidden().items_center().gap_2(); h_flex() .items_center() - .gap_1() + .gap_2() .py_1() .px_2() .border_t_1() - .border_color(cx.theme().border) + .border_color(cx.theme().status_bar_border) + .bg(cx.theme().status_bar) .text_xs() .text_color(cx.theme().muted_foreground) .refine_style(&self.style) .when(has_left, |this| this.child(region().children(self.left))) - // The center region is always present as a flex spacer, so `left` - // and `right` are pushed to each end even without center content. .child( region() .flex_1() diff --git a/crates/ui/src/theme/default-theme.json b/crates/ui/src/theme/default-theme.json index 2e19e66f70..d6c433535e 100644 --- a/crates/ui/src/theme/default-theme.json +++ b/crates/ui/src/theme/default-theme.json @@ -91,6 +91,8 @@ "tiles.background": "#fafafa", "title_bar.background": "#F8F8F8", "title_bar.border": "#e5e5e5", + "status_bar.background": "#F8F8F8", + "status_bar.border": "#e5e5e5", "warning.background": "yellow-500", "warning.foreground": "neutral-50", "overlay": "#0000000d", @@ -290,6 +292,8 @@ "tiles.background": "#171717", "title_bar.background": "#171717", "title_bar.border": "#262626", + "status_bar.background": "#171717", + "status_bar.border": "#262626", "warning.background": "yellow-400", "warning.foreground": "yellow-600", "overlay": "#00000033", diff --git a/crates/ui/src/theme/schema.rs b/crates/ui/src/theme/schema.rs index c936be8740..e46e26a55d 100644 --- a/crates/ui/src/theme/schema.rs +++ b/crates/ui/src/theme/schema.rs @@ -359,6 +359,12 @@ pub struct ThemeConfigColors { /// TitleBar border color. #[serde(rename = "title_bar.border")] pub title_bar_border: Option, + /// StatusBar background color, use for the bottom status bar. + #[serde(rename = "status_bar.background")] + pub status_bar: Option, + /// StatusBar border color. + #[serde(rename = "status_bar.border")] + pub status_bar_border: Option, /// Background color for Tiles. #[serde(rename = "tiles.background")] pub tiles: Option, @@ -651,6 +657,8 @@ impl ThemeColor { apply_color!(table_row_border, fallback = self.border); apply_color!(title_bar, fallback = self.background); apply_color!(title_bar_border, fallback = self.border); + apply_color!(status_bar, fallback = self.title_bar); + apply_color!(status_bar_border, fallback = self.title_bar_border); apply_color!(tiles, fallback = self.background); apply_color!(overlay); apply_color!(window_border, fallback = self.border); diff --git a/crates/ui/src/theme/theme_color.rs b/crates/ui/src/theme/theme_color.rs index 9f7c1b79db..da241645d4 100644 --- a/crates/ui/src/theme/theme_color.rs +++ b/crates/ui/src/theme/theme_color.rs @@ -199,6 +199,10 @@ pub struct ThemeColor { pub title_bar: Hsla, /// TitleBar border color. pub title_bar_border: Hsla, + /// StatusBar background color, use for the bottom status bar. + pub status_bar: Hsla, + /// StatusBar border color. + pub status_bar_border: Hsla, /// Background color for Tiles. pub tiles: Hsla, /// Warning background color. diff --git a/docs/docs/components/status-bar.md b/docs/docs/components/status-bar.md index d0f7a14b4e..929e1a6a47 100644 --- a/docs/docs/components/status-bar.md +++ b/docs/docs/components/status-bar.md @@ -20,8 +20,8 @@ use gpui_component::status_bar::StatusBar; Pass any `impl IntoElement` — a string, an `Icon`, a `Button`, a custom layout, etc. — to a region. `left` and `right` pin items to each end; `child` / `children` add to the center, whose alignment follows the pinned ends — centered with both `left` and `right`, end-aligned with only `left`, start-aligned otherwise (only `right`, or neither, like a plain container). Call a method multiple times to add more. - For a **non-interactive label**, pass a plain string — it inherits the bar's text style and has no hover. -- For a **clickable button**, use `StatusBar::button(id)`, which returns a ghost, xsmall `Button` so every status bar button stays a consistent size. Chain `label`, `icon`, `tooltip`, `on_click`, etc. -- For a **separator**, use `StatusBar::separator()`. +- For a **clickable button**, pass a ghost, xsmall `Button` — `Button::new(id).ghost().xsmall()` — so buttons stay a consistent size. Chain `label`, `icon`, `tooltip`, `on_click`, etc. +- For a **separator**, pass `Separator::vertical()`. - For anything else, pass the element directly. ## Usage @@ -40,13 +40,13 @@ StatusBar::new() ```rust StatusBar::new() .left( - StatusBar::button("branch") + Button::new("branch").ghost().xsmall() .icon(IconName::Github) .label("main") .on_click(|_, window, cx| { /* ... */ }), ) .right( - StatusBar::button("go-to-line") + Button::new("go-to-line").ghost().xsmall() .label("Ln 1, Col 1") .tooltip("Go to Line/Column") .on_click(cx.listener(|this, _, window, cx| { /* ... */ })), @@ -57,8 +57,8 @@ StatusBar::new() ```rust StatusBar::new() - .left(StatusBar::button("branch").icon(IconName::Github).label("main")) - .left(StatusBar::separator()) + .left(Button::new("branch").ghost().xsmall().icon(IconName::Github).label("main")) + .left(Separator::vertical()) .left( // Any custom element works. h_flex() @@ -88,8 +88,6 @@ StatusBar::new() | Method | Description | | ----------------- | ---------------------------------------------------- | | `new()` | Create a new, empty status bar | -| `button(id)` | A ghost, xsmall `Button` preset for the status bar | -| `separator()` | A vertical separator for splitting items into groups | | `left(child)` | Append an element to the left region (call to add more) | | `right(child)` | Append an element to the right region | | `child(c)` / `children(cs)` | Add element(s) to the center region | @@ -99,4 +97,5 @@ Each region method takes `impl IntoElement`. `StatusBar` also implements `Styled ## Notes - The center (via `child` / `children`) is centered with both `left` and `right`, end-aligned with only `left`, and start-aligned otherwise (only `right`, or neither — like a plain container). -- Use a plain string (or any non-interactive element) for read-only items to avoid the button hover effect; use `StatusBar::button` only for clickable items. +- Use a plain string (or any non-interactive element) for read-only items to avoid the button hover effect; use a ghost xsmall `Button` only for clickable items. +- Colors come from the `status_bar` (background) and `status_bar_border` theme tokens, which fall back to `background` / `border`. diff --git a/docs/zh-CN/docs/components/status-bar.md b/docs/zh-CN/docs/components/status-bar.md index c177bcfe5f..67ec0b9601 100644 --- a/docs/zh-CN/docs/components/status-bar.md +++ b/docs/zh-CN/docs/components/status-bar.md @@ -20,8 +20,8 @@ use gpui_component::status_bar::StatusBar; 向区域传入任意 `impl IntoElement` —— 字符串、`Icon`、`Button`、自定义布局等。`left` 和 `right` 把项固定在两端;`child` / `children` 添加到中间区域,其对齐方式取决于固定了哪一端 —— 同时有 `left` 和 `right` 时居中,只有 `left` 时右对齐,否则左对齐(只有 `right`,或两者都没有,像普通容器一样)。多次调用即可追加更多。 - **不可交互的标签**:直接传字符串 —— 它会继承状态栏的文字样式,且没有 hover。 -- **可点击的按钮**:用 `StatusBar::button(id)`,它返回一个 ghost、xsmall 的 `Button`,保证所有状态栏按钮尺寸一致。可链式调用 `label`、`icon`、`tooltip`、`on_click` 等。 -- **分隔线**:用 `StatusBar::separator()`。 +- **可点击的按钮**:传入一个 ghost、xsmall 的 `Button` —— `Button::new(id).ghost().xsmall()` —— 保证按钮尺寸一致。可链式调用 `label`、`icon`、`tooltip`、`on_click` 等。 +- **分隔线**:传入 `Separator::vertical()`。 - **其他任意内容**:直接传该元素。 ## 用法 @@ -40,13 +40,13 @@ StatusBar::new() ```rust StatusBar::new() .left( - StatusBar::button("branch") + Button::new("branch").ghost().xsmall() .icon(IconName::Github) .label("main") .on_click(|_, window, cx| { /* ... */ }), ) .right( - StatusBar::button("go-to-line") + Button::new("go-to-line").ghost().xsmall() .label("Ln 1, Col 1") .tooltip("Go to Line/Column") .on_click(cx.listener(|this, _, window, cx| { /* ... */ })), @@ -57,8 +57,8 @@ StatusBar::new() ```rust StatusBar::new() - .left(StatusBar::button("branch").icon(IconName::Github).label("main")) - .left(StatusBar::divider()) + .left(Button::new("branch").ghost().xsmall().icon(IconName::Github).label("main")) + .left(Separator::vertical()) .left( // 任意自定义元素都可以。 h_flex() @@ -88,8 +88,6 @@ StatusBar::new() | 方法 | 说明 | | ---------------- | ------------------------------------------ | | `new()` | 创建一个空的状态栏 | -| `button(id)` | 状态栏专用的 ghost、xsmall `Button` 预设 | -| `divider()` | 用于分隔项的竖直分割线 | | `left(child)` | 向左侧区域追加一个元素(可多次调用) | | `right(child)` | 向右侧区域追加一个元素 | | `child(c)` / `children(cs)` | 向中间区域添加元素 | @@ -99,4 +97,5 @@ StatusBar::new() ## 注意事项 - 中间区域(通过 `child` / `children`)在同时有 `left` 和 `right` 时居中,只有 `left` 时右对齐,否则左对齐(只有 `right`,或两者都没有 —— 像普通容器一样)。 -- 只读项请用纯字符串(或任意不可交互元素),以避免按钮的 hover 效果;只有可点击项才用 `StatusBar::button`。 +- 只读项请用纯字符串(或任意不可交互元素),以避免按钮的 hover 效果;只有可点击项才用 ghost、xsmall 的 `Button`。 +- 颜色取自 `status_bar`(背景)和 `status_bar_border`(边框)主题变量,缺省回退到 `background` / `border`。 diff --git a/themes/everforest.json b/themes/everforest.json index 56229dc182..a5105e4d2f 100644 --- a/themes/everforest.json +++ b/themes/everforest.json @@ -40,6 +40,7 @@ "tab_bar.background": "#F4F1E2", "title_bar.background": "#F9F5E4", "title_bar.border": "#E7E5D4", + "status_bar.background": "#F9F5E4", "base.red": "#e67e80", "base.green": "#a7c080", "base.yellow": "#dbbc7f", diff --git a/themes/hybrid.json b/themes/hybrid.json index dd261f8d16..133d5547f4 100644 --- a/themes/hybrid.json +++ b/themes/hybrid.json @@ -41,6 +41,7 @@ "tab_bar.background": "#e8e8e8", "title_bar.background": "#D0D0D0", "title_bar.border": "#B3B3B3", + "status_bar.background": "#DADADA", "base.red": "#5F0000", "base.green": "#005F00", "base.yellow": "#948000", diff --git a/themes/macos-classic.json b/themes/macos-classic.json index 70c2ac87bc..9af13b9a52 100644 --- a/themes/macos-classic.json +++ b/themes/macos-classic.json @@ -38,6 +38,7 @@ "tab_bar.background": "#E9E9E9", "title_bar.background": "#FEFEFE", "title_bar.border": "#DADADA", + "status_bar.background": "#E9E9E9", "base.yellow": "#B59A00", "base.red": "#d21f07", "base.blue": "#0060de", diff --git a/themes/mellifluous.json b/themes/mellifluous.json index fac6457425..afcfad917d 100644 --- a/themes/mellifluous.json +++ b/themes/mellifluous.json @@ -45,6 +45,7 @@ "tab_bar.background": "#E7E7E7", "title_bar.background": "#dfdfdf", "title_bar.border": "#CACACA", + "status_bar.background": "#dfdfdf", "base.red": "#C95954", "base.green": "#828040", "base.yellow": "#c98f54",