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, GesturePhase, Length, RenderError, Renderer, ScrollDirection, ScrollMsg,
20 ScrollState, ScrollbarMode, SnapMode, TouchPhase, UNBOUNDED, UiAction, WidgetId,
21};
22use zest_theme::Theme;
23
24pub struct Scrollable<'a, C: PixelColor, M: Clone> {
26 rect: Rectangle,
27 child: Element<'a, C, M>,
28 dir: ScrollDirection,
29 bar: ScrollbarMode,
30 snap: SnapMode,
31 state: ScrollState,
32 on_scroll: Option<Box<dyn Fn(ScrollMsg) -> M + 'a>>,
33 width: Length,
34 height: Length,
35 content: Size,
37 child_origin: Point,
39 child_size: Size,
40}
41
42impl<'a, C: PixelColor + 'a, M: Clone + 'a> Scrollable<'a, C, M> {
43 pub fn new<W>(child: W) -> Self
46 where
47 W: Widget<C, M> + 'a,
48 {
49 Self {
50 rect: Rectangle::zero(),
51 child: Element::new(child),
52 dir: ScrollDirection::Vertical,
53 bar: ScrollbarMode::Auto,
54 snap: SnapMode::None,
55 state: ScrollState::new(),
56 on_scroll: None,
57 width: Length::Fill,
58 height: Length::Fill,
59 content: Size::zero(),
60 child_origin: Point::zero(),
61 child_size: Size::zero(),
62 }
63 }
64
65 #[must_use]
67 pub fn direction(mut self, dir: ScrollDirection) -> Self {
68 self.dir = dir;
69 self
70 }
71
72 #[must_use]
74 pub fn scroll_state(mut self, state: &ScrollState) -> Self {
75 self.state = *state;
76 self
77 }
78
79 #[must_use]
81 pub fn scrollbar(mut self, mode: ScrollbarMode) -> Self {
82 self.bar = mode;
83 self
84 }
85
86 #[must_use]
88 pub fn snap(mut self, mode: SnapMode) -> Self {
89 self.snap = mode;
90 self
91 }
92
93 #[must_use]
97 pub fn on_scroll<F>(mut self, f: F) -> Self
98 where
99 F: Fn(ScrollMsg) -> M + 'a,
100 {
101 self.on_scroll = Some(Box::new(f));
102 self
103 }
104
105 #[must_use]
107 pub fn width(mut self, w: impl Into<Length>) -> Self {
108 self.width = w.into();
109 self
110 }
111
112 #[must_use]
114 pub fn height(mut self, h: impl Into<Length>) -> Self {
115 self.height = h.into();
116 self
117 }
118
119 fn snap_lines(&self) -> Vec<i32> {
120 if self.snap == SnapMode::None {
121 return Vec::new();
122 }
123 let offset = scroll_core::render_offset(self.state, self.dir);
124 scroll_core::snap_lines(
125 &[Rectangle::new(self.child_origin, self.child_size)],
126 self.rect.top_left,
127 offset,
128 self.rect.size,
129 self.dir,
130 self.snap,
131 )
132 }
133}
134
135impl<'a, C: PixelColor + 'a, M: Clone + 'a> Widget<C, M> for Scrollable<'a, C, M> {
136 fn measure(&mut self, constraints: Constraints) -> Size {
137 let w = self
138 .width
139 .resolve(constraints.max.width, constraints.max.width);
140 let h = self
141 .height
142 .resolve(constraints.max.height, constraints.max.height);
143 constraints.clamp(Size::new(w, h))
144 }
145
146 fn preferred_size(&self) -> (Length, Length) {
147 (self.width, self.height)
148 }
149
150 fn arrange(&mut self, rect: Rectangle) {
151 self.rect = rect;
152 let dir = self.dir;
153 let (vw, vh) = (rect.size.width, rect.size.height);
154 let max_w = if dir.scrolls_x() { UNBOUNDED } else { vw };
157 let max_h = if dir.scrolls_y() { UNBOUNDED } else { vh };
158 let m = self
159 .child
160 .measure(Constraints::loose(Size::new(max_w, max_h)));
161 self.content = Size::new(
162 if dir.scrolls_x() { m.width } else { vw },
163 if dir.scrolls_y() { m.height } else { vh },
164 );
165 let off = scroll_core::render_offset(self.state, dir);
166 let size = Size::new(self.content.width.max(vw), self.content.height.max(vh));
167 self.child
168 .arrange(Rectangle::new(rect.top_left - off, size));
169 self.child_origin = rect.top_left;
170 self.child_size = size;
171 }
172
173 fn rect(&self) -> Rectangle {
174 self.rect
175 }
176
177 fn handle_touch(&mut self, point: Point, phase: TouchPhase) -> Option<M> {
178 let state = self.state;
179 let dir = self.dir;
180 let viewport = self.rect;
181 let content = self.content;
182 let lines = self.snap_lines();
183 let on_scroll = self.on_scroll.as_deref();
184 let child = &mut self.child;
185 scroll_core::route_touch(
186 state,
187 dir,
188 viewport,
189 content,
190 point,
191 phase,
192 &lines,
193 on_scroll,
194 |p, ph| child.handle_touch(p, ph),
195 )
196 }
197
198 fn mark_pressed(&mut self, point: Point) {
199 if matches!(
201 self.state.phase,
202 GesturePhase::Dragging | GesturePhase::Flinging | GesturePhase::Springing
203 ) {
204 return;
205 }
206 self.child.mark_pressed(point);
207 }
208
209 fn draw<'t>(
210 &self,
211 renderer: &mut dyn Renderer<C>,
212 theme: &Theme<'t, C>,
213 ) -> Result<(), RenderError> {
214 renderer.push_clip(self.rect);
215 self.child.draw(renderer, theme)?;
216 renderer.pop_clip();
217 scroll_core::draw_scrollbars(
218 renderer,
219 theme,
220 self.state,
221 self.bar,
222 self.dir,
223 self.rect,
224 self.content,
225 )
226 }
227
228 fn collect_focusable(&self, out: &mut Vec<WidgetId>) {
229 self.child.collect_focusable(out);
230 }
231
232 fn sync_focus(&mut self, focused: Option<WidgetId>) {
233 self.child.sync_focus(focused);
234 }
235
236 fn route_action(&mut self, target: WidgetId, action: UiAction) -> Option<M> {
237 self.child.route_action(target, action)
238 }
239
240 fn navigate_focus(&self, target: WidgetId, action: UiAction) -> Option<WidgetId> {
241 self.child.navigate_focus(target, action)
242 }
243
244 fn focus_rect(&self, target: WidgetId) -> Option<Rectangle> {
245 self.child.focus_rect(target)
246 }
247
248 fn focus_at(&self, point: Point) -> Option<WidgetId> {
249 self.child.focus_at(point)
250 }
251}