1use super::{Widget, element::Element, scroll_core};
16use alloc::{boxed::Box, vec::Vec};
17use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
18use zest_core::{
19 Constraints, Length, RenderError, Renderer, ScrollDirection, ScrollMsg, ScrollState,
20 ScrollbarMode, SnapMode, TouchPhase, UiAction, WidgetId,
21};
22use zest_theme::Theme;
23
24struct ScrollCore<'a, M> {
26 state: ScrollState,
28 dir: ScrollDirection,
30 bar: ScrollbarMode,
32 snap: SnapMode,
34 on_scroll: Option<Box<dyn Fn(ScrollMsg) -> M + 'a>>,
36}
37
38pub struct Row<'a, C: PixelColor, M: Clone> {
40 rect: Rectangle,
41 children: Vec<Element<'a, C, M>>,
42 spacing: u32,
43 width: Length,
44 height: Length,
45 scroll: Option<ScrollCore<'a, M>>,
46 content_w: u32,
48 child_origins: Vec<Point>,
50 child_sizes: Vec<Size>,
52}
53
54impl<'a, C: PixelColor + 'a, M: Clone + 'a> Row<'a, C, M> {
55 pub fn new() -> Self {
58 Self {
59 rect: Rectangle::zero(),
60 children: Vec::new(),
61 spacing: 0,
62 width: Length::Fill,
63 height: Length::Fill,
64 scroll: None,
65 content_w: 0,
66 child_origins: Vec::new(),
67 child_sizes: Vec::new(),
68 }
69 }
70
71 #[must_use]
73 pub fn spacing(mut self, spacing: u32) -> Self {
74 self.spacing = spacing;
75 self
76 }
77
78 #[must_use]
80 pub fn width(mut self, width: impl Into<Length>) -> Self {
81 self.width = width.into();
82 self
83 }
84
85 #[must_use]
87 pub fn height(mut self, height: impl Into<Length>) -> Self {
88 self.height = height.into();
89 self
90 }
91
92 #[must_use]
94 pub fn push<W>(mut self, child: W) -> Self
95 where
96 W: Widget<C, M> + 'a,
97 {
98 self.children.push(Element::new(child));
99 self
100 }
101
102 #[must_use]
106 pub fn scrollable(mut self, dir: ScrollDirection) -> Self {
107 let core = self.scroll.get_or_insert(ScrollCore {
108 state: ScrollState::new(),
109 dir,
110 bar: ScrollbarMode::Auto,
111 snap: SnapMode::None,
112 on_scroll: None,
113 });
114 core.dir = dir;
115 self
116 }
117
118 #[must_use]
122 pub fn scroll_state(mut self, state: &ScrollState) -> Self {
123 let core = self.scroll.get_or_insert(ScrollCore {
124 state: *state,
125 dir: ScrollDirection::Horizontal,
126 bar: ScrollbarMode::Auto,
127 snap: SnapMode::None,
128 on_scroll: None,
129 });
130 core.state = *state;
131 self
132 }
133
134 #[must_use]
136 pub fn scrollbar(mut self, mode: ScrollbarMode) -> Self {
137 let core = self.scroll.get_or_insert(ScrollCore {
138 state: ScrollState::new(),
139 dir: ScrollDirection::Horizontal,
140 bar: mode,
141 snap: SnapMode::None,
142 on_scroll: None,
143 });
144 core.bar = mode;
145 self
146 }
147
148 #[must_use]
150 pub fn snap(mut self, mode: SnapMode) -> Self {
151 let core = self.scroll.get_or_insert(ScrollCore {
152 state: ScrollState::new(),
153 dir: ScrollDirection::Horizontal,
154 bar: ScrollbarMode::Auto,
155 snap: mode,
156 on_scroll: None,
157 });
158 core.snap = mode;
159 self
160 }
161
162 #[must_use]
165 pub fn on_scroll<F>(mut self, f: F) -> Self
166 where
167 F: Fn(ScrollMsg) -> M + 'a,
168 {
169 let core = self.scroll.get_or_insert(ScrollCore {
170 state: ScrollState::new(),
171 dir: ScrollDirection::Horizontal,
172 bar: ScrollbarMode::Auto,
173 snap: SnapMode::None,
174 on_scroll: None,
175 });
176 core.on_scroll = Some(Box::new(f));
177 self
178 }
179
180 fn snap_lines(&self) -> Vec<i32> {
183 match &self.scroll {
184 Some(core) if core.snap != SnapMode::None => {
185 let rects: Vec<Rectangle> = self
186 .child_origins
187 .iter()
188 .zip(self.child_sizes.iter())
189 .map(|(p, s)| Rectangle::new(*p, *s))
190 .collect();
191 let offset = scroll_core::render_offset(core.state, core.dir);
192 scroll_core::snap_lines(
193 &rects,
194 self.rect.top_left,
195 offset,
196 self.rect.size,
197 core.dir,
198 core.snap,
199 )
200 }
201 _ => Vec::new(),
202 }
203 }
204
205 fn relayout(&mut self) {
208 let n = self.children.len();
209 if n == 0 {
210 return;
211 }
212 let avail_h = self.rect.size.height;
213 let total_spacing = self.spacing.saturating_mul(n as u32 - 1);
214 let avail_w = self.rect.size.width.saturating_sub(total_spacing);
215
216 let cross = Constraints::loose(Size::new(avail_w, avail_h));
217 let mut widths: Vec<u32> = Vec::with_capacity(n);
218 let mut fixed_total: u32 = 0;
219 let mut shrink_total: u32 = 0;
220 let mut portion_total: u32 = 0;
221
222 for child in &mut self.children {
223 let (w_intent, _) = child.preferred_size();
224 let w = match w_intent {
225 Length::Fixed(px) => {
226 fixed_total = fixed_total.saturating_add(px);
227 px
228 }
229 Length::Shrink => {
230 let m = child.measure(cross).width;
231 shrink_total = shrink_total.saturating_add(m);
232 m
233 }
234 Length::Fill | Length::FillPortion(_) => {
235 portion_total = portion_total.saturating_add(w_intent.portion());
236 0
237 }
238 };
239 widths.push(w);
240 }
241
242 let consumed = fixed_total.saturating_add(shrink_total);
243 let remaining = avail_w.saturating_sub(consumed);
244 if portion_total > 0 {
245 let unit = remaining / portion_total;
246 for (child, w) in self.children.iter_mut().zip(widths.iter_mut()) {
247 let (w_intent, _) = child.preferred_size();
248 if w_intent == Length::Fill || matches!(w_intent, Length::FillPortion(_)) {
249 *w = unit.saturating_mul(w_intent.portion());
250 }
251 }
252 }
253
254 let mut x = self.rect.top_left.x;
255 for (child, w) in self.children.iter_mut().zip(widths.iter()) {
256 let cell = Rectangle::new(Point::new(x, self.rect.top_left.y), Size::new(*w, avail_h));
257 child.arrange(cell);
258 x += *w as i32 + self.spacing as i32;
259 }
260 }
261
262 fn relayout_scroll(&mut self, dir: ScrollDirection) {
265 let n = self.children.len();
266 self.child_origins.clear();
267 self.child_sizes.clear();
268 if n == 0 {
269 self.content_w = 0;
270 return;
271 }
272 let scrolls_x = dir.scrolls_x();
273 let avail_h = self.rect.size.height;
276 let spacing = self.spacing;
277
278 let measure_w = if scrolls_x {
281 zest_core::UNBOUNDED
282 } else {
283 self.rect
284 .size
285 .width
286 .saturating_sub(spacing.saturating_mul(n as u32 - 1))
287 };
288 let cross = Constraints::loose(Size::new(measure_w, avail_h));
289
290 let mut widths: Vec<u32> = Vec::with_capacity(n);
291 for child in &mut self.children {
292 let (w_intent, _) = child.preferred_size();
293 let w = match w_intent {
294 Length::Fixed(px) => px,
295 _ => child.measure(cross).width,
296 };
297 widths.push(w);
298 }
299
300 let total_spacing = spacing.saturating_mul(n as u32 - 1);
301 let content_w: u32 = widths
302 .iter()
303 .copied()
304 .sum::<u32>()
305 .saturating_add(total_spacing);
306 self.content_w = content_w;
307
308 let off = self
310 .scroll
311 .as_ref()
312 .map_or(Point::zero(), |c| scroll_core::render_offset(c.state, dir));
313
314 let mut x = self.rect.top_left.x - off.x;
315 let y = self.rect.top_left.y - off.y;
316 for (child, w) in self.children.iter_mut().zip(widths.iter()) {
317 let origin = Point::new(x, y);
318 let size = Size::new(*w, avail_h);
319 child.arrange(Rectangle::new(origin, size));
320 self.child_origins.push(origin);
321 self.child_sizes.push(size);
322 x += *w as i32 + spacing as i32;
323 }
324 }
325}
326
327impl<'a, C: PixelColor + 'a, M: Clone + 'a> Default for Row<'a, C, M> {
328 fn default() -> Self {
329 Self::new()
330 }
331}
332
333impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Row<'a, C, M> {
334 fn measure(&mut self, constraints: Constraints) -> Size {
335 let w = self
336 .width
337 .resolve(constraints.max.width, constraints.max.width);
338 let h = self
339 .height
340 .resolve(constraints.max.height, constraints.max.height);
341 constraints.clamp(Size::new(w, h))
342 }
343
344 fn preferred_size(&self) -> (Length, Length) {
345 (self.width, self.height)
346 }
347
348 fn arrange(&mut self, rect: Rectangle) {
349 self.rect = rect;
350 match self.scroll.as_ref().map(|c| c.dir) {
351 Some(dir) if dir != ScrollDirection::None => self.relayout_scroll(dir),
352 _ => self.relayout(),
353 }
354 }
355
356 fn rect(&self) -> Rectangle {
357 self.rect
358 }
359
360 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
361 match self.scroll.as_ref() {
362 Some(core) if core.dir != ScrollDirection::None => {
363 let dir = core.dir;
364 let state = core.state;
365 let viewport = self.rect;
366 let content = Size::new(self.content_w, self.rect.size.height);
367 let lines = self.snap_lines();
368 let on_scroll = self.scroll.as_ref().and_then(|c| c.on_scroll.as_deref());
371 let children = &mut self.children;
372 scroll_core::route_touch(
373 state,
374 dir,
375 viewport,
376 content,
377 point,
378 phase,
379 &lines,
380 on_scroll,
381 |p, ph| {
382 for child in children.iter_mut().rev() {
383 if let Some(msg) = child.handle_touch(p, ph) {
384 return Some(msg);
385 }
386 }
387 None
388 },
389 )
390 }
391 _ => {
392 for child in self.children.iter_mut().rev() {
393 if let Some(msg) = child.handle_touch(point, phase) {
394 return Some(msg);
395 }
396 }
397 None
398 }
399 }
400 }
401
402 fn mark_pressed(&mut self, point: Point) {
403 if let Some(core) = self.scroll.as_ref() {
406 if matches!(
407 core.state.phase,
408 zest_core::GesturePhase::Dragging
409 | zest_core::GesturePhase::Flinging
410 | zest_core::GesturePhase::Springing
411 ) {
412 return;
413 }
414 }
415 for child in &mut self.children {
416 child.mark_pressed(point);
417 }
418 }
419
420 fn collect_focusable(&self, out: &mut Vec<WidgetId>) {
421 for child in &self.children {
422 child.collect_focusable(out);
423 }
424 }
425
426 fn sync_focus(&mut self, focused: Option<WidgetId>) {
427 for child in &mut self.children {
428 child.sync_focus(focused);
429 }
430 }
431
432 fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
433 for child in &mut self.children {
434 if let Some(msg) = child.route_action(target, action) {
435 return Some(msg);
436 }
437 }
438 None
439 }
440
441 fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
442 for child in &self.children {
443 if let Some(next) = child.navigate_focus(target, action) {
444 return Some(next);
445 }
446 }
447 None
448 }
449
450 fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
451 for child in &self.children {
452 if let Some(rect) = child.focus_rect(target) {
453 return Some(rect);
454 }
455 }
456 None
457 }
458
459 fn focus_at(&self, point: Point) -> Option<WidgetId> {
460 for child in self.children.iter().rev() {
461 if let Some(id) = child.focus_at(point) {
462 return Some(id);
463 }
464 }
465 None
466 }
467
468 fn draw<'t>(
469 &self,
470 renderer: &mut dyn Renderer<C>,
471 theme: &Theme<'t, C>,
472 ) -> Result<(), RenderError> {
473 match self.scroll.as_ref() {
474 Some(core) if core.dir != ScrollDirection::None => {
475 let viewport = self.rect;
476 renderer.push_clip(viewport);
477 for child in &self.children {
478 child.draw(renderer, theme)?;
479 }
480 renderer.pop_clip();
481 let content = Size::new(self.content_w, self.rect.size.height);
482 scroll_core::draw_scrollbars(
483 renderer, theme, core.state, core.bar, core.dir, viewport, content,
484 )?;
485 Ok(())
486 }
487 _ => {
488 for child in &self.children {
489 child.draw(renderer, theme)?;
490 }
491 Ok(())
492 }
493 }
494 }
495}