smudge_core/geometry.rs
1//! Geometry and color types shared by layout, paint and damage.
2//!
3//! Layout and paint work in logical pixels as [`f32`], with the origin at the
4//! top left of the window and y growing downwards. Damage leaves the library as
5//! [`PixelRect`] in physical pixels, so a renderer never repeats the scale
6//! conversion.
7
8/// A position in logical pixels, relative to the top left of the window.
9#[derive(Copy, Clone, Debug, Default, PartialEq)]
10pub struct Point {
11 /// Horizontal offset from the left edge of the window.
12 pub x: f32,
13 /// Vertical offset from the top edge of the window.
14 pub y: f32,
15}
16
17/// A width and a height in logical pixels.
18#[derive(Copy, Clone, Debug, Default, PartialEq)]
19pub struct Size {
20 /// Horizontal extent.
21 pub width: f32,
22 /// Vertical extent.
23 pub height: f32,
24}
25
26/// An axis-aligned rectangle in logical pixels, stored as its minimum and
27/// maximum corners.
28///
29/// `x0` and `y0` are the top left corner and `x1` and `y1` are the bottom right
30/// corner. A rectangle is empty when its width or its height is not positive,
31/// which includes the case where the corners are inverted.
32#[derive(Copy, Clone, Debug, Default, PartialEq)]
33pub struct Rect {
34 /// Horizontal coordinate of the top left corner.
35 pub x0: f32,
36 /// Vertical coordinate of the top left corner.
37 pub y0: f32,
38 /// Horizontal coordinate of the bottom right corner.
39 pub x1: f32,
40 /// Vertical coordinate of the bottom right corner.
41 pub y1: f32,
42}
43
44impl Rect {
45 /// Creates a rectangle from its minimum and maximum corners.
46 pub const fn new(x0: f32, y0: f32, x1: f32, y1: f32) -> Rect {
47 Rect { x0, y0, x1, y1 }
48 }
49
50 /// Creates a rectangle from its top left corner and its size.
51 pub fn from_origin_size(origin: Point, size: Size) -> Rect {
52 Rect::new(
53 origin.x,
54 origin.y,
55 origin.x + size.width,
56 origin.y + size.height,
57 )
58 }
59
60 /// Returns the horizontal extent, which is not positive when the rectangle
61 /// is empty.
62 pub fn width(self) -> f32 {
63 self.x1 - self.x0
64 }
65
66 /// Returns the vertical extent, which is not positive when the rectangle is
67 /// empty.
68 pub fn height(self) -> f32 {
69 self.y1 - self.y0
70 }
71
72 /// Returns the horizontal and vertical extents as a [`Size`].
73 pub fn size(self) -> Size {
74 Size {
75 width: self.width(),
76 height: self.height(),
77 }
78 }
79
80 /// Returns the top left corner.
81 pub fn origin(self) -> Point {
82 Point {
83 x: self.x0,
84 y: self.y0,
85 }
86 }
87
88 /// Returns `true` when the rectangle covers no area.
89 ///
90 /// A rectangle is empty when its width or its height is not positive, and
91 /// when either extent is NaN.
92 pub fn is_empty(self) -> bool {
93 !(self.width() > 0.0 && self.height() > 0.0)
94 }
95
96 /// Returns the area in square logical pixels, or zero when the rectangle is
97 /// empty.
98 pub fn area(self) -> f32 {
99 if self.is_empty() {
100 0.0
101 } else {
102 self.width() * self.height()
103 }
104 }
105
106 /// Returns `true` when the point lies inside the rectangle or on its edge.
107 ///
108 /// An empty rectangle contains no point.
109 pub fn contains(self, point: Point) -> bool {
110 !self.is_empty()
111 && point.x >= self.x0
112 && point.x <= self.x1
113 && point.y >= self.y0
114 && point.y <= self.y1
115 }
116
117 /// Returns the rectangle moved by `by`.
118 pub fn translated(self, by: Point) -> Rect {
119 Rect::new(
120 self.x0 + by.x,
121 self.y0 + by.y,
122 self.x1 + by.x,
123 self.y1 + by.y,
124 )
125 }
126
127 /// Returns the part of the rectangle that overlaps `other`.
128 ///
129 /// The result is empty when the two do not overlap or when either operand
130 /// is empty.
131 pub fn intersect(self, other: Rect) -> Rect {
132 if self.is_empty() || other.is_empty() {
133 return Rect::default();
134 }
135 let rect = Rect::new(
136 self.x0.max(other.x0),
137 self.y0.max(other.y0),
138 self.x1.min(other.x1),
139 self.y1.min(other.y1),
140 );
141 if rect.is_empty() {
142 Rect::default()
143 } else {
144 rect
145 }
146 }
147
148 /// Returns the smallest rectangle covering both operands.
149 ///
150 /// An empty operand is ignored, so the union of an empty rectangle with
151 /// `other` is `other`.
152 pub fn union(self, other: Rect) -> Rect {
153 match (self.is_empty(), other.is_empty()) {
154 (true, true) => Rect::default(),
155 (true, false) => other,
156 (false, true) => self,
157 (false, false) => Rect::new(
158 self.x0.min(other.x0),
159 self.y0.min(other.y0),
160 self.x1.max(other.x1),
161 self.y1.max(other.y1),
162 ),
163 }
164 }
165}
166
167/// A rectangle in physical pixels, relative to the top left of the surface.
168///
169/// Every rectangle this library produces is non-empty and clamped to the
170/// surface it was computed for, so `width` and `height` are at least one.
171#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
172pub struct PixelRect {
173 /// Distance from the left edge of the surface, in physical pixels.
174 pub x: u32,
175 /// Distance from the top edge of the surface, in physical pixels.
176 pub y: u32,
177 /// Width in physical pixels.
178 pub width: u32,
179 /// Height in physical pixels.
180 pub height: u32,
181}
182
183impl PixelRect {
184 /// Returns the number of pixels the rectangle covers.
185 pub fn area(self) -> u64 {
186 u64::from(self.width) * u64::from(self.height)
187 }
188
189 /// Returns the smallest rectangle covering both operands.
190 ///
191 /// An operand with no area is ignored, so the union of
192 /// `PixelRect::default()` with `other` is `other`.
193 pub fn union(self, other: PixelRect) -> PixelRect {
194 if other.area() == 0 {
195 return self;
196 }
197 if self.area() == 0 {
198 return other;
199 }
200 let x0 = self.x.min(other.x);
201 let y0 = self.y.min(other.y);
202 let x1 = self
203 .x
204 .saturating_add(self.width)
205 .max(other.x.saturating_add(other.width));
206 let y1 = self
207 .y
208 .saturating_add(self.height)
209 .max(other.y.saturating_add(other.height));
210 PixelRect {
211 x: x0,
212 y: y0,
213 width: x1 - x0,
214 height: y1 - y0,
215 }
216 }
217
218 /// Returns the overlapping part of two rectangles, or `None` when they do
219 /// not overlap.
220 pub fn intersect(self, other: PixelRect) -> Option<PixelRect> {
221 let x0 = self.x.max(other.x);
222 let y0 = self.y.max(other.y);
223 let x1 = self
224 .x
225 .saturating_add(self.width)
226 .min(other.x.saturating_add(other.width));
227 let y1 = self
228 .y
229 .saturating_add(self.height)
230 .min(other.y.saturating_add(other.height));
231 if x0 >= x1 || y0 >= y1 {
232 return None;
233 }
234 Some(PixelRect {
235 x: x0,
236 y: y0,
237 width: x1 - x0,
238 height: y1 - y0,
239 })
240 }
241
242 /// Returns `true` when `other` lies entirely inside this rectangle.
243 ///
244 /// The edges are inclusive, so a rectangle with no width or height counts
245 /// as inside when it sits on the boundary.
246 pub fn contains_rect(self, other: PixelRect) -> bool {
247 let outer_x1 = u64::from(self.x) + u64::from(self.width);
248 let outer_y1 = u64::from(self.y) + u64::from(self.height);
249 u64::from(other.x) >= u64::from(self.x)
250 && u64::from(other.y) >= u64::from(self.y)
251 && u64::from(other.x) + u64::from(other.width) <= outer_x1
252 && u64::from(other.y) + u64::from(other.height) <= outer_y1
253 }
254
255 /// Returns the smallest pixel rectangle covering every physical pixel the
256 /// logical rectangle touches, clamped to the surface.
257 ///
258 /// The minimum corner is rounded down and the maximum corner is rounded up,
259 /// so a rectangle that covers part of a pixel still claims that pixel.
260 /// Returns `None` when the result would be empty, which includes a
261 /// rectangle entirely outside the surface.
262 ///
263 /// NaN and infinite coordinates, and a scale that is not positive and
264 /// finite, are treated as empty.
265 pub fn from_logical(
266 rect: Rect,
267 scale: f32,
268 surface_width: u32,
269 surface_height: u32,
270 ) -> Option<PixelRect> {
271 if !scale.is_finite() || scale <= 0.0 {
272 return None;
273 }
274 if !rect.x0.is_finite()
275 || !rect.y0.is_finite()
276 || !rect.x1.is_finite()
277 || !rect.y1.is_finite()
278 {
279 return None;
280 }
281 let max_x = surface_width as f32;
282 let max_y = surface_height as f32;
283 let x0 = (rect.x0 * scale).floor().clamp(0.0, max_x) as u32;
284 let y0 = (rect.y0 * scale).floor().clamp(0.0, max_y) as u32;
285 let x1 = (rect.x1 * scale).ceil().clamp(0.0, max_x) as u32;
286 let y1 = (rect.y1 * scale).ceil().clamp(0.0, max_y) as u32;
287 if x1 <= x0 || y1 <= y0 {
288 return None;
289 }
290 Some(PixelRect {
291 x: x0,
292 y: y0,
293 width: x1 - x0,
294 height: y1 - y0,
295 })
296 }
297}
298
299/// A straight-alpha sRGB color.
300///
301/// Channels are not premultiplied. Renderers convert to whatever their own
302/// surface expects.
303#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
304pub struct Color {
305 /// Red channel.
306 pub r: u8,
307 /// Green channel.
308 pub g: u8,
309 /// Blue channel.
310 pub b: u8,
311 /// Alpha channel, where 0 is fully transparent and 255 fully opaque.
312 pub a: u8,
313}
314
315impl Color {
316 /// Fully transparent black.
317 pub const TRANSPARENT: Color = Color::rgba(0, 0, 0, 0);
318 /// Opaque black.
319 pub const BLACK: Color = Color::rgb(0, 0, 0);
320 /// Opaque white.
321 pub const WHITE: Color = Color::rgb(255, 255, 255);
322
323 /// Creates a fully opaque color from its red, green and blue channels.
324 pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
325 Color { r, g, b, a: 255 }
326 }
327
328 /// Creates a color from its red, green, blue and alpha channels.
329 pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Color {
330 Color { r, g, b, a }
331 }
332
333 /// Returns `true` when the color is fully opaque.
334 pub const fn is_opaque(self) -> bool {
335 self.a == 255
336 }
337}
338
339/// The corner radii of a rounded rectangle, in logical pixels.
340///
341/// A radius of zero gives a square corner.
342#[derive(Copy, Clone, Debug, Default, PartialEq)]
343pub struct Corners {
344 /// Radius of the top left corner.
345 pub top_left: f32,
346 /// Radius of the top right corner.
347 pub top_right: f32,
348 /// Radius of the bottom right corner.
349 pub bottom_right: f32,
350 /// Radius of the bottom left corner.
351 pub bottom_left: f32,
352}
353
354impl Corners {
355 /// Square corners.
356 pub const ZERO: Corners = Corners::all(0.0);
357
358 /// A radius of `radius` logical pixels on every corner.
359 pub const fn all(radius: f32) -> Corners {
360 Corners {
361 top_left: radius,
362 top_right: radius,
363 bottom_right: radius,
364 bottom_left: radius,
365 }
366 }
367}
368
369#[cfg(test)]
370#[path = "geometry_tests.rs"]
371mod tests;