Skip to content

Commit

Permalink
feat(rect): add is_empty() to simplify some common checks (#534)
Browse files Browse the repository at this point in the history
- add `Rect::is_empty()` that checks whether either height or width == 0
- refactored `Rect` into layout/rect.rs from layout.rs. No public API change as
   the module is private and the type is re-exported under the `layout` module.
  • Loading branch information
joshka authored Sep 26, 2023
1 parent 32e4619 commit cbf86da
Show file tree
Hide file tree
Showing 2 changed files with 270 additions and 252 deletions.
256 changes: 4 additions & 252 deletions src/layout.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
use std::{
cell::RefCell,
cmp::{max, min},
collections::HashMap,
fmt,
num::NonZeroUsize,
rc::Rc,
sync::OnceLock,
};
use std::{cell::RefCell, collections::HashMap, fmt, num::NonZeroUsize, rc::Rc, sync::OnceLock};

use cassowary::{
strength::{MEDIUM, REQUIRED, STRONG, WEAK},
Expand All @@ -33,6 +25,9 @@ pub enum Direction {
Vertical,
}

mod rect;
pub use rect::*;

/// Constraints to apply
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Constraint {
Expand Down Expand Up @@ -165,116 +160,6 @@ pub enum Alignment {
Right,
}

/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
/// area they are supposed to render to.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}

impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
}
}

impl Rect {
/// Creates a new rect, with width and height limited to keep the area under max u16.
/// If clipped, aspect ratio will be preserved.
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
let max_area = u16::max_value();
let (clipped_width, clipped_height) =
if u32::from(width) * u32::from(height) > u32::from(max_area) {
let aspect_ratio = f64::from(width) / f64::from(height);
let max_area_f = f64::from(max_area);
let height_f = (max_area_f / aspect_ratio).sqrt();
let width_f = height_f * aspect_ratio;
(width_f as u16, height_f as u16)
} else {
(width, height)
};
Rect {
x,
y,
width: clipped_width,
height: clipped_height,
}
}

pub const fn area(self) -> u16 {
self.width.saturating_mul(self.height)
}

pub const fn left(self) -> u16 {
self.x
}

pub const fn right(self) -> u16 {
self.x.saturating_add(self.width)
}

pub const fn top(self) -> u16 {
self.y
}

pub const fn bottom(self) -> u16 {
self.y.saturating_add(self.height)
}

pub fn inner(self, margin: &Margin) -> Rect {
let doubled_margin_horizontal = margin.horizontal.saturating_mul(2);
let doubled_margin_vertical = margin.vertical.saturating_mul(2);

if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
Rect::default()
} else {
Rect {
x: self.x.saturating_add(margin.horizontal),
y: self.y.saturating_add(margin.vertical),
width: self.width.saturating_sub(doubled_margin_horizontal),
height: self.height.saturating_sub(doubled_margin_vertical),
}
}
}

pub fn union(self, other: Rect) -> Rect {
let x1 = min(self.x, other.x);
let y1 = min(self.y, other.y);
let x2 = max(self.x + self.width, other.x + other.width);
let y2 = max(self.y + self.height, other.y + other.height);
Rect {
x: x1,
y: y1,
width: x2 - x1,
height: y2 - y1,
}
}

pub fn intersection(self, other: Rect) -> Rect {
let x1 = max(self.x, other.x);
let y1 = max(self.y, other.y);
let x2 = min(self.x + self.width, other.x + other.width);
let y2 = min(self.y + self.height, other.y + other.height);
Rect {
x: x1,
y: y1,
width: x2 - x1,
height: y2 - y1,
}
}

pub const fn intersects(self, other: Rect) -> bool {
self.x < other.x + other.width
&& self.x + self.width > other.x
&& self.y < other.y + other.height
&& self.y + self.height > other.y
}
}

#[derive(Debug, Default, Display, EnumString, Clone, Eq, PartialEq, Hash)]
pub(crate) enum SegmentSize {
EvenDistribution,
Expand Down Expand Up @@ -808,79 +693,6 @@ mod tests {
assert_eq!("".parse::<Alignment>(), Err(ParseError::VariantNotFound));
}

#[test]
fn rect_to_string() {
assert_eq!(Rect::new(1, 2, 3, 4).to_string(), "3x4+1+2");
}

#[test]
fn rect_new() {
assert_eq!(
Rect::new(1, 2, 3, 4),
Rect {
x: 1,
y: 2,
width: 3,
height: 4
}
);
}

#[test]
fn rect_area() {
assert_eq!(Rect::new(1, 2, 3, 4).area(), 12);
}

#[test]
fn rect_left() {
assert_eq!(Rect::new(1, 2, 3, 4).left(), 1);
}

#[test]
fn rect_right() {
assert_eq!(Rect::new(1, 2, 3, 4).right(), 4);
}

#[test]
fn rect_top() {
assert_eq!(Rect::new(1, 2, 3, 4).top(), 2);
}

#[test]
fn rect_bottom() {
assert_eq!(Rect::new(1, 2, 3, 4).bottom(), 6);
}

#[test]
fn rect_inner() {
assert_eq!(
Rect::new(1, 2, 3, 4).inner(&Margin::new(1, 2)),
Rect::new(2, 4, 1, 0)
);
}

#[test]
fn rect_union() {
assert_eq!(
Rect::new(1, 2, 3, 4).union(Rect::new(2, 3, 4, 5)),
Rect::new(1, 2, 5, 6)
);
}

#[test]
fn rect_intersection() {
assert_eq!(
Rect::new(1, 2, 3, 4).intersection(Rect::new(2, 3, 4, 5)),
Rect::new(2, 3, 2, 3)
);
}

#[test]
fn rect_intersects() {
assert!(Rect::new(1, 2, 3, 4).intersects(Rect::new(2, 3, 4, 5)));
assert!(!Rect::new(1, 2, 3, 4).intersects(Rect::new(5, 6, 7, 8)));
}

#[test]
fn segment_size_to_string() {
assert_eq!(
Expand Down Expand Up @@ -997,50 +809,6 @@ mod tests {
);
}

#[test]
fn test_rect_size_truncation() {
for width in 256u16..300u16 {
for height in 256u16..300u16 {
let rect = Rect::new(0, 0, width, height);
rect.area(); // Should not panic.
assert!(rect.width < width || rect.height < height);
// The target dimensions are rounded down so the math will not be too precise
// but let's make sure the ratios don't diverge crazily.
assert!(
(f64::from(rect.width) / f64::from(rect.height)
- f64::from(width) / f64::from(height))
.abs()
< 1.0
);
}
}

// One dimension below 255, one above. Area above max u16.
let width = 900;
let height = 100;
let rect = Rect::new(0, 0, width, height);
assert_ne!(rect.width, 900);
assert_ne!(rect.height, 100);
assert!(rect.width < width || rect.height < height);
}

#[test]
fn test_rect_size_preservation() {
for width in 0..256u16 {
for height in 0..256u16 {
let rect = Rect::new(0, 0, width, height);
rect.area(); // Should not panic.
assert_eq!(rect.width, width);
assert_eq!(rect.height, height);
}
}

// One dimension below 255, one above. Area below max u16.
let rect = Rect::new(0, 0, 300, 100);
assert_eq!(rect.width, 300);
assert_eq!(rect.height, 100);
}

#[test]
fn test_constraint_apply() {
assert_eq!(Constraint::Percentage(0).apply(100), 0);
Expand Down Expand Up @@ -1078,22 +846,6 @@ mod tests {
assert_eq!(Constraint::Min(u16::MAX).apply(100), u16::MAX);
}

#[test]
fn rect_can_be_const() {
const RECT: Rect = Rect {
x: 0,
y: 0,
width: 10,
height: 10,
};
const _AREA: u16 = RECT.area();
const _LEFT: u16 = RECT.left();
const _RIGHT: u16 = RECT.right();
const _TOP: u16 = RECT.top();
const _BOTTOM: u16 = RECT.bottom();
assert!(RECT.intersects(RECT));
}

#[test]
fn layout_can_be_const() {
const _LAYOUT: Layout = Layout::new();
Expand Down
Loading

0 comments on commit cbf86da

Please sign in to comment.