1use super::{Widget, element::Element, scroll_core};
18use alloc::boxed::Box;
19use embedded_graphics::{pixelcolor::PixelColor, prelude::*, primitives::Rectangle};
20use zest_core::{
21 Constraints, Length, RenderError, Renderer, ScrollDirection, ScrollMsg, ScrollState,
22 ScrollbarMode, SnapMode, TouchPhase, UiAction, WidgetId,
23};
24use zest_theme::Theme;
25
26struct ScrollCore<'a, M> {
28 state: ScrollState,
30 dir: ScrollDirection,
32 bar: ScrollbarMode,
34 snap: SnapMode,
36 on_scroll: Option<Box<dyn Fn(ScrollMsg) -> M + 'a>>,
38}
39
40pub struct Container<'a, C: PixelColor, M: Clone> {
42 rect: Rectangle,
43 padding: u32,
44 child: Option<Element<'a, C, M>>,
45 width: Length,
46 height: Length,
47 scroll: Option<ScrollCore<'a, M>>,
48 content: Size,
50}
51
52impl<'a, C: PixelColor + 'a, M: Clone + 'a> Container<'a, C, M> {
53 pub fn new() -> Self {
57 Self {
58 rect: Rectangle::zero(),
59 padding: 0,
60 child: None,
61 width: Length::Fill,
62 height: Length::Fill,
63 scroll: None,
64 content: Size::zero(),
65 }
66 }
67
68 #[must_use]
70 pub fn padding(mut self, padding: u32) -> Self {
71 self.padding = padding;
72 self
73 }
74
75 #[must_use]
77 pub fn width(mut self, width: impl Into<Length>) -> Self {
78 self.width = width.into();
79 self
80 }
81
82 #[must_use]
84 pub fn height(mut self, height: impl Into<Length>) -> Self {
85 self.height = height.into();
86 self
87 }
88
89 #[must_use]
92 pub fn child<W>(mut self, child: W) -> Self
93 where
94 W: Widget<C, M> + 'a,
95 {
96 self.child = Some(Element::new(child));
97 self
98 }
99
100 #[must_use]
104 pub fn scrollable(mut self, dir: ScrollDirection) -> Self {
105 let core = self.scroll.get_or_insert(ScrollCore {
106 state: ScrollState::new(),
107 dir,
108 bar: ScrollbarMode::Auto,
109 snap: SnapMode::None,
110 on_scroll: None,
111 });
112 core.dir = dir;
113 self
114 }
115
116 #[must_use]
120 pub fn scroll_state(mut self, state: &ScrollState) -> Self {
121 let core = self.scroll.get_or_insert(ScrollCore {
122 state: *state,
123 dir: ScrollDirection::Vertical,
124 bar: ScrollbarMode::Auto,
125 snap: SnapMode::None,
126 on_scroll: None,
127 });
128 core.state = *state;
129 self
130 }
131
132 #[must_use]
134 pub fn scrollbar(mut self, mode: ScrollbarMode) -> Self {
135 let core = self.scroll.get_or_insert(ScrollCore {
136 state: ScrollState::new(),
137 dir: ScrollDirection::Vertical,
138 bar: mode,
139 snap: SnapMode::None,
140 on_scroll: None,
141 });
142 core.bar = mode;
143 self
144 }
145
146 #[must_use]
148 pub fn snap(mut self, mode: SnapMode) -> Self {
149 let core = self.scroll.get_or_insert(ScrollCore {
150 state: ScrollState::new(),
151 dir: ScrollDirection::Vertical,
152 bar: ScrollbarMode::Auto,
153 snap: mode,
154 on_scroll: None,
155 });
156 core.snap = mode;
157 self
158 }
159
160 #[must_use]
163 pub fn on_scroll<F>(mut self, f: F) -> Self
164 where
165 F: Fn(ScrollMsg) -> M + 'a,
166 {
167 let core = self.scroll.get_or_insert(ScrollCore {
168 state: ScrollState::new(),
169 dir: ScrollDirection::Vertical,
170 bar: ScrollbarMode::Auto,
171 snap: SnapMode::None,
172 on_scroll: None,
173 });
174 core.on_scroll = Some(Box::new(f));
175 self
176 }
177
178 fn inner_rect(&self) -> Rectangle {
179 let pad = self.padding as i32;
180 let pad_u = self.padding;
181 Rectangle::new(
182 self.rect.top_left + Point::new(pad, pad),
183 Size::new(
184 self.rect.size.width.saturating_sub(pad_u * 2),
185 self.rect.size.height.saturating_sub(pad_u * 2),
186 ),
187 )
188 }
189
190 fn snap_lines(&self) -> alloc::vec::Vec<i32> {
193 match &self.scroll {
194 Some(core) if core.snap != SnapMode::None => {
195 let viewport = self.inner_rect();
196 let off = scroll_core::render_offset(core.state, core.dir);
197 let rect = Rectangle::new(viewport.top_left, self.content);
199 scroll_core::snap_lines(
200 &[rect],
201 viewport.top_left,
202 off,
203 viewport.size,
204 core.dir,
205 core.snap,
206 )
207 }
208 _ => alloc::vec::Vec::new(),
209 }
210 }
211
212 fn arrange_scroll(&mut self, dir: ScrollDirection) {
215 let viewport = self.inner_rect();
216 let measure_w = if dir.scrolls_x() {
217 zest_core::UNBOUNDED
218 } else {
219 viewport.size.width
220 };
221 let measure_h = if dir.scrolls_y() {
222 zest_core::UNBOUNDED
223 } else {
224 viewport.size.height
225 };
226 let off = self
227 .scroll
228 .as_ref()
229 .map_or(Point::zero(), |c| scroll_core::render_offset(c.state, dir));
230 if let Some(child) = self.child.as_mut() {
231 let measured = child.measure(Constraints::loose(Size::new(measure_w, measure_h)));
232 let content = Size::new(
235 if dir.scrolls_x() {
236 measured.width.max(viewport.size.width)
237 } else {
238 viewport.size.width
239 },
240 if dir.scrolls_y() {
241 measured.height.max(viewport.size.height)
242 } else {
243 viewport.size.height
244 },
245 );
246 self.content = content;
247 child.arrange(Rectangle::new(viewport.top_left - off, content));
248 } else {
249 self.content = Size::zero();
250 }
251 }
252}
253
254impl<'a, C: PixelColor + 'a, M: Clone + 'a> Default for Container<'a, C, M> {
255 fn default() -> Self {
256 Self::new()
257 }
258}
259
260impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Container<'a, C, M> {
261 fn measure(&mut self, constraints: Constraints) -> Size {
262 let dx = 2 * self.padding;
263 let dy = 2 * self.padding;
264 let inner = constraints.shrink(dx, dy);
265 let child_size = self
266 .child
267 .as_mut()
268 .map_or(Size::zero(), |c| c.measure(inner));
269 let intrinsic_w = child_size.width.saturating_add(dx);
270 let intrinsic_h = child_size.height.saturating_add(dy);
271 let w = self.width.resolve(intrinsic_w, constraints.max.width);
272 let h = self.height.resolve(intrinsic_h, constraints.max.height);
273 constraints.clamp(Size::new(w, h))
274 }
275
276 fn preferred_size(&self) -> (Length, Length) {
277 (self.width, self.height)
278 }
279
280 fn arrange(&mut self, rect: Rectangle) {
281 self.rect = rect;
282 match self.scroll.as_ref().map(|c| c.dir) {
283 Some(dir) if dir != ScrollDirection::None => self.arrange_scroll(dir),
284 _ => {
285 let inner = self.inner_rect();
286 if let Some(child) = self.child.as_mut() {
287 child.arrange(inner);
288 }
289 }
290 }
291 }
292
293 fn rect(&self) -> Rectangle {
294 self.rect
295 }
296
297 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
298 match self.scroll.as_ref() {
299 Some(core) if core.dir != ScrollDirection::None => {
300 let dir = core.dir;
301 let state = core.state;
302 let viewport = self.inner_rect();
303 let content = self.content;
304 let lines = self.snap_lines();
305 let on_scroll = self.scroll.as_ref().and_then(|c| c.on_scroll.as_deref());
306 let child = &mut self.child;
307 scroll_core::route_touch(
308 state,
309 dir,
310 viewport,
311 content,
312 point,
313 phase,
314 &lines,
315 on_scroll,
316 |p, ph| child.as_mut().and_then(|c| c.handle_touch(p, ph)),
317 )
318 }
319 _ => self
320 .child
321 .as_mut()
322 .and_then(|child| child.handle_touch(point, phase)),
323 }
324 }
325
326 fn mark_pressed(&mut self, point: Point) {
327 if let Some(core) = self.scroll.as_ref() {
330 if matches!(
331 core.state.phase,
332 zest_core::GesturePhase::Dragging
333 | zest_core::GesturePhase::Flinging
334 | zest_core::GesturePhase::Springing
335 ) {
336 return;
337 }
338 }
339 if let Some(child) = self.child.as_mut() {
340 child.mark_pressed(point);
341 }
342 }
343
344 fn collect_focusable(&self, out: &mut alloc::vec::Vec<WidgetId>) {
345 if let Some(child) = self.child.as_ref() {
346 child.collect_focusable(out);
347 }
348 }
349
350 fn sync_focus(&mut self, focused: Option<WidgetId>) {
351 if let Some(child) = self.child.as_mut() {
352 child.sync_focus(focused);
353 }
354 }
355
356 fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
357 self.child
358 .as_mut()
359 .and_then(|child| child.route_action(target, action))
360 }
361
362 fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
363 self.child
364 .as_ref()
365 .and_then(|child| child.navigate_focus(target, action))
366 }
367
368 fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
369 self.child
370 .as_ref()
371 .and_then(|child| child.focus_rect(target))
372 }
373
374 fn focus_at(&self, point: Point) -> Option<WidgetId> {
375 self.child.as_ref().and_then(|child| child.focus_at(point))
376 }
377
378 fn draw<'t>(
379 &self,
380 renderer: &mut dyn Renderer<C>,
381 theme: &Theme<'t, C>,
382 ) -> Result<(), RenderError> {
383 match self.scroll.as_ref() {
384 Some(core) if core.dir != ScrollDirection::None => {
385 let viewport = self.inner_rect();
386 renderer.push_clip(viewport);
387 if let Some(child) = &self.child {
388 child.draw(renderer, theme)?;
389 }
390 renderer.pop_clip();
391 scroll_core::draw_scrollbars(
392 renderer,
393 theme,
394 core.state,
395 core.bar,
396 core.dir,
397 viewport,
398 self.content,
399 )?;
400 Ok(())
401 }
402 _ => {
403 if let Some(child) = &self.child {
404 child.draw(renderer, theme)?;
405 }
406 Ok(())
407 }
408 }
409 }
410}