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