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