gpui_component/
breadcrumb.rs1use 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)]
13pub struct Breadcrumb {
14 style: StyleRefinement,
15 items: Vec<BreadcrumbItem>,
16}
17
18#[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 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 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 pub fn new() -> Self {
115 Self {
116 items: Vec::new(),
117 style: StyleRefinement::default(),
118 }
119 }
120
121 pub fn child(mut self, item: impl Into<BreadcrumbItem>) -> Self {
123 self.items.push(item.into());
124 self
125 }
126
127 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}