gpui_component/
breadcrumb.rs

1use std::rc::Rc;
2
3use gpui::{
4    div, prelude::FluentBuilder as _, App, ClickEvent, ElementId, InteractiveElement as _,
5    IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement,
6    StyleRefinement, Styled, Window,
7};
8
9use crate::{h_flex, ActiveTheme, Icon, IconName, StyledExt};
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            .child(self.label)
96            .text_color(cx.theme().muted_foreground)
97            .when(self.is_last, |this| this.text_color(cx.theme().foreground))
98            .when(self.disabled, |this| {
99                this.text_color(cx.theme().muted_foreground)
100            })
101            .refine_style(&self.style)
102            .when(!self.disabled, |this| {
103                this.when_some(self.on_click, |this, on_click| {
104                    this.cursor_pointer().on_click(move |event, window, cx| {
105                        on_click(event, window, cx);
106                    })
107                })
108            })
109    }
110}
111
112impl Breadcrumb {
113    /// Create a new breadcrumb.
114    pub fn new() -> Self {
115        Self {
116            items: Vec::new(),
117            style: StyleRefinement::default(),
118        }
119    }
120
121    /// Add an [`BreadcrumbItem`] to the breadcrumb.
122    pub fn child(mut self, item: impl Into<BreadcrumbItem>) -> Self {
123        self.items.push(item.into());
124        self
125    }
126
127    /// Add multiple [`BreadcrumbItem`] items to the breadcrumb.
128    pub fn children(mut self, items: impl IntoIterator<Item = impl Into<BreadcrumbItem>>) -> Self {
129        self.items.extend(items.into_iter().map(Into::into));
130        self
131    }
132}
133
134#[derive(IntoElement)]
135struct BreadcrumbSeparator;
136impl RenderOnce for BreadcrumbSeparator {
137    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
138        Icon::new(IconName::ChevronRight)
139            .text_color(cx.theme().muted_foreground)
140            .size_3p5()
141            .into_any_element()
142    }
143}
144
145impl Styled for Breadcrumb {
146    fn style(&mut self) -> &mut StyleRefinement {
147        &mut self.style
148    }
149}
150
151impl RenderOnce for Breadcrumb {
152    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
153        let items_count = self.items.len();
154
155        let mut children = vec![];
156        for (ix, item) in self.items.into_iter().enumerate() {
157            let is_last = ix == items_count - 1;
158
159            let item = item.id(ix);
160            children.push(item.is_last(is_last).into_any_element());
161            if !is_last {
162                children.push(BreadcrumbSeparator.into_any_element());
163            }
164        }
165
166        h_flex()
167            .gap_1p5()
168            .text_sm()
169            .text_color(cx.theme().muted_foreground)
170            .refine_style(&self.style)
171            .children(children)
172    }
173}