anathema_default_widgets/
overflow.rs1use std::ops::ControlFlow;
2
3use anathema_geometry::{Pos, Size};
4use anathema_value_resolver::AttributeStorage;
5use anathema_widgets::error::Result;
6use anathema_widgets::layout::{Constraints, LayoutCtx, PositionCtx};
7use anathema_widgets::paint::{PaintCtx, SizePos};
8use anathema_widgets::{LayoutForEach, PaintChildren, PositionChildren, Widget, WidgetId};
9
10use crate::layout::many::Many;
11use crate::layout::{AXIS, Axis, DIRECTION, Direction};
12use crate::{HEIGHT, WIDTH};
13
14const UNCONSTRAINED: &str = "unconstrained";
15const CLAMP: &str = "clamp";
16
17#[derive(Debug, Default)]
18pub struct Overflow {
19 offset: Pos,
20 inner_size: Size,
22
23 direction: Direction,
24 is_dirty: bool,
25}
26
27impl Overflow {
28 pub fn scroll(&mut self, direction: Direction, amount: Pos) {
29 self.is_dirty = true;
30
31 match (self.direction, direction) {
32 (Direction::Forward, Direction::Forward) => self.offset += amount,
33 (Direction::Forward, Direction::Backward) => self.offset -= amount,
34 (Direction::Backward, Direction::Backward) => self.offset += amount,
35 (Direction::Backward, Direction::Forward) => self.offset -= amount,
36 }
37 }
38
39 pub fn scroll_up(&mut self) {
40 self.scroll(Direction::Backward, Pos { x: 0, y: 1 });
41 }
42
43 pub fn scroll_up_by(&mut self, amount: i32) {
44 self.scroll(Direction::Backward, Pos { x: 0, y: amount });
45 }
46
47 pub fn scroll_down(&mut self) {
48 self.scroll(Direction::Forward, Pos { x: 0, y: 1 });
49 }
50
51 pub fn scroll_down_by(&mut self, amount: i32) {
52 self.scroll(Direction::Forward, Pos { x: 0, y: amount });
53 }
54
55 pub fn scroll_right(&mut self) {
56 self.scroll(Direction::Forward, Pos { x: 1, y: 0 });
57 }
58
59 pub fn scroll_right_by(&mut self, amount: i32) {
60 self.scroll(Direction::Forward, Pos { x: amount, y: 0 });
61 }
62
63 pub fn scroll_left(&mut self) {
64 self.scroll(Direction::Backward, Pos { x: 1, y: 0 });
65 }
66
67 pub fn scroll_left_by(&mut self, amount: i32) {
68 self.scroll(Direction::Backward, Pos { x: amount, y: 0 });
69 }
70
71 pub fn scroll_to(&mut self, pos: Pos) {
72 self.offset = pos;
73 }
74
75 pub fn offset(&self) -> Pos {
76 self.offset
77 }
78
79 fn clamp(&mut self, children: Size, parent: Size) {
80 if self.offset.x < 0 {
81 self.offset.x = 0;
82 }
83
84 if self.offset.y < 0 {
85 self.offset.y = 0;
86 }
87
88 if children.height <= parent.height {
89 self.offset.y = 0;
90 } else {
91 let max_y = children.height as i32 - parent.height as i32;
92 if self.offset.y > max_y {
93 self.offset.y = max_y;
94 }
95 }
96
97 if children.width <= parent.width {
98 self.offset.x = 0;
99 } else {
100 let max_x = children.width as i32 - parent.width as i32;
101 if self.offset.x > max_x {
102 self.offset.x = max_x
103 }
104 }
105 }
106}
107
108impl Widget for Overflow {
109 fn layout<'bp>(
110 &mut self,
111 children: LayoutForEach<'_, 'bp>,
112 mut constraints: Constraints,
113 id: WidgetId,
114 ctx: &mut LayoutCtx<'_, 'bp>,
115 ) -> Result<Size> {
116 let attributes = ctx.attribute_storage.get(id);
117 let axis = attributes.get_as(AXIS).unwrap_or(Axis::Vertical);
118
119 let output_size: Size = (constraints.max_width(), constraints.max_height()).into();
120
121 match axis {
122 Axis::Horizontal => constraints.unbound_width(),
123 Axis::Vertical => constraints.unbound_height(),
124 }
125
126 if attributes.get_as::<bool>(UNCONSTRAINED).unwrap_or_default() {
127 constraints.unbound_width();
128 constraints.unbound_height();
129 }
130
131 if let Some(width) = attributes.get_as::<u16>(WIDTH) {
132 constraints.make_width_tight(width);
133 }
134
135 if let Some(height) = attributes.get_as::<u16>(HEIGHT) {
136 constraints.make_height_tight(height);
137 }
138
139 self.direction = attributes.get_as(DIRECTION).unwrap_or_default();
140
141 let unconstrained = true;
143 let mut many = Many::new(self.direction, axis, unconstrained);
144
145 _ = many.layout(children, constraints, ctx)?;
147
148 self.inner_size = many.used_size.inner_size();
149
150 Ok(output_size)
151 }
152
153 fn position<'bp>(
154 &mut self,
155 mut children: PositionChildren<'_, 'bp>,
156 id: WidgetId,
157 attribute_storage: &AttributeStorage<'bp>,
158 ctx: PositionCtx,
159 ) {
160 let attributes = attribute_storage.get(id);
161 let direction = attributes.get_as(DIRECTION).unwrap_or_default();
162 let axis = attributes.get_as(AXIS).unwrap_or(Axis::Vertical);
163 let mut pos = ctx.pos;
164
165 match attributes.get_as::<bool>(CLAMP).unwrap_or_default() {
167 false => (),
168 true => self.clamp(self.inner_size, ctx.inner_size),
169 }
170
171 if let Direction::Backward = direction {
172 match axis {
173 Axis::Horizontal => pos.x += ctx.inner_size.width as i32,
174 Axis::Vertical => pos.y += ctx.inner_size.height as i32,
175 }
176 }
177
178 let mut pos = match direction {
179 Direction::Forward => pos - self.offset,
180 Direction::Backward => pos + self.offset,
181 };
182
183 let mut count = 0;
184 _ = children.each(|node, children| {
185 match direction {
186 Direction::Forward => {
187 node.position(children, pos, attribute_storage, ctx.viewport);
188 match axis {
189 Axis::Horizontal => pos.x += node.size().width as i32,
190 Axis::Vertical => pos.y += node.size().height as i32,
191 }
192 }
193 Direction::Backward => {
194 match axis {
195 Axis::Horizontal => pos.x -= node.size().width as i32,
196 Axis::Vertical => pos.y -= node.size().height as i32,
197 }
198 node.position(children, pos, attribute_storage, ctx.viewport);
199 }
200 }
201
202 count += 1;
203 ControlFlow::Continue(())
204 });
205 }
206
207 fn paint<'bp>(
208 &mut self,
209 mut children: PaintChildren<'_, 'bp>,
210 _: WidgetId,
211 attribute_storage: &AttributeStorage<'bp>,
212 mut ctx: PaintCtx<'_, SizePos>,
213 ) {
214 let region = ctx.create_region();
215
216 _ = children.each(|widget, children| {
217 ctx.set_clip_region(region);
218 let ctx = ctx.to_unsized();
219 widget.paint(children, ctx, attribute_storage);
220 ControlFlow::Continue(())
221 });
222 }
223
224 fn needs_reflow(&mut self) -> bool {
225 let needs_reflow = self.is_dirty;
226 self.is_dirty = false;
227 needs_reflow
228 }
229}
230
231#[cfg(test)]
232mod test {
233
234 use crate::Overflow;
235 use crate::testing::TestRunner;
236
237 #[test]
238 fn overflow() {
239 let tpl = "
240 overflow
241 for i in [0, 1, 2]
242 border
243 text i
244";
245
246 let expected_first = "
247 ╔═══╗
248 ║┌─┐║
249 ║│0│║
250 ║└─┘║
251 ║┌─┐║
252 ║│1│║
253 ║└─┘║
254 ╚═══╝
255";
256
257 let expected_second = "
258 ╔═══╗
259 ║│0│║
260 ║└─┘║
261 ║┌─┐║
262 ║│1│║
263 ║└─┘║
264 ║┌─┐║
265 ╚═══╝
266";
267
268 TestRunner::new(tpl, (3, 6))
269 .instance()
270 .render_assert(expected_first)
271 .with_widget(|mut query| {
272 query.by_tag("overflow").first(|el, _| {
273 let overflow = el.to::<Overflow>();
274 overflow.scroll_down();
275 });
276 })
277 .render_assert(expected_second);
278 }
279
280 #[test]
281 fn clamp_prevents_scrolling() {
282 let tpl = "
283 overflow
284 text '0'";
285
286 let expected_first = "
287 ╔═══╗
288 ║0 ║
289 ║ ║
290 ╚═══╝
291";
292
293 TestRunner::new(tpl, (3, 2))
294 .instance()
295 .with_widget(|mut query| {
296 query.by_tag("overflow").first(|el, _| {
297 let overflow = el.to::<Overflow>();
298 overflow.scroll_down();
299 });
300 })
301 .render_assert(expected_first);
302 }
303}