gpui_component/
breadcrumb.rs1use 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#[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 .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 pub fn new() -> Self {
120 Self {
121 items: Vec::new(),
122 style: StyleRefinement::default(),
123 }
124 }
125
126 pub fn child(mut self, item: impl Into<BreadcrumbItem>) -> Self {
128 self.items.push(item.into());
129 self
130 }
131
132 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}