1use std::marker::PhantomData;
2use std::path::PathBuf;
3
4use gpui2::Div;
5
6use crate::prelude::*;
7use crate::{h_stack, HighlightedText};
8
9#[derive(Clone)]
10pub struct Symbol(pub Vec<HighlightedText>);
11
12#[derive(Element)]
13pub struct Breadcrumb<S: 'static + Send + Sync> {
14 state_type: PhantomData<S>,
15 path: PathBuf,
16 symbols: Vec<Symbol>,
17}
18
19impl<S: 'static + Send + Sync> Breadcrumb<S> {
20 pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
21 Self {
22 state_type: PhantomData,
23 path,
24 symbols,
25 }
26 }
27
28 fn render_separator(&self, cx: &WindowContext) -> Div<S> {
29 let theme = theme(cx);
30
31 div().child(" › ").text_color(theme.text_muted)
32 }
33
34 fn render(
35 &mut self,
36 view_state: &mut S,
37 cx: &mut ViewContext<S>,
38 ) -> impl Element<ViewState = S> {
39 let theme = theme(cx);
40
41 let symbols_len = self.symbols.len();
42
43 h_stack()
44 .id("breadcrumb")
45 .px_1()
46 .text_sm()
47 .text_color(theme.text_muted)
48 .rounded_md()
49 .hover(|style| style.bg(theme.ghost_element_hover))
50 .active(|style| style.bg(theme.ghost_element_active))
51 .child(self.path.clone().to_str().unwrap().to_string())
52 .child(if !self.symbols.is_empty() {
53 self.render_separator(cx)
54 } else {
55 div()
56 })
57 .child(
58 div().flex().children(
59 self.symbols
60 .iter()
61 .enumerate()
62 // TODO: Could use something like `intersperse` here instead.
63 .flat_map(|(ix, symbol)| {
64 let mut items =
65 vec![div().flex().children(symbol.0.iter().map(|segment| {
66 div().child(segment.text.clone()).text_color(segment.color)
67 }))];
68
69 let is_last_segment = ix == symbols_len - 1;
70 if !is_last_segment {
71 items.push(self.render_separator(cx));
72 }
73
74 items
75 })
76 .collect::<Vec<_>>(),
77 ),
78 )
79 }
80}
81
82#[cfg(feature = "stories")]
83pub use stories::*;
84
85#[cfg(feature = "stories")]
86mod stories {
87 use std::str::FromStr;
88
89 use crate::Story;
90
91 use super::*;
92
93 #[derive(Element)]
94 pub struct BreadcrumbStory<S: 'static + Send + Sync> {
95 state_type: PhantomData<S>,
96 }
97
98 impl<S: 'static + Send + Sync> BreadcrumbStory<S> {
99 pub fn new() -> Self {
100 Self {
101 state_type: PhantomData,
102 }
103 }
104
105 fn render(
106 &mut self,
107 view_state: &mut S,
108 cx: &mut ViewContext<S>,
109 ) -> impl Element<ViewState = S> {
110 let theme = theme(cx);
111
112 Story::container(cx)
113 .child(Story::title_for::<_, Breadcrumb<S>>(cx))
114 .child(Story::label(cx, "Default"))
115 .child(Breadcrumb::new(
116 PathBuf::from_str("crates/ui/src/components/toolbar.rs").unwrap(),
117 vec![
118 Symbol(vec![
119 HighlightedText {
120 text: "impl ".to_string(),
121 color: theme.syntax.keyword,
122 },
123 HighlightedText {
124 text: "BreadcrumbStory".to_string(),
125 color: theme.syntax.function,
126 },
127 ]),
128 Symbol(vec![
129 HighlightedText {
130 text: "fn ".to_string(),
131 color: theme.syntax.keyword,
132 },
133 HighlightedText {
134 text: "render".to_string(),
135 color: theme.syntax.function,
136 },
137 ]),
138 ],
139 ))
140 }
141 }
142}