diff --git a/assets/settings/default.json b/assets/settings/default.json index 563f0371f9612e5b69cce570fd84fdab325824c4..74ba9e6d52158ea63bab9c6200567b27286aa1da 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -922,6 +922,10 @@ /// /// Default: false "tree_view": false, + // Whether to show a badge on the git panel icon with the count of uncommitted changes. + // + // Default: false + "show_count_badge": false, "scrollbar": { // When to show the scrollbar in the git panel. // @@ -946,6 +950,8 @@ "dock": "right", // Default width of the notification panel. "default_width": 380, + // Whether to show a badge on the notification panel icon with the count of unread notifications. + "show_count_badge": false, }, "agent": { // Whether the inline assistant should use streaming tools, when available @@ -1867,6 +1873,8 @@ // Timeout for hover and Cmd-click path hyperlink discovery in milliseconds. Specifying a // timeout of `0` will disable path hyperlinking in terminal. "path_hyperlink_timeout_ms": 1, + // Whether to show a badge on the terminal panel icon with the count of open terminals. + "show_count_badge": false, }, "code_actions_on_format": {}, // Settings related to running tasks. diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index fd70163896113f0a20b66c5181749d58385b4c34..308d521832d5f2964a46f32e88329bd15d5358ee 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -677,6 +677,9 @@ impl Panel for NotificationPanel { } fn icon_label(&self, _window: &Window, cx: &App) -> Option { + if !NotificationPanelSettings::get_global(cx).show_count_badge { + return None; + } let count = self.notification_store.read(cx).unread_notification_count(); if count == 0 { None diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index ebd021be4b56f4051feae01f3fef7a063c3a8214..938d33159e9adb7a9e63ceb73219b70724efee17 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -15,6 +15,7 @@ pub struct NotificationPanelSettings { pub button: bool, pub dock: DockPosition, pub default_width: Pixels, + pub show_count_badge: bool, } impl Settings for CollaborationPanelSettings { @@ -36,6 +37,7 @@ impl Settings for NotificationPanelSettings { button: panel.button.unwrap(), dock: panel.dock.unwrap().into(), default_width: panel.default_width.map(px).unwrap(), + show_count_badge: panel.show_count_badge.unwrap(), }; } } diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index ac1c4f97d4ebdbd387da0a0bb3306c58dde8c11e..7bd4f3b32a6bd1fa2be6e26d5662f51c80e5a0e6 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -5797,6 +5797,14 @@ impl Panel for GitPanel { Some("Git Panel") } + fn icon_label(&self, _: &Window, cx: &App) -> Option { + if !GitPanelSettings::get_global(cx).show_count_badge { + return None; + } + let total = self.changes_count; + (total > 0).then(|| total.to_string()) + } + fn toggle_action(&self) -> Box { Box::new(ToggleFocus) } diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index d48cf44232afa93a8d9ce4e441364912a7200c45..baf453e310c02097da1d11344e79bac31f891d0b 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -28,6 +28,7 @@ pub struct GitPanelSettings { pub collapse_untracked_diff: bool, pub tree_view: bool, pub diff_stats: bool, + pub show_count_badge: bool, } impl ScrollbarVisibility for GitPanelSettings { @@ -64,6 +65,7 @@ impl Settings for GitPanelSettings { collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(), tree_view: git_panel.tree_view.unwrap(), diff_stats: git_panel.diff_stats.unwrap(), + show_count_badge: git_panel.show_count_badge.unwrap(), } } } diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index bcc579984bda0268a7405cbd1ea184cafc493aab..abfe0ec727c7388a612c38f5bb0b0c4d0dbf5682 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -877,6 +877,7 @@ impl VsCodeSettings { scrollbar: None, scroll_multiplier: None, toolbar: None, + show_count_badge: None, }) } diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 95fc79e2718859baf60ebdd548a172bdc5526468..8ab0ad6874a9c87a2104ba580c7fb1a90276027e 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -635,6 +635,11 @@ pub struct GitPanelSettingsContent { /// /// Default: true pub diff_stats: Option, + + /// Whether to show a badge on the git panel icon with the count of uncommitted changes. + /// + /// Default: false + pub show_count_badge: Option, } #[derive( @@ -682,6 +687,10 @@ pub struct NotificationPanelSettingsContent { /// Default: 300 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub default_width: Option, + /// Whether to show a badge on the notification panel icon with the count of unread notifications. + /// + /// Default: false + pub show_count_badge: Option, } #[with_fallible_options] diff --git a/crates/settings_content/src/terminal.rs b/crates/settings_content/src/terminal.rs index a13613badfaa0a375dbcbdf6424e7bda59a84dc4..83f3b32fdd14a6ee693f775b74022af4841af0a5 100644 --- a/crates/settings_content/src/terminal.rs +++ b/crates/settings_content/src/terminal.rs @@ -171,6 +171,10 @@ pub struct TerminalSettingsContent { /// Default: 45 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub minimum_contrast: Option, + /// Whether to show a badge on the terminal panel icon with the count of open terminals. + /// + /// Default: false + pub show_count_badge: Option, } /// Shell configuration to open the terminal with. diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index e6c4ba58c39968985478067527fd7109a22db3b5..f5398b60fe528153c3a6d146fcf1eb9b105f713f 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -4820,7 +4820,7 @@ fn panels_page() -> SettingsPage { ] } - fn terminal_panel_section() -> [SettingsPageItem; 2] { + fn terminal_panel_section() -> [SettingsPageItem; 3] { [ SettingsPageItem::SectionHeader("Terminal Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -4836,6 +4836,28 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Show Count Badge", + description: "Show a badge on the terminal panel icon with the count of open terminals.", + field: Box::new(SettingField { + json_path: Some("terminal.show_count_badge"), + pick: |settings_content| { + settings_content + .terminal + .as_ref()? + .show_count_badge + .as_ref() + }, + write: |settings_content, value| { + settings_content + .terminal + .get_or_insert_default() + .show_count_badge = value; + }, + }), + metadata: None, + files: USER, + }), ] } @@ -5048,7 +5070,7 @@ fn panels_page() -> SettingsPage { ] } - fn git_panel_section() -> [SettingsPageItem; 13] { + fn git_panel_section() -> [SettingsPageItem; 14] { [ SettingsPageItem::SectionHeader("Git Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -5244,6 +5266,28 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Show Count Badge", + description: "Whether to show a badge on the git panel icon with the count of uncommitted changes.", + field: Box::new(SettingField { + json_path: Some("git_panel.show_count_badge"), + pick: |settings_content| { + settings_content + .git_panel + .as_ref()? + .show_count_badge + .as_ref() + }, + write: |settings_content, value| { + settings_content + .git_panel + .get_or_insert_default() + .show_count_badge = value; + }, + }), + metadata: None, + files: USER, + }), SettingsPageItem::SettingItem(SettingItem { title: "Scroll Bar", description: "How and when the scrollbar should be displayed.", @@ -5294,7 +5338,7 @@ fn panels_page() -> SettingsPage { ] } - fn notification_panel_section() -> [SettingsPageItem; 4] { + fn notification_panel_section() -> [SettingsPageItem; 5] { [ SettingsPageItem::SectionHeader("Notification Panel"), SettingsPageItem::SettingItem(SettingItem { @@ -5359,6 +5403,28 @@ fn panels_page() -> SettingsPage { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Show Count Badge", + description: "Show a badge on the notification panel icon with the count of unread notifications.", + field: Box::new(SettingField { + json_path: Some("notification_panel.show_count_badge"), + pick: |settings_content| { + settings_content + .notification_panel + .as_ref()? + .show_count_badge + .as_ref() + }, + write: |settings_content, value| { + settings_content + .notification_panel + .get_or_insert_default() + .show_count_badge = value; + }, + }), + metadata: None, + files: USER, + }), ] } diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 45f22319869381ae497e64c2f8e65abed6fe9d69..f24bd5ead6cfd8cb0d4ded66a770a6040d957b72 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -50,6 +50,7 @@ pub struct TerminalSettings { pub minimum_contrast: f32, pub path_hyperlink_regexes: Vec, pub path_hyperlink_timeout_ms: u64, + pub show_count_badge: bool, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -129,6 +130,7 @@ impl settings::Settings for TerminalSettings { }) .collect(), path_hyperlink_timeout_ms: project_content.path_hyperlink_timeout_ms.unwrap(), + show_count_badge: user_content.show_count_badge.unwrap(), } } } diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 93b9e651191e791da8bbda35600c3db001b46d90..b3c1f0bf1754d9b0d814bea3dff48b5a7f205613 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -1606,6 +1606,9 @@ impl Panel for TerminalPanel { } fn icon_label(&self, _window: &Window, cx: &App) -> Option { + if !TerminalSettings::get_global(cx).show_count_badge { + return None; + } let count = self .center .panes() diff --git a/crates/ui/src/components.rs b/crates/ui/src/components.rs index ef344529cd92efcbf8f57d192c44bbb53befc25e..68b1ff9beb7a8918ee3f5e1857e3cc68e15a3fc1 100644 --- a/crates/ui/src/components.rs +++ b/crates/ui/src/components.rs @@ -6,6 +6,7 @@ mod callout; mod chip; mod collab; mod context_menu; +mod count_badge; mod data_table; mod diff_stat; mod disclosure; @@ -49,6 +50,7 @@ pub use callout::*; pub use chip::*; pub use collab::*; pub use context_menu::*; +pub use count_badge::*; pub use data_table::*; pub use diff_stat::*; pub use disclosure::*; diff --git a/crates/ui/src/components/count_badge.rs b/crates/ui/src/components/count_badge.rs new file mode 100644 index 0000000000000000000000000000000000000000..c546d69e6d15b12e75ff94424b03b82f371ac94a --- /dev/null +++ b/crates/ui/src/components/count_badge.rs @@ -0,0 +1,93 @@ +use gpui::FontWeight; + +use crate::prelude::*; + +/// A small, pill-shaped badge that displays a numeric count. +/// +/// The count is capped at 99 and displayed as "99+" beyond that. +#[derive(IntoElement, RegisterComponent)] +pub struct CountBadge { + count: usize, +} + +impl CountBadge { + pub fn new(count: usize) -> Self { + Self { count } + } +} + +impl RenderOnce for CountBadge { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + let label = if self.count > 99 { + "99+".to_string() + } else { + self.count.to_string() + }; + + let bg = cx + .theme() + .colors() + .editor_background + .blend(cx.theme().status().error.opacity(0.4)); + + h_flex() + .absolute() + .top_0() + .right_0() + .p_px() + .h_3p5() + .min_w_3p5() + .rounded_full() + .justify_center() + .text_center() + .border_1() + .border_color(cx.theme().colors().border) + .bg(bg) + .shadow_sm() + .child( + Label::new(label) + .size(LabelSize::Custom(rems_from_px(9.))) + .weight(FontWeight::MEDIUM), + ) + } +} + +impl Component for CountBadge { + fn scope() -> ComponentScope { + ComponentScope::Status + } + + fn description() -> Option<&'static str> { + Some("A small, pill-shaped badge that displays a numeric count.") + } + + fn preview(_window: &mut Window, cx: &mut App) -> Option { + let container = || { + div() + .relative() + .size_8() + .border_1() + .border_color(cx.theme().colors().border) + .bg(cx.theme().colors().background) + }; + + Some( + v_flex() + .gap_6() + .child(example_group_with_title( + "Count Badge", + vec![ + single_example( + "Basic Count", + container().child(CountBadge::new(3)).into_any_element(), + ), + single_example( + "Capped Count", + container().child(CountBadge::new(150)).into_any_element(), + ), + ], + )) + .into_any_element(), + ) + } +} diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 439c6df5ee45938368895a67834d57df695fde89..44a24f687a49552f707d968e82d19387b74b0ac1 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -12,8 +12,10 @@ use gpui::{ }; use settings::SettingsStore; use std::sync::Arc; -use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex}; -use ui::{prelude::*, right_click_menu}; +use ui::{ + ContextMenu, CountBadge, Divider, DividerColor, IconButton, Tooltip, prelude::*, + right_click_menu, +}; use util::ResultExt as _; pub(crate) const RESIZE_HANDLE_SIZE: Pixels = px(6.); @@ -940,6 +942,7 @@ impl Render for PanelButtons { }; let focus_handle = dock.focus_handle(cx); + let icon_label = entry.panel.icon_label(window, cx); Some( right_click_menu(name) @@ -973,7 +976,7 @@ impl Render for PanelButtons { .trigger(move |is_active, _window, _cx| { // Include active state in element ID to invalidate the cached // tooltip when panel state changes (e.g., via keyboard shortcut) - IconButton::new((name, is_active_button as u64), icon) + let button = IconButton::new((name, is_active_button as u64), icon) .icon_size(IconSize::Small) .toggle_state(is_active_button) .on_click({ @@ -987,7 +990,15 @@ impl Render for PanelButtons { this.tooltip(move |_window, cx| { Tooltip::for_action(tooltip.clone(), &*action, cx) }) - }) + }); + + div().relative().child(button).when_some( + icon_label + .clone() + .filter(|_| !is_active_button) + .and_then(|label| label.parse::().ok()), + |this, count| this.child(CountBadge::new(count)), + ) }), ) }) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index dc3d076bd6addc911dfdbf0cc736d876acc78484..38271ac77cf05d9545f22084696837121b13f93d 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -7868,7 +7868,6 @@ impl Render for Workspace { window, cx, )), - BottomDockLayout::RightAligned => div() .flex() .flex_row() @@ -7927,7 +7926,6 @@ impl Render for Workspace { .children(self.render_dock(DockPosition::Bottom, &self.bottom_dock, window, cx)) ), ), - BottomDockLayout::Contained => div() .flex() .flex_row()