1use crate::layout::{context::LayoutContext, measure::Measure, prop::Prop, widget::RawWidget};
2use anyhow::Result;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum RectangleError {
7 #[error("empty group")]
8 EmptyGroup,
9}
10
11#[derive(Copy, Clone)]
12pub struct Point<'a> {
13 pub x: Measure<'a>,
14 pub y: Measure<'a>,
15}
16
17pub type RectanglePainter<'a> = Box<dyn FnOnce(RectangleMetrics) -> Result<()> + 'a>;
18
19pub struct Rectangle<'a> {
20 pub left: Measure<'a>,
21 pub right: Measure<'a>,
22 pub top: Measure<'a>,
23 pub bottom: Measure<'a>,
24 pub width: Measure<'a>,
25 pub height: Measure<'a>,
26
27 pub painter: RectanglePainter<'a>,
29}
30
31#[derive(Copy, Clone, Debug)]
32pub struct RectangleMeasures<'a> {
33 pub left: Measure<'a>,
34 pub right: Measure<'a>,
35 pub top: Measure<'a>,
36 pub bottom: Measure<'a>,
37 pub width: Measure<'a>,
38 pub height: Measure<'a>,
39}
40
41#[derive(Debug, Copy, Clone)]
42pub struct RectangleMetrics {
43 pub left: f64,
44 pub right: f64,
45 pub top: f64,
46 pub bottom: f64,
47 pub width: f64,
48 pub height: f64,
49}
50
51#[allow(dead_code)]
52impl<'a> RectangleMeasures<'a> {
53 pub fn group_center(group: &[&RectangleMeasures<'a>]) -> Result<Point<'a>> {
54 if group.len() == 0 {
55 Err(RectangleError::EmptyGroup.into())
56 } else {
57 let p = group
58 .iter()
59 .map(|x| (x.left, x.right, x.top, x.bottom))
60 .reduce(|(a_l, a_r, a_t, a_b), (b_l, b_r, b_t, b_b)| {
61 (a_l.min(b_l), a_r.max(b_r), a_t.min(b_t), a_b.max(b_b))
62 })
63 .map(|(left, right, top, bottom)| Point {
64 x: (left + right) / 2.0,
65 y: (top + bottom) / 2.0,
66 })
67 .unwrap();
68 Ok(p)
69 }
70 }
71
72 pub fn group_leftmost(group: &[&RectangleMeasures<'a>]) -> Result<Measure<'a>> {
73 if group.len() == 0 {
74 Err(RectangleError::EmptyGroup.into())
75 } else {
76 let p = group
77 .iter()
78 .map(|x| x.left)
79 .reduce(|a, b| a.min(b))
80 .unwrap();
81 Ok(p)
82 }
83 }
84
85 pub fn group_rightmost(group: &[&RectangleMeasures<'a>]) -> Result<Measure<'a>> {
86 if group.len() == 0 {
87 Err(RectangleError::EmptyGroup.into())
88 } else {
89 let p = group
90 .iter()
91 .map(|x| x.right)
92 .reduce(|a, b| a.max(b))
93 .unwrap();
94 Ok(p)
95 }
96 }
97
98 pub fn group_topmost(group: &[&RectangleMeasures<'a>]) -> Result<Measure<'a>> {
99 if group.len() == 0 {
100 Err(RectangleError::EmptyGroup.into())
101 } else {
102 let p = group.iter().map(|x| x.top).reduce(|a, b| a.min(b)).unwrap();
103 Ok(p)
104 }
105 }
106
107 pub fn group_bottommost(group: &[&RectangleMeasures<'a>]) -> Result<Measure<'a>> {
108 if group.len() == 0 {
109 Err(RectangleError::EmptyGroup.into())
110 } else {
111 let p = group
112 .iter()
113 .map(|x| x.bottom)
114 .reduce(|a, b| a.max(b))
115 .unwrap();
116 Ok(p)
117 }
118 }
119
120 pub fn within(&self, that: &RectangleMeasures<'a>) -> Prop<'a> {
121 self.left_to(that.right, 0.0)
122 & self.right_to(that.left, 0.0)
123 & self.top_to(that.bottom, 0.0)
124 & self.bottom_to(that.top, 0.0)
125 }
126
127 pub fn center(&self) -> Result<Point<'a>> {
128 Self::group_center(&[self])
129 }
130
131 pub fn left_to(&self, that: Measure<'a>, distance: f64) -> Prop<'a> {
132 self.right.prop_eq(that - distance)
133 }
134
135 pub fn right_to(&self, that: Measure<'a>, distance: f64) -> Prop<'a> {
136 self.left.prop_eq(that + distance)
137 }
138
139 pub fn top_to(&self, that: Measure<'a>, distance: f64) -> Prop<'a> {
140 self.bottom.prop_eq(that - distance)
141 }
142
143 pub fn bottom_to(&self, that: Measure<'a>, distance: f64) -> Prop<'a> {
144 self.top.prop_eq(that + distance)
145 }
146}
147
148#[allow(dead_code)]
149impl<'a> Rectangle<'a> {
150 pub fn measures(&self) -> RectangleMeasures<'a> {
151 RectangleMeasures {
152 left: self.left,
153 right: self.right,
154 top: self.top,
155 bottom: self.bottom,
156 width: self.width,
157 height: self.height,
158 }
159 }
160
161 pub fn square(ctx: &'a LayoutContext, painter: RectanglePainter<'a>) -> Self {
162 let border_length = Measure::new_unbound(ctx);
163
164 Self {
165 left: Measure::new_unbound(ctx),
166 right: Measure::new_unbound(ctx),
167 top: Measure::new_unbound(ctx),
168 bottom: Measure::new_unbound(ctx),
169 width: border_length,
170 height: border_length,
171 painter,
172 }
173 }
174
175 pub fn row_spacer(ctx: &'a LayoutContext, flex_unit: Measure<'a>) -> Self {
176 Self {
177 left: Measure::new_unbound(ctx),
178 right: Measure::new_unbound(ctx),
179 top: Measure::new_unbound(ctx),
180 bottom: Measure::new_unbound(ctx),
181 width: flex_unit,
182 height: Measure::new_unbound(ctx),
183 painter: Box::new(|metrics| {
184 log::debug!("row_spacer metrics: {:?}", metrics);
185 Ok(())
186 }),
187 }
188 }
189
190 pub fn unbound(ctx: &'a LayoutContext, painter: RectanglePainter<'a>) -> Self {
191 Self {
192 left: Measure::new_unbound(ctx),
193 right: Measure::new_unbound(ctx),
194 top: Measure::new_unbound(ctx),
195 bottom: Measure::new_unbound(ctx),
196 width: Measure::new_unbound(ctx),
197 height: Measure::new_unbound(ctx),
198 painter,
199 }
200 }
201
202 pub fn with_width_and_height(
203 ctx: &'a LayoutContext,
204 width: f64,
205 height: f64,
206 painter: RectanglePainter<'a>,
207 ) -> Self {
208 Self {
209 left: Measure::new_unbound(ctx),
210 right: Measure::new_unbound(ctx),
211 top: Measure::new_unbound(ctx),
212 bottom: Measure::new_unbound(ctx),
213 width: Measure::new_const(ctx, width).unwrap(),
214 height: Measure::new_const(ctx, height).unwrap(),
215 painter,
216 }
217 }
218}
219
220impl<'a> RawWidget<'a> for Rectangle<'a> {
221 fn measures(&self) -> Vec<Measure<'a>> {
222 vec![
223 self.left,
224 self.right,
225 self.top,
226 self.bottom,
227 self.width,
228 self.height,
229 ]
230 }
231
232 fn constraints(&self) -> Vec<Prop<'a>> {
233 vec![
234 (self.left + self.width).prop_eq(self.right),
235 (self.top + self.height).prop_eq(self.bottom),
236 self.top.prop_ge(Measure::zero(self.top.ctx)),
237 self.left.prop_ge(Measure::zero(self.left.ctx)),
238 self
239 .width
240 .prop_ge(Measure::new_const(self.width.ctx, 0.0).unwrap()),
241 self
242 .height
243 .prop_ge(Measure::new_const(self.height.ctx, 0.0).unwrap()),
244 ]
245 }
246
247 fn paint(self: Box<Self>, measures: &[f64]) -> Result<()> {
248 let metrics = RectangleMetrics {
249 left: measures[0],
250 right: measures[1],
251 top: measures[2],
252 bottom: measures[3],
253 width: measures[4],
254 height: measures[5],
255 };
256
257 (self.painter)(metrics)
258 }
259}