Skip to main content

crabshell_ui/
geometry.rs

1//! Points, sizes and rectangles. Every unit is a logical pixel; the renderer
2//! multiplies by the output's scale.
3
4/// A point in logical pixels, measured from the window's top-left corner.
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
6pub struct Point {
7  /// Distance from the left edge.
8  pub x: f32,
9  /// Distance from the top edge.
10  pub y: f32,
11}
12
13impl Point {
14  /// A point at `x`, `y`.
15  pub const fn new(x: f32, y: f32) -> Point {
16    Point { x, y }
17  }
18}
19
20/// A width and height in logical pixels.
21#[derive(Clone, Copy, Debug, Default, PartialEq)]
22pub struct Size {
23  /// The width.
24  pub width: f32,
25  /// The height.
26  pub height: f32,
27}
28
29impl Size {
30  /// A size of `width` by `height`.
31  pub const fn new(width: f32, height: f32) -> Size {
32    Size { width, height }
33  }
34}
35
36/// A rectangle in logical pixels: its top-left corner and its size.
37#[derive(Clone, Copy, Debug, Default, PartialEq)]
38pub struct Rect {
39  /// The left edge.
40  pub x: f32,
41  /// The top edge.
42  pub y: f32,
43  /// The width.
44  pub width: f32,
45  /// The height.
46  pub height: f32,
47}
48
49impl Rect {
50  /// A rectangle with its top-left corner at `x`, `y`.
51  pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Rect {
52    Rect {
53      x,
54      y,
55      width,
56      height,
57    }
58  }
59
60  /// The right edge.
61  pub fn right(&self) -> f32 {
62    self.x + self.width
63  }
64
65  /// The bottom edge.
66  pub fn bottom(&self) -> f32 {
67    self.y + self.height
68  }
69
70  /// Whether the rectangle covers no area.
71  pub fn is_empty(&self) -> bool {
72    self.width <= 0.0 || self.height <= 0.0
73  }
74
75  /// Whether `point` lies inside. The left and top edges count as inside,
76  /// the right and bottom edges don't, so two rectangles that touch never
77  /// both contain a point.
78  pub fn contains(&self, point: Point) -> bool {
79    point.x >= self.x && point.x < self.right() && point.y >= self.y && point.y < self.bottom()
80  }
81
82  /// The smallest rectangle that covers both. An empty rectangle adds
83  /// nothing.
84  pub fn union(&self, other: Rect) -> Rect {
85    if other.is_empty() {
86      return *self;
87    }
88    if self.is_empty() {
89      return other;
90    }
91    let x = self.x.min(other.x);
92    let y = self.y.min(other.y);
93    Rect::new(
94      x,
95      y,
96      self.right().max(other.right()) - x,
97      self.bottom().max(other.bottom()) - y,
98    )
99  }
100
101  /// The area both cover, or an empty rectangle if they don't overlap.
102  pub fn intersect(&self, other: Rect) -> Rect {
103    let x = self.x.max(other.x);
104    let y = self.y.max(other.y);
105    let right = self.right().min(other.right());
106    let bottom = self.bottom().min(other.bottom());
107    if right > x && bottom > y {
108      Rect::new(x, y, right - x, bottom - y)
109    } else {
110      Rect::default()
111    }
112  }
113}
114
115impl Rect {
116  /// This rectangle grown by `by` on every side. An empty one stays as
117  /// it is.
118  pub(crate) fn outset(&self, by: f32) -> Rect {
119    if self.is_empty() {
120      return *self;
121    }
122    Rect::new(
123      self.x - by,
124      self.y - by,
125      self.width + 2.0 * by,
126      self.height + 2.0 * by,
127    )
128  }
129}
130
131/// A 2D affine transform, in the order tiny-skia's `Transform::from_row`
132/// takes: a point `(x, y)` goes to `(sx·x + kx·y + tx, ky·x + sy·y + ty)`.
133#[derive(Clone, Copy, Debug, PartialEq)]
134pub struct Affine {
135  /// How far x moves along x.
136  pub sx: f32,
137  /// How far x moves along y.
138  pub ky: f32,
139  /// How far y moves along x.
140  pub kx: f32,
141  /// How far y moves along y.
142  pub sy: f32,
143  /// Added to x.
144  pub tx: f32,
145  /// Added to y.
146  pub ty: f32,
147}
148
149impl Affine {
150  /// Where `point` goes.
151  pub(crate) fn map(&self, point: Point) -> Point {
152    Point::new(
153      self.sx * point.x + self.kx * point.y + self.tx,
154      self.ky * point.x + self.sy * point.y + self.ty,
155    )
156  }
157
158  /// The smallest rectangle covering where `rect` goes, or an empty one
159  /// if that isn't made of finite numbers.
160  pub(crate) fn map_rect(&self, rect: Rect) -> Rect {
161    let corners = [
162      Point::new(rect.x, rect.y),
163      Point::new(rect.right(), rect.y),
164      Point::new(rect.x, rect.bottom()),
165      Point::new(rect.right(), rect.bottom()),
166    ]
167    .map(|corner| self.map(corner));
168    let (mut left, mut top) = (f32::INFINITY, f32::INFINITY);
169    let (mut right, mut bottom) = (f32::NEG_INFINITY, f32::NEG_INFINITY);
170    for corner in corners {
171      left = left.min(corner.x);
172      top = top.min(corner.y);
173      right = right.max(corner.x);
174      bottom = bottom.max(corner.y);
175    }
176    let mapped = Rect::new(left, top, right - left, bottom - top);
177    if [mapped.x, mapped.y, mapped.width, mapped.height]
178      .iter()
179      .all(|n| n.is_finite())
180    {
181      mapped
182    } else {
183      Rect::default()
184    }
185  }
186
187  /// The transform that undoes this one, or `None` when it squashes the
188  /// plane flat, as a scale of 0 does.
189  pub(crate) fn invert(&self) -> Option<Affine> {
190    let det = self.sx * self.sy - self.kx * self.ky;
191    if det == 0.0 || !det.is_finite() {
192      return None;
193    }
194    let (sx, ky, kx, sy) = (self.sy / det, -self.ky / det, -self.kx / det, self.sx / det);
195    Some(Affine {
196      sx,
197      ky,
198      kx,
199      sy,
200      tx: -(sx * self.tx + kx * self.ty),
201      ty: -(ky * self.tx + sy * self.ty),
202    })
203  }
204}
205
206/// Where `rect`, in the innermost space of `chain`, lands in the window:
207/// through each transform from the innermost out, grown by a logical
208/// pixel before each, since a transformed layer is filtered bilinearly
209/// and bleeds up to half a pixel past its content.
210pub(crate) fn through(chain: &[Affine], rect: Rect) -> Rect {
211  chain.iter().rev().fold(rect, |rect, transform| {
212    // A line, empty itself, would cover an area once turned.
213    if rect.is_empty() {
214      rect
215    } else {
216      transform.map_rect(rect.outset(1.0))
217    }
218  })
219}