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