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#[derive(IntoElement)]
12pub struct Breadcrumb {
13    style: StyleRefinement,
14    items: Vec<BreadcrumbItem>,
15}
16
17#[derive(IntoElement)]
18pub struct BreadcrumbItem {
19    id: ElementId,
20    style: StyleRefinement,
21    text: SharedString,
22    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App)>>,
23    disabled: bool,
24    is_last: bool,
25}
26
27impl BreadcrumbItem {
28    pub fn new(id: impl Into<ElementId>, text: impl Into<SharedString>) -> Self {
29        Self {
30            id: id.into(),
31            style: StyleRefinement::default(),
32            text: text.into(),
33            on_click: None,
34            disabled: false,
35            is_last: false,
36        }
37    }
38
39    pub fn disabled(mut self, disabled: bool) -> Self {
40        self.disabled = disabled;
41        self
42    }
43
44    pub fn on_click(
45        mut self,
46        on_click: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
47    ) -> Self {
48        self.on_click = Some(Rc::new(on_click));
49        self
50    }
51
52    /// For internal use only.
53    fn is_last(mut self, is_last: bool) -> Self {
54        self.is_last = is_last;
55        self
56    }
57}
58
59impl Styled for BreadcrumbItem {
60    fn style(&mut self) -> &mut StyleRefinement {
61        &mut self.style
62    }
63}
64
65impl RenderOnce for BreadcrumbItem {
66    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
67        div()
68            .id(self.id)
69            .child(self.text)
70            .text_color(cx.theme().muted_foreground)
71            .when(self.is_last, |this| this.text_color(cx.theme().foreground))
72            .when(self.disabled, |this| {
73                this.text_color(cx.theme().muted_foreground)
74            })
75            .refine_style(&self.style)
76            .when(!self.disabled, |this| {
77                this.when_some(self.on_click, |this, on_click| {
78                    this.cursor_pointer().on_click(move |event, window, cx| {
79                        on_click(event, window, cx);
80                    })
81                })
82            })
83    }
84}
85
86impl Breadcrumb {
87    pub fn new() -> Self {
88        Self {
89            items: Vec::new(),
90            style: StyleRefinement::default(),
91        }
92    }
93
94    /// Add an item to the breadcrumb.
95    pub fn item(mut self, item: BreadcrumbItem) -> Self {
96        self.items.push(item);
97        self
98    }
99}
100
101#[derive(IntoElement)]
102struct BreadcrumbSeparator;
103impl RenderOnce for BreadcrumbSeparator {
104    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
105        Icon::new(IconName::ChevronRight)
106            .text_color(cx.theme().muted_foreground)
107            .size_3p5()
108            .into_any_element()
109    }
110}
111
112impl Styled for Breadcrumb {
113    fn style(&mut self) -> &mut StyleRefinement {
114        &mut self.style
115    }
116}
117
118impl RenderOnce for Breadcrumb {
119    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
120        let items_count = self.items.len();
121
122        let mut children = vec![];
123        for (ix, item) in self.items.into_iter().enumerate() {
124            let is_last = ix == items_count - 1;
125
126            children.push(item.is_last(is_last).into_any_element());
127            if !is_last {
128                children.push(BreadcrumbSeparator.into_any_element());
129            }
130        }
131
132        h_flex()
133            .gap_1p5()
134            .text_sm()
135            .text_color(cx.theme().muted_foreground)
136            .refine_style(&self.style)
137            .children(children)
138    }
139}