Skip to main content

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;