Skip to main content

gpui_component/
breadcrumb.rs

1use std::rc::Rc;
2
3use gpui::{
4    App, ClickEvent, ElementId, InteractiveElement as _, IntoElement, ParentElement, RenderOnce,
5    Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
6    prelude::FluentBuilder as _,
7};
8
9use crate::{ActiveTheme, Icon, IconName, StyledExt, h_flex};
10
11/// A breadcrumb navigation element.
12#[derive(IntoElement)]
13pub struct Breadcrumb {
14    style: StyleRefinement,
15    items: Vec<BreadcrumbItem>,
16}
17
18/// Item for the [`Breadcrumb`].
19#[derive(IntoElement)]
20pub struct BreadcrumbItem {
21    id: ElementId,
22    style: StyleRefinement,
23    label: SharedString,
24    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
25    disabled: bool,
26    is_last: bool,
27}
28
29impl BreadcrumbItem {
30    /// Create a new BreadcrumbItem with the given id and label.
31    pub fn new(label: impl Into<SharedString>) -> Self {
32        Self {
33            id: ElementId::Integer(0),
34            style: StyleRefinement::default(),
35            label: label.into(),
36            on_click: None,
37            disabled: false,
38            is_last: false,
39        }
40    }
41
42    pub fn disabled(mut self, disabled: bool) -> Self {
43        self.disabled = disabled;
44        self
45    }
46
47    pub fn on_click(
48        mut self,
49        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
50    ) -> Self {
51        self.on_click = Some(Rc::new(on_click));
52        self
53    }
54
55    fn id(mut self, id: impl Into<ElementId>) -> Self {
56        self.id = id.into();
57        self
58    }
59
60    /// For internal use only.
61    fn is_last(mut self, is_last: bool) -> Self {
62        self.is_last = is_last;
63        self
64    }
65}
66
67impl Styled for BreadcrumbItem {
68    fn style(&mut self) -> &mut StyleRefinement {
69        &mut self.style
70    }
71}
72
73impl From<&'static str> for BreadcrumbItem {
74    fn from(value: &'static str) -> Self {
75        Self::new(value)
76    }
77}
78
79impl From<String> for BreadcrumbItem {
80    fn from(value: String) -> Self {
81        Self::new(value)
82    }
83}
84
85impl From<SharedString> for BreadcrumbItem {
86    fn from(value: SharedString) -> Self {
87        Self::new(value)
88    }
89}
90
91impl RenderOnce for BreadcrumbItem {
92    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
93        div()
94            .id(self.id)
95            .role(if self.on_click.is_some() && !self.disabled {
96                Role::Link
97            } else {
98                Role::ListItem
99            })
100            .child(self.label)
101            .text_color(cx.theme().muted_foreground)
102            .when(self.is_last, |this| this.text_color(cx.theme().foreground))
103            .when(self.disabled, |this| {
104                this.text_color(cx.theme().muted_foreground)
105            })
106            .refine_style(&self.style)
107            .when(!self.disabled, |this| {
108                this.when_some(self.on_click, |this, on_click| {
109                    this.cursor_pointer().on_click(move |event, window, cx| {
110                        on_click(event, window, cx);
111                    })
112                })
113            })
114    }
115}
116
117impl Breadcrumb {
118    /// Create a new breadcrumb.
119    pub fn new() -> Self {
120        Self {
121            items: Vec::new(),
122            style: StyleRefinement::default(),
123        }
124    }
125
126    /// Add an [`BreadcrumbItem`] to the breadcrumb.
127    pub fn child(mut self, item: impl Into<BreadcrumbItem>) -> Self {
128        self.items.push(item.into());
129        self
130    }
131
132    /// Add multiple [`BreadcrumbItem`] items to the breadcrumb.
133    pub fn children(mut self, items: impl IntoIterator<Item = impl Into<BreadcrumbItem>>) -> Self {
134        self.items.extend(items.into_iter().map(Into::into));
135        self
136    }
137}
138
139#[derive(IntoElement)]
140struct BreadcrumbSeparator;
141impl RenderOnce for BreadcrumbSeparator {
142    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
143        Icon::new(IconName::ChevronRight)
144            .text_color(cx.theme().muted_foreground)
145            .size_3p5()
146            .into_any_element()
147    }
148}
149
150impl Styled for Breadcrumb {
151    fn style(&mut self) -> &mut StyleRefinement {
152        &mut self.style
153    }
154}
155
156impl RenderOnce for Breadcrumb {
157    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
158        let items_count = self.items.len();
159
160        let mut children = vec![];
161        for (ix, item) in self.items.into_iter().enumerate() {
162            let is_last = ix == items_count - 1;
163
164            let item = item.id(ix);
165            children.push(item.is_last(is_last).into_any_element());
166            if !is_last {
167                children.push(BreadcrumbSeparator.into_any_element());
168            }
169        }
170
171        h_flex()
172            .gap_1p5()
173            .text_sm()
174            .text_color(cx.theme().muted_foreground)
175            .refine_style(&self.style)
176            .children(children)
177    }
178}