Skip to main content

tui_lipan/widgets/
breadcrumb.rs

1//! Breadcrumb widget.
2
3use std::sync::Arc;
4
5use crate::callback::Callback;
6use crate::core::element::Element;
7use crate::core::event::MouseEvent;
8use crate::style::{Align, Justify, Length, Padding, Style, StyleSlot};
9use crate::widgets::{Button, HStack, Text};
10
11/// A breadcrumb navigation widget.
12#[derive(Clone)]
13pub struct Breadcrumb {
14    segments: Vec<Arc<str>>,
15    separator: Arc<str>,
16    gap: u16,
17    width: Length,
18    height: Length,
19    padding: Padding,
20    align: Align,
21    justify: Justify,
22    style: Style,
23    active: Option<usize>,
24    on_select: Option<Callback<usize>>,
25    active_style: Style,
26    inactive_style: Style,
27    hover_style: StyleSlot,
28    separator_style: Style,
29}
30
31impl Default for Breadcrumb {
32    fn default() -> Self {
33        Self {
34            segments: Vec::new(),
35            separator: "/".into(),
36            gap: 1,
37            width: Length::Flex(1),
38            height: Length::Auto,
39            padding: Padding::default(),
40            align: Align::Center,
41            justify: Justify::Start,
42            style: Style::default(),
43            active: None,
44            on_select: None,
45            active_style: Style::default(),
46            inactive_style: Style::default(),
47            hover_style: StyleSlot::Inherit,
48            separator_style: Style::default(),
49        }
50    }
51}
52
53impl Breadcrumb {
54    /// Create a new breadcrumb.
55    pub fn new() -> Self {
56        Self::default()
57    }
58
59    /// Set segments.
60    pub fn segments(mut self, segments: impl IntoIterator<Item = impl Into<Arc<str>>>) -> Self {
61        self.segments = segments.into_iter().map(Into::into).collect();
62        self
63    }
64
65    /// Set separator text.
66    pub fn separator(mut self, separator: impl Into<Arc<str>>) -> Self {
67        self.separator = separator.into();
68        self
69    }
70
71    /// Set gap between segments.
72    pub fn gap(mut self, gap: u16) -> Self {
73        self.gap = gap;
74        self
75    }
76
77    /// Set width.
78    pub fn width(mut self, width: Length) -> Self {
79        self.width = width;
80        self
81    }
82
83    /// Set height.
84    pub fn height(mut self, height: Length) -> Self {
85        self.height = height;
86        self
87    }
88
89    /// Set padding.
90    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
91        self.padding = padding.into();
92        self
93    }
94
95    /// Set cross-axis alignment.
96    pub fn align(mut self, align: Align) -> Self {
97        self.align = align;
98        self
99    }
100
101    /// Set main-axis alignment.
102    pub fn justify(mut self, justify: Justify) -> Self {
103        self.justify = justify;
104        self
105    }
106
107    /// Set base style for the breadcrumb container.
108    pub fn style(mut self, style: Style) -> Self {
109        self.style = style;
110        self
111    }
112
113    /// Set active segment index.
114    pub fn active(mut self, active: Option<usize>) -> Self {
115        self.active = active;
116        self
117    }
118
119    /// Set selection callback.
120    pub fn on_select(mut self, cb: Callback<usize>) -> Self {
121        self.on_select = Some(cb);
122        self
123    }
124
125    /// Set active segment style.
126    pub fn active_style(mut self, style: Style) -> Self {
127        self.active_style = style;
128        self
129    }
130
131    /// Set inactive segment style.
132    pub fn inactive_style(mut self, style: Style) -> Self {
133        self.inactive_style = style;
134        self
135    }
136
137    /// Set hover style.
138    pub fn hover_style(mut self, style: Style) -> Self {
139        self.hover_style = StyleSlot::Replace(style);
140        self
141    }
142
143    /// Extend the themed hover style.
144    pub fn extend_hover_style(mut self, style: Style) -> Self {
145        self.hover_style = StyleSlot::Extend(style);
146        self
147    }
148
149    /// Inherit the themed hover style.
150    pub fn inherit_hover_style(mut self) -> Self {
151        self.hover_style = StyleSlot::Inherit;
152        self
153    }
154
155    /// Set hover style slot directly for composite forwarding.
156    pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
157        self.hover_style = slot;
158        self
159    }
160
161    /// Set separator style.
162    pub fn separator_style(mut self, style: Style) -> Self {
163        self.separator_style = style;
164        self
165    }
166}
167
168impl From<Breadcrumb> for Element {
169    fn from(breadcrumb: Breadcrumb) -> Self {
170        let mut stack = HStack::new()
171            .gap(breadcrumb.gap)
172            .width(breadcrumb.width)
173            .height(breadcrumb.height)
174            .padding(breadcrumb.padding)
175            .align(breadcrumb.align)
176            .justify(breadcrumb.justify)
177            .style(breadcrumb.style);
178        let active_index = breadcrumb
179            .active
180            .unwrap_or_else(|| breadcrumb.segments.len().saturating_sub(1));
181
182        for (i, segment) in breadcrumb.segments.into_iter().enumerate() {
183            if i > 0 {
184                stack = stack.child(
185                    Text::new(breadcrumb.separator.clone()).style(breadcrumb.separator_style),
186                );
187            }
188
189            let is_active = i == active_index;
190            let style = if is_active {
191                breadcrumb.active_style
192            } else {
193                breadcrumb.inactive_style
194            };
195
196            if let Some(cb) = breadcrumb.on_select.clone() {
197                let mut button = Button::filled(segment)
198                    .padding(0)
199                    .style(style)
200                    .hover_style_slot(breadcrumb.hover_style)
201                    .width(Length::Auto);
202                button = button.on_click(Callback::new(move |_: MouseEvent| cb.emit(i)));
203                stack = stack.child(button);
204            } else {
205                stack = stack.child(Text::new(segment).style(style));
206            }
207        }
208
209        stack.into()
210    }
211}