liquid_layout/widgets/
rectangle.rs

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  // `drop` is NOT called on this!
28  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}