1use std::num::Saturating;
2use std::{cmp::Ordering, marker::PhantomData, ops::Deref};
3
4use embedded_graphics::{
5 pixelcolor::PixelColor,
6 prelude::{DrawTarget, Point, Size},
7 primitives::Rectangle,
8};
9
10use crate::{layoutable::Layoutable, ComponentSize, ValueRange};
11
12pub trait Orientation {
13 fn split_component_size(
14 size: ComponentSize,
15 ) -> (ValueRange<Saturating<u32>>, ValueRange<Saturating<u32>>);
16 fn split_size(size: Size) -> (Saturating<u32>, Saturating<u32>);
17 fn split_point(p: Point) -> (Saturating<i32>, Saturating<i32>);
18 fn create_component_size(
19 along: ValueRange<Saturating<u32>>,
20 cross: ValueRange<Saturating<u32>>,
21 ) -> ComponentSize;
22 fn create_size(along: Saturating<u32>, across: Saturating<u32>) -> Size;
23 fn create_point(along: Saturating<i32>, cross: Saturating<i32>) -> Point;
24}
25
26pub struct Horizontal {}
27
28impl Orientation for Horizontal {
29 #[inline]
30 fn split_component_size(
31 size: ComponentSize,
32 ) -> (ValueRange<Saturating<u32>>, ValueRange<Saturating<u32>>) {
33 (size.width, size.height)
34 }
35
36 #[inline]
37 fn split_size(size: Size) -> (Saturating<u32>, Saturating<u32>) {
38 (Saturating(size.width), Saturating(size.height))
39 }
40
41 #[inline]
42 fn split_point(p: Point) -> (Saturating<i32>, Saturating<i32>) {
43 let Point { x, y } = p;
44 (Saturating(x), Saturating(y))
45 }
46
47 #[inline]
48 fn create_component_size(
49 along: ValueRange<Saturating<u32>>,
50 cross: ValueRange<Saturating<u32>>,
51 ) -> ComponentSize {
52 ComponentSize {
53 width: along,
54 height: cross,
55 }
56 }
57
58 #[inline]
59 fn create_size(along: Saturating<u32>, across: Saturating<u32>) -> Size {
60 Size {
61 width: along.0,
62 height: across.0,
63 }
64 }
65
66 #[inline]
67 fn create_point(along: Saturating<i32>, cross: Saturating<i32>) -> Point {
68 Point {
69 x: along.0,
70 y: cross.0,
71 }
72 }
73}
74
75pub struct Vertical {}
76
77impl Orientation for Vertical {
78 fn split_component_size(
79 size: ComponentSize,
80 ) -> (ValueRange<Saturating<u32>>, ValueRange<Saturating<u32>>) {
81 (size.height, size.width)
82 }
83
84 fn split_size(size: Size) -> (Saturating<u32>, Saturating<u32>) {
85 (Saturating(size.height), Saturating(size.width))
86 }
87
88 fn split_point(p: Point) -> (Saturating<i32>, Saturating<i32>) {
89 (Saturating(p.y), Saturating(p.x))
90 }
91
92 fn create_component_size(
93 along: ValueRange<Saturating<u32>>,
94 cross: ValueRange<Saturating<u32>>,
95 ) -> ComponentSize {
96 ComponentSize {
97 width: cross,
98 height: along,
99 }
100 }
101
102 fn create_size(along: Saturating<u32>, across: Saturating<u32>) -> Size {
103 Size {
104 width: across.0,
105 height: along.0,
106 }
107 }
108
109 fn create_point(along: Saturating<i32>, cross: Saturating<i32>) -> Point {
110 Point {
111 x: cross.0,
112 y: along.0,
113 }
114 }
115}
116
117pub trait LinearLayout<C: PixelColor, O: Orientation>: Sized {
118 fn len() -> usize;
119 fn fill_sizes(&self, sizes: &mut [ComponentSize]);
120 fn fill_weights(&self, weights: &mut [u32]);
121 fn draw_placed_components<DrawError>(
122 &self,
123 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
124 places: &[Rectangle],
125 ) -> Result<(), DrawError>;
126}
127
128#[derive(Default, Debug)]
129pub struct SingleLinearLayout<L: Layoutable<C>, C: PixelColor, O: Orientation> {
130 layout: L,
131 weight: u32,
132 p1: PhantomData<C>,
133 p2: PhantomData<O>,
134}
135
136impl<L: Layoutable<C>, C: PixelColor, O: Orientation> Layoutable<C>
137 for SingleLinearLayout<L, C, O>
138{
139 fn size(&self) -> ComponentSize {
140 self.layout.size()
141 }
142
143 fn draw_placed<DrawError>(
144 &self,
145 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
146 position: Rectangle,
147 ) -> Result<(), DrawError> {
148 self.layout.draw_placed(target, position)
149 }
150}
151
152impl<L: Layoutable<C>, C: PixelColor, O: Orientation> LinearLayout<C, O>
153 for SingleLinearLayout<L, C, O>
154{
155 #[inline]
156 fn len() -> usize {
157 1
158 }
159
160 #[inline]
161 fn fill_sizes(&self, sizes: &mut [ComponentSize]) {
162 sizes[0] = self.size();
163 }
164
165 #[inline]
166 fn fill_weights(&self, weights: &mut [u32]) {
167 weights[0] = self.weight;
168 }
169
170 #[inline]
171 fn draw_placed_components<DrawError>(
172 &self,
173 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
174 places: &[Rectangle],
175 ) -> Result<(), DrawError> {
176 self.layout.draw_placed(target, places[0])
177 }
178}
179
180pub struct LayoutableLinearLayout<C: PixelColor, O: Orientation, LL: LinearLayout<C, O>>(
181 LL,
182 PhantomData<C>,
183 PhantomData<O>,
184);
185
186impl<C: PixelColor, O: Orientation, LL: LinearLayout<C, O>> LayoutableLinearLayout<C, O, LL> {
187 pub fn append<L>(
198 self,
199 element: L,
200 weight: u32,
201 ) -> LayoutableLinearLayout<C, O, ChainingLinearLayout<LL, L, C, O>>
202 where
203 L: Layoutable<C>,
204 {
205 LayoutableLinearLayout(
206 ChainingLinearLayout {
207 base_layout: self.0,
208 layoutable: element,
209 weight,
210 p: Default::default(),
211 o: Default::default(),
212 },
213 PhantomData,
214 PhantomData,
215 )
216 }
217}
218
219impl<C: PixelColor, O: Orientation, LL: LinearLayout<C, O>> From<LL>
220 for LayoutableLinearLayout<C, O, LL>
221{
222 fn from(value: LL) -> Self {
223 LayoutableLinearLayout(value, PhantomData, PhantomData)
224 }
225}
226
227impl<C: PixelColor, O: Orientation, LL: LinearLayout<C, O>> Layoutable<C>
228 for LayoutableLinearLayout<C, O, LL>
229{
230 fn size(&self) -> ComponentSize {
231 let mut sizes = vec![ComponentSize::default(); LL::len()].into_boxed_slice();
232 self.0.fill_sizes(&mut sizes);
233 let mut total_along = ValueRange::default();
234 let mut total_cross = ValueRange::default();
235 for size in sizes.iter() {
236 let (along, cross) = O::split_component_size(*size);
237 total_along += along;
238 total_cross.expand(&cross);
239 }
240 O::create_component_size(total_along, total_cross)
241 }
242 fn draw_placed<DrawError>(
243 &self,
244 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
245 position: Rectangle,
246 ) -> Result<(), DrawError> {
247 let (along_target, cross_target) = O::split_size(position.size);
248 let (mut along_offset, cross_offset) = O::split_point(position.top_left);
249
250 let mut sizes = vec![ComponentSize::default(); LL::len()].into_boxed_slice();
251 self.0.fill_sizes(&mut sizes);
252 let sizes = sizes
253 .iter()
254 .map(|s| O::split_component_size(*s).0)
255 .collect::<Box<_>>();
256 let preferred_sizes = sizes.iter().map(|s| s.preferred_value).collect::<Box<_>>();
257 let total_preferred: Saturating<u32> =
258 preferred_sizes.iter().fold(Saturating(0), |s, v| s + v);
259 let places = match along_target.cmp(&total_preferred) {
260 Ordering::Less => {
261 let min_sizes = sizes.iter().map(|s| s.min_value).collect::<Box<_>>();
262 let total_min = min_sizes.iter().fold(Saturating(0), |s, v| s + v);
263 if total_min >= along_target {
264 min_sizes
265 } else {
266 let mut remaining_budget = total_preferred - along_target;
267 let mut result_sizes = preferred_sizes;
268 let mut weights = vec![0; LL::len()].into_boxed_slice();
269 self.0.fill_weights(&mut weights);
270 while remaining_budget > Saturating(0) {
271 let remaining_budget_before = remaining_budget;
272 let mut entries_with_headroom = weights
273 .iter()
274 .zip(result_sizes.iter_mut())
275 .zip(sizes.iter())
276 .filter(|((weight, result_size), size)| {
277 **weight > 0 && **result_size > size.min_value
278 })
279 .collect::<Box<_>>();
280 let mut remaining_weights: u32 = entries_with_headroom
281 .iter()
282 .map(|((weight, _), _)| **weight)
283 .sum();
284 if remaining_weights == 0 {
285 break;
286 }
287 for ((weight, result_size), size) in entries_with_headroom.iter_mut() {
288 let theoretical_decrease = remaining_budget * Saturating(**weight)
289 / Saturating(remaining_weights);
290 let selected_decrease =
291 (theoretical_decrease).min(**result_size - size.min_value);
292 **result_size -= selected_decrease;
293 remaining_budget -= theoretical_decrease;
294 remaining_weights -= *weight;
295 }
296 if remaining_budget_before == remaining_budget {
297 break;
299 }
300 }
301 result_sizes
302 }
303 }
304 Ordering::Equal => preferred_sizes,
305 Ordering::Greater => {
306 let max_sizes = sizes.iter().map(|s| s.max_value).collect::<Box<_>>();
307 let total_max = max_sizes.iter().fold(Saturating(0), |s, v| s + v);
308 if total_max <= along_target {
309 max_sizes
310 } else {
311 let mut remaining_budget = along_target - total_preferred;
312 let mut result_sizes = preferred_sizes;
313 let mut weights = vec![0; LL::len()].into_boxed_slice();
314 self.0.fill_weights(&mut weights);
315 while remaining_budget > Saturating(0) {
316 let remaining_budget_before = remaining_budget;
317 let mut entries_with_headroom = weights
318 .iter()
319 .zip(result_sizes.iter_mut())
320 .zip(sizes.iter())
321 .filter(|((weight, result_size), size)| {
322 **weight > 0 && **result_size < size.max_value
323 })
324 .collect::<Box<_>>();
325 let mut remaining_weights: u32 = entries_with_headroom
326 .iter()
327 .map(|((weight, _), _)| **weight)
328 .sum();
329 if remaining_weights == 0 {
330 break;
331 }
332
333 for ((weight, result_size), size) in entries_with_headroom.iter_mut() {
334 let theoretical_increase = remaining_budget * Saturating(**weight)
335 / Saturating(remaining_weights);
336 let selected_increase =
337 (theoretical_increase).min(size.max_value - **result_size);
338 **result_size += selected_increase;
339 remaining_budget -= theoretical_increase;
340 remaining_weights -= *weight;
341 }
342 if remaining_budget_before == remaining_budget {
343 break;
345 }
346 }
347 result_sizes
348 }
349 }
350 }
351 .iter()
352 .map(|l| {
353 let place = Rectangle {
354 top_left: O::create_point(along_offset, cross_offset),
355 size: O::create_size(*l, cross_target),
356 };
357 along_offset += Saturating(l.0 as i32);
358 place
359 })
360 .collect::<Box<_>>();
361 self.0.draw_placed_components(target, &places)
362 }
363}
364
365impl<C: PixelColor, O: Orientation, LL: LinearLayout<C, O>> Deref
366 for LayoutableLinearLayout<C, O, LL>
367{
368 type Target = LL;
369
370 fn deref(&self) -> &Self::Target {
371 &self.0
372 }
373}
374
375pub struct ChainingLinearLayout<
391 LL: LinearLayout<C, O>,
392 L: Layoutable<C>,
393 C: PixelColor,
394 O: Orientation,
395> {
396 base_layout: LL,
397 layoutable: L,
398 weight: u32,
399 p: PhantomData<C>,
400 o: PhantomData<O>,
401}
402
403impl<LL: LinearLayout<C, O>, L: Layoutable<C>, C: PixelColor, O: Orientation> LinearLayout<C, O>
404 for ChainingLinearLayout<LL, L, C, O>
405{
406 #[inline]
407 fn len() -> usize {
408 LL::len() + 1
409 }
410
411 #[inline]
412 fn fill_sizes(&self, sizes: &mut [ComponentSize]) {
413 let idx = Self::len() - 1;
414 self.base_layout.fill_sizes(&mut sizes[0..idx]);
415 sizes[idx] = self.layoutable.size();
416 }
417
418 #[inline]
419 fn fill_weights(&self, weights: &mut [u32]) {
420 let idx = Self::len() - 1;
421 self.base_layout.fill_weights(&mut weights[0..idx]);
422 weights[idx] = self.weight;
423 }
424
425 #[inline]
426 fn draw_placed_components<DrawError>(
427 &self,
428 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
429 places: &[Rectangle],
430 ) -> Result<(), DrawError> {
431 let idx = Self::len() - 1;
432 self.base_layout
433 .draw_placed_components(target, &places[0..idx])?;
434 self.layoutable.draw_placed(target, places[idx])
435 }
436}
437
438pub fn vertical_layout<L: Layoutable<C>, C: PixelColor>(
488 first_child: L,
489 first_child_weight: u32,
490) -> LayoutableLinearLayout<C, Vertical, SingleLinearLayout<L, C, Vertical>> {
491 LayoutableLinearLayout(
492 SingleLinearLayout {
493 layout: first_child,
494 weight: first_child_weight,
495 p1: PhantomData,
496 p2: PhantomData,
497 },
498 PhantomData,
499 PhantomData,
500 )
501}
502
503pub fn horizontal_layout<L: Layoutable<C>, C: PixelColor>(
513 first_child: L,
514 first_child_weight: u32,
515) -> LayoutableLinearLayout<C, Horizontal, SingleLinearLayout<L, C, Horizontal>> {
516 LayoutableLinearLayout(
517 SingleLinearLayout {
518 layout: first_child,
519 weight: first_child_weight,
520 p1: PhantomData,
521 p2: PhantomData,
522 },
523 PhantomData,
524 PhantomData,
525 )
526}