1use super::{Widget, button::Button, element::Element, row::Row};
7use alloc::{string::String, vec::Vec};
8use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
9use zest_core::{Constraints, Length, RenderError, Renderer, TouchPhase, UiAction, WidgetId};
10use zest_theme::{ButtonClass, Theme};
11
12pub const DEFAULT_HEIGHT: u32 = 20;
14
15#[derive(Clone)]
16pub struct Tab<M> {
18 pub label: String,
20 pub message: M,
22 pub active: bool,
25}
26
27impl<M> Tab<M> {
28 pub fn new(label: impl Into<String>, message: M, active: bool) -> Self {
30 Self {
31 label: label.into(),
32 message,
33 active,
34 }
35 }
36}
37
38pub struct TabBar<'a, C: PixelColor, M: Clone> {
44 id: Option<WidgetId>,
45 tabs: Vec<Tab<M>>,
46 spacing: u32,
47 inner: Option<Row<'a, C, M>>,
48 width: Length,
49 height: Length,
50}
51
52impl<'a, C: PixelColor + 'a, M: Clone + 'a> TabBar<'a, C, M> {
53 pub fn new<I>(tabs: I) -> Self
55 where
56 I: IntoIterator<Item = Tab<M>>,
57 {
58 Self {
59 id: None,
60 tabs: tabs.into_iter().collect(),
61 spacing: 2,
62 inner: None,
63 width: Length::Fill,
64 height: Length::Fixed(DEFAULT_HEIGHT),
65 }
66 }
67
68 #[must_use]
70 pub fn width(mut self, width: impl Into<Length>) -> Self {
71 self.width = width.into();
72 self
73 }
74
75 #[must_use]
77 pub fn height(mut self, height: impl Into<Length>) -> Self {
78 self.height = height.into();
79 self
80 }
81
82 #[must_use]
84 pub fn id(mut self, id: WidgetId) -> Self {
85 self.id = Some(id);
86 self
87 }
88
89 #[must_use]
91 pub fn spacing(mut self, spacing: u32) -> Self {
92 self.spacing = spacing;
93 self
94 }
95
96 fn build_inner(&self) -> Row<'a, C, M> {
97 let mut row = Row::new().spacing(self.spacing);
98 for (index, tab) in self.tabs.iter().enumerate() {
99 let class = if tab.active {
100 ButtonClass::Suggested
101 } else {
102 ButtonClass::Standard
103 };
104 let mut button = Button::new(tab.label.clone())
105 .on_press(tab.message.clone())
106 .class(class);
107 if let Some(id) = self.tab_id(index) {
108 button = button.id(id);
109 }
110 row = row.push(button);
111 }
112 row
113 }
114
115 fn ensure_built(&mut self) {
116 if self.inner.is_none() {
117 self.inner = Some(self.build_inner());
118 }
119 }
120
121 fn tab_id(&self, index: usize) -> Option<WidgetId> {
122 self.id
123 .map(|id| WidgetId::new(id.raw().wrapping_add(index as u64 + 1)))
124 }
125}
126
127impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for TabBar<'a, C, M> {
128 fn measure(&mut self, constraints: Constraints) -> Size {
129 let w = self
130 .width
131 .resolve(constraints.max.width, constraints.max.width);
132 let h = self.height.resolve(DEFAULT_HEIGHT, constraints.max.height);
133 constraints.clamp(Size::new(w, h))
134 }
135
136 fn preferred_size(&self) -> (Length, Length) {
137 (self.width, self.height)
138 }
139
140 fn arrange(&mut self, rect: Rectangle) {
141 self.ensure_built();
142 if let Some(inner) = self.inner.as_mut() {
143 inner.arrange(rect);
144 }
145 }
146
147 fn rect(&self) -> Rectangle {
148 self.inner.as_ref().map_or(Rectangle::zero(), Widget::rect)
149 }
150
151 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
152 self.ensure_built();
153 self.inner
154 .as_mut()
155 .and_then(|inner| Widget::<C, M>::handle_touch(inner, point, phase))
156 }
157
158 fn mark_pressed(&mut self, point: Point) {
159 self.ensure_built();
160 if let Some(inner) = self.inner.as_mut() {
161 Widget::<C, M>::mark_pressed(inner, point);
162 }
163 }
164
165 fn collect_focusable(&self, out: &mut Vec<WidgetId>) {
166 for index in 0..self.tabs.len() {
167 if let Some(id) = self.tab_id(index) {
168 out.push(id);
169 }
170 }
171 }
172
173 fn sync_focus(&mut self, focused: Option<WidgetId>) {
174 self.ensure_built();
175 if let Some(inner) = self.inner.as_mut() {
176 inner.sync_focus(focused);
177 }
178 }
179
180 fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
181 self.ensure_built();
182 self.inner
183 .as_mut()
184 .and_then(|inner| inner.route_action(target, action))
185 }
186
187 fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
188 self.inner
189 .as_ref()
190 .and_then(|inner| inner.navigate_focus(target, action))
191 }
192
193 fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
194 self.inner
195 .as_ref()
196 .and_then(|inner| inner.focus_rect(target))
197 }
198
199 fn focus_at(&self, point: Point) -> Option<WidgetId> {
200 self.inner.as_ref().and_then(|inner| inner.focus_at(point))
201 }
202
203 fn draw<'t>(
204 &self,
205 renderer: &mut dyn Renderer<C>,
206 theme: &Theme<'t, C>,
207 ) -> Result<(), RenderError> {
208 if let Some(inner) = self.inner.as_ref() {
209 Widget::<C, M>::draw(inner, renderer, theme)
210 } else {
211 Ok(())
212 }
213 }
214}
215
216impl<'a, C: PixelColor + 'a, M: Clone + 'a> From<TabBar<'a, C, M>> for Element<'a, C, M> {
217 fn from(bar: TabBar<'a, C, M>) -> Self {
218 Element::new(bar)
219 }
220}