use crate::theme::{Theme, ThemeExt};
use gpui::prelude::*;
use gpui::*;
#[derive(Clone)]
pub struct BreadcrumbItem {
id: SharedString,
label: SharedString,
icon: Option<SharedString>,
href: Option<SharedString>,
}
impl BreadcrumbItem {
pub fn new(id: impl Into<SharedString>, label: impl Into<SharedString>) -> Self {
Self {
id: id.into(),
label: label.into(),
icon: None,
href: None,
}
}
pub fn icon(mut self, icon: impl Into<SharedString>) -> Self {
self.icon = Some(icon.into());
self
}
pub fn href(mut self, href: impl Into<SharedString>) -> Self {
self.href = Some(href.into());
self
}
pub fn id(&self) -> &SharedString {
&self.id
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BreadcrumbSeparator {
#[default]
Slash,
Chevron,
Dot,
}
impl BreadcrumbSeparator {
fn char(&self) -> &'static str {
match self {
BreadcrumbSeparator::Slash => "/",
BreadcrumbSeparator::Chevron => "›",
BreadcrumbSeparator::Dot => "•",
}
}
}
pub struct Breadcrumbs {
items: Vec<BreadcrumbItem>,
separator: BreadcrumbSeparator,
on_click: Option<Box<dyn Fn(&SharedString, &mut Window, &mut App) + 'static>>,
}
impl Breadcrumbs {
pub fn new() -> Self {
Self {
items: Vec::new(),
separator: BreadcrumbSeparator::default(),
on_click: None,
}
}
pub fn items(mut self, items: Vec<BreadcrumbItem>) -> Self {
self.items = items;
self
}
pub fn separator(mut self, separator: BreadcrumbSeparator) -> Self {
self.separator = separator;
self
}
pub fn on_click(
mut self,
handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static,
) -> Self {
self.on_click = Some(Box::new(handler));
self
}
pub fn build_with_theme(self, theme: &Theme) -> Div {
let mut container = div().flex().items_center().gap_2().text_sm();
let last_idx = self.items.len().saturating_sub(1);
for (idx, item) in self.items.iter().enumerate() {
let is_last = idx == last_idx;
let item_id = item.id.clone();
if idx > 0 {
container = container.child(
div()
.text_color(theme.text_muted)
.child(self.separator.char()),
);
}
let mut crumb = div()
.id(SharedString::from(format!("breadcrumb-{}", item_id)))
.flex()
.items_center()
.gap_1();
if is_last {
crumb = crumb
.text_color(theme.text_primary)
.font_weight(FontWeight::MEDIUM);
} else {
let hover_color = theme.accent;
crumb = crumb
.text_color(theme.text_muted)
.cursor_pointer()
.hover(move |s| s.text_color(hover_color));
if let Some(ref handler) = self.on_click {
let handler_ptr: *const dyn Fn(&SharedString, &mut Window, &mut App) =
handler.as_ref();
let id = item_id.clone();
crumb =
crumb.on_mouse_up(MouseButton::Left, move |_event, window, cx| unsafe {
(*handler_ptr)(&id, window, cx);
});
}
}
if let Some(icon) = &item.icon {
crumb = crumb.child(div().child(icon.clone()));
}
crumb = crumb.child(item.label.clone());
container = container.child(crumb);
}
container
}
}
impl Default for Breadcrumbs {
fn default() -> Self {
Self::new()
}
}
impl RenderOnce for Breadcrumbs {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
self.build_with_theme(&theme)
}
}
impl IntoElement for Breadcrumbs {
type Element = gpui::Component<Self>;
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}
}