Detailed changes
@@ -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.
@@ -677,6 +677,9 @@ impl Panel for NotificationPanel {
}
fn icon_label(&self, _window: &Window, cx: &App) -> Option<String> {
+ if !NotificationPanelSettings::get_global(cx).show_count_badge {
+ return None;
+ }
let count = self.notification_store.read(cx).unread_notification_count();
if count == 0 {
None
@@ -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(),
};
}
}
@@ -5797,6 +5797,14 @@ impl Panel for GitPanel {
Some("Git Panel")
}
+ fn icon_label(&self, _: &Window, cx: &App) -> Option<String> {
+ 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<dyn Action> {
Box::new(ToggleFocus)
}
@@ -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(),
}
}
}
@@ -877,6 +877,7 @@ impl VsCodeSettings {
scrollbar: None,
scroll_multiplier: None,
toolbar: None,
+ show_count_badge: None,
})
}
@@ -635,6 +635,11 @@ pub struct GitPanelSettingsContent {
///
/// Default: true
pub diff_stats: Option<bool>,
+
+ /// Whether to show a badge on the git panel icon with the count of uncommitted changes.
+ ///
+ /// Default: false
+ pub show_count_badge: Option<bool>,
}
#[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<f32>,
+ /// Whether to show a badge on the notification panel icon with the count of unread notifications.
+ ///
+ /// Default: false
+ pub show_count_badge: Option<bool>,
}
#[with_fallible_options]
@@ -171,6 +171,10 @@ pub struct TerminalSettingsContent {
/// Default: 45
#[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
pub minimum_contrast: Option<f32>,
+ /// Whether to show a badge on the terminal panel icon with the count of open terminals.
+ ///
+ /// Default: false
+ pub show_count_badge: Option<bool>,
}
/// Shell configuration to open the terminal with.
@@ -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,
+ }),
]
}
@@ -50,6 +50,7 @@ pub struct TerminalSettings {
pub minimum_contrast: f32,
pub path_hyperlink_regexes: Vec<String>,
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(),
}
}
}
@@ -1606,6 +1606,9 @@ impl Panel for TerminalPanel {
}
fn icon_label(&self, _window: &Window, cx: &App) -> Option<String> {
+ if !TerminalSettings::get_global(cx).show_count_badge {
+ return None;
+ }
let count = self
.center
.panes()
@@ -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::*;
@@ -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<AnyElement> {
+ 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(),
+ )
+ }
+}
@@ -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::<usize>().ok()),
+ |this, count| this.child(CountBadge::new(count)),
+ )
}),
)
})
@@ -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()