1use std::marker::PhantomData;
2use std::num::Saturating;
3
4use embedded_graphics::{
5 draw_target::DrawTarget, geometry::Point, pixelcolor::PixelColor, prelude::Size,
6 primitives::Rectangle,
7};
8
9use crate::layoutable::Layoutable;
10use crate::{ComponentSize, ValueRange};
11
12pub fn center<L: Layoutable<C>, C: PixelColor>(l: L) -> impl Layoutable<C> {
32 AlignLayout::<_, _, CenteredAlignment, CenteredAlignment>::new(l)
33}
34
35pub fn west<L: Layoutable<C>, C: PixelColor>(l: L) -> impl Layoutable<C> {
39 AlignLayout::<_, _, StartAlignment, CenteredAlignment>::new(l)
40}
41pub fn east<L: Layoutable<C>, C: PixelColor>(l: L) -> impl Layoutable<C> {
45 AlignLayout::<_, _, EndAlignment, CenteredAlignment>::new(l)
46}
47pub fn north<L: Layoutable<C>, C: PixelColor>(l: L) -> impl Layoutable<C> {
51 AlignLayout::<_, _, CenteredAlignment, StartAlignment>::new(l)
52}
53pub fn south<L: Layoutable<C>, C: PixelColor>(l: L) -> impl Layoutable<C> {
57 AlignLayout::<_, _, CenteredAlignment, EndAlignment>::new(l)
58}
59
60trait Alignment {
61 fn place(
62 available_range: Saturating<u32>,
63 target_range: ValueRange<Saturating<u32>>,
64 ) -> (Saturating<i32>, Saturating<u32>);
65}
66
67struct AlignLayout<L: Layoutable<C>, C: PixelColor, HA: Alignment, VA: Alignment> {
68 layoutable: L,
69 p1: PhantomData<C>,
70 p2: PhantomData<VA>,
71 p3: PhantomData<HA>,
72}
73
74impl<L: Layoutable<C>, C: PixelColor, HA: Alignment, VA: Alignment> AlignLayout<L, C, HA, VA> {
75 fn new(layoutable: L) -> Self {
76 Self {
77 layoutable,
78 p1: PhantomData,
79 p2: PhantomData,
80 p3: PhantomData,
81 }
82 }
83 fn place(component_size: ComponentSize, available_area: Rectangle) -> Rectangle {
84 let Size {
85 width: available_width,
86 height: available_height,
87 } = available_area.size;
88 let ComponentSize { width, height } = component_size;
89 let origin = available_area.top_left;
90 let (Saturating(x), Saturating(width)) = HA::place(Saturating(available_width), width);
91 let (Saturating(y), Saturating(height)) = VA::place(Saturating(available_height), height);
92 Rectangle {
93 top_left: origin + Point { x, y },
94 size: Size { width, height },
95 }
96 }
97}
98
99impl<L: Layoutable<C>, C: PixelColor, HA: Alignment, VA: Alignment> Layoutable<C>
100 for AlignLayout<L, C, HA, VA>
101{
102 #[inline]
103 fn size(&self) -> ComponentSize {
104 self.layoutable.size()
105 }
106
107 #[inline]
108 fn draw_placed<DrawError>(
109 &self,
110 target: &mut impl DrawTarget<Color = C, Error = DrawError>,
111 position: Rectangle,
112 ) -> Result<(), DrawError> {
113 self.layoutable
114 .draw_placed(target, Self::place(self.layoutable.size(), position))
115 }
116}
117
118pub struct CenteredAlignment;
119
120impl Alignment for CenteredAlignment {
121 #[inline]
122 fn place(
123 available_range: Saturating<u32>,
124 target_range: ValueRange<Saturating<u32>>,
125 ) -> (Saturating<i32>, Saturating<u32>) {
126 if target_range.max_value < available_range {
127 (
128 Saturating((available_range - target_range.max_value).0 as i32 / 2),
129 target_range.max_value,
130 )
131 } else if target_range.min_value > available_range {
132 (
133 (Saturating(available_range.0 as i32)
134 - Saturating(target_range.min_value.0 as i32))
135 / Saturating(2),
136 target_range.min_value,
137 )
138 } else {
139 (Saturating(0), available_range)
140 }
141 }
142}
143
144pub struct StartAlignment;
145
146impl Alignment for StartAlignment {
147 #[inline]
148 fn place(
149 available_range: Saturating<u32>,
150 target_range: ValueRange<Saturating<u32>>,
151 ) -> (Saturating<i32>, Saturating<u32>) {
152 if target_range.max_value < available_range {
153 (Saturating(0), target_range.max_value)
154 } else if target_range.min_value > available_range {
155 (Saturating(0), target_range.min_value)
156 } else {
157 (Saturating(0), available_range)
158 }
159 }
160}
161
162pub struct EndAlignment;
163
164impl Alignment for EndAlignment {
165 #[inline]
166 fn place(
167 available_range: Saturating<u32>,
168 target_range: ValueRange<Saturating<u32>>,
169 ) -> (Saturating<i32>, Saturating<u32>) {
170 if target_range.max_value < available_range {
171 (
172 Saturating(available_range.0 as i32) - Saturating(target_range.max_value.0 as i32),
173 target_range.max_value,
174 )
175 } else if target_range.min_value > available_range {
176 (
177 Saturating(available_range.0 as i32) - Saturating(target_range.min_value.0 as i32),
178 target_range.min_value,
179 )
180 } else {
181 (Saturating(0), available_range)
182 }
183 }
184}