feat: replace sidebar row icon with prefix and suffix slots

This commit is contained in:
2026-11-10 07:43:30 +07:00
parent 322f037289
commit fbc4f24d16
4 changed files with 88 additions and 64 deletions
+64 -58
View File
@@ -10,7 +10,6 @@ use gpui::{
Styled as _, Window, div,
};
use gpui_native_core::scroll_area::ScrollArea;
use gpui_native_icons::Icon;
use gpui_native_theme::ActiveTheme as _;
use gpui_native_utils::{nav_key, paint_selectable_row, selectable_label_color};
@@ -19,15 +18,15 @@ type ChangeHandler = Rc<dyn Fn(Option<usize>, &mut Window, &mut App)>;
type EntriesFn = Box<dyn Fn(&mut Window, &mut App) -> Vec<SidebarEntry>>;
/// One entry of a [`Sidebar`], a section header or a selectable row.
#[derive(Clone)]
pub enum SidebarEntry {
/// A non-selectable section label.
Header { label: SharedString },
/// A selectable row.
Row {
label: SharedString,
icon: Option<Icon>,
detail: Option<SharedString>,
prefix: Option<AnyElement>,
suffix: Option<AnyElement>,
disabled: bool,
},
}
@@ -50,23 +49,13 @@ impl SidebarEntry {
{
Self::Row {
label: label.into(),
icon: None,
detail: None,
prefix: None,
suffix: None,
disabled: false,
}
}
/// Shows an icon leading the label, a role or any custom [`IconNamed`].
pub fn icon<I>(mut self, icon: I) -> Self
where
I: Into<Icon>,
{
if let Self::Row { icon: slot, .. } = &mut self {
*slot = Some(icon.into());
}
self
}
/// Shows muted trailing text.
pub fn detail<D>(mut self, detail: D) -> Self
where
@@ -81,6 +70,28 @@ impl SidebarEntry {
self
}
/// Shows an element leading the row.
pub fn prefix<E>(mut self, prefix: E) -> Self
where
E: IntoElement,
{
if let Self::Row { prefix: slot, .. } = &mut self {
*slot = Some(prefix.into_any_element());
}
self
}
/// Shows an element trailing the row.
pub fn suffix<E>(mut self, suffix: E) -> Self
where
E: IntoElement,
{
if let Self::Row { suffix: slot, .. } = &mut self {
*slot = Some(suffix.into_any_element());
}
self
}
/// Renders the row dimmed; clicks and keyboard skip it.
pub fn disabled(mut self, disabled: bool) -> Self {
if let Self::Row {
@@ -207,7 +218,7 @@ impl RenderOnce for Sidebar {
let mut row_position = 0usize;
let children: Vec<AnyElement> = entries
.iter()
.into_iter()
.enumerate()
.map(|(index, entry)| {
let is_row = matches!(entry, SidebarEntry::Row { .. });
@@ -274,7 +285,7 @@ impl RenderOnce for Sidebar {
#[allow(clippy::too_many_arguments)]
fn render_entry(
cx: &App,
entry: &SidebarEntry,
entry: SidebarEntry,
index: usize,
position_in_set: usize,
size_of_set: usize,
@@ -293,17 +304,18 @@ fn render_entry(
.text_sm()
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme.colors.muted_foreground)
.child(label.clone())
.child(label)
.into_any_element(),
SidebarEntry::Row {
label,
icon: row_icon,
detail,
prefix,
suffix,
disabled: row_disabled,
} => {
let label_color = selectable_label_color(cx, selected, *row_disabled);
let label_color = selectable_label_color(cx, selected, row_disabled);
let mut row = div()
div()
.id(("sidebar-row", index))
.flex()
.items_center()
@@ -316,51 +328,45 @@ fn render_entry(
.aria_position_in_set(position_in_set)
.aria_size_of_set(size_of_set)
.map(|row| {
if material && !*row_disabled {
if material && !row_disabled {
if selected {
row.bg(theme.material.selection)
} else {
row.hover(|row| row.bg(theme.material.hover))
}
} else {
paint_selectable_row(cx, row, selected, *row_disabled)
paint_selectable_row(cx, row, selected, row_disabled)
}
});
if !*row_disabled {
row = row.cursor_pointer().on_click({
let on_change = on_change.clone();
let focus = focus.clone();
move |_, window, cx| {
window.focus(&focus, cx);
if let Some(on_change) = &on_change {
on_change(Some(index), window, cx);
})
.when(!row_disabled, |row| {
row.cursor_pointer().on_click({
let on_change = on_change.clone();
let focus = focus.clone();
move |_, window, cx| {
window.focus(&focus, cx);
if let Some(on_change) = &on_change {
on_change(Some(index), window, cx);
}
}
}
});
}
row = row.when_some(row_icon.clone(), |row, icon| {
row.child(icon.size(theme.sizes.medium.icon_size).color(label_color))
});
row = row.child(div().flex_1().text_color(label_color).child(label.clone()));
if let Some(detail) = detail {
row = row.child(
div()
.flex_none()
.text_sm()
.text_color(if selected {
theme.colors.primary_foreground
} else {
theme.colors.muted_foreground
})
.child(detail.clone()),
);
}
row.into_any_element()
})
})
.when_some(prefix, |row, prefix| row.child(prefix))
.child(div().flex_1().text_color(label_color).child(label))
.when_some(detail, |row, detail| {
row.child(
div()
.flex_none()
.text_sm()
.text_color(if selected {
theme.colors.primary_foreground
} else {
theme.colors.muted_foreground
})
.child(detail),
)
})
.when_some(suffix, |row, suffix| row.child(suffix))
.into_any_element()
}
}
}