diff options
| author | Laurenz <laurmaedje@gmail.com> | 2020-10-10 22:19:36 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2020-10-10 22:19:36 +0200 |
| commit | 92c01da36016e94ff20163806ddcbcf7e33d4031 (patch) | |
| tree | 1a900b3c11edcc93e9153fada3ce92310db5768b /src/geom | |
| parent | 42500d5ed85539c5ab04dd3544beaf802da29be9 (diff) | |
Switch back to custom geometry types, unified with layout primitives 🏞
Diffstat (limited to 'src/geom')
| -rw-r--r-- | src/geom/align.rs | 48 | ||||
| -rw-r--r-- | src/geom/dir.rs | 83 | ||||
| -rw-r--r-- | src/geom/gen.rs | 90 | ||||
| -rw-r--r-- | src/geom/length.rs | 213 | ||||
| -rw-r--r-- | src/geom/linear.rs | 172 | ||||
| -rw-r--r-- | src/geom/macros.rs | 47 | ||||
| -rw-r--r-- | src/geom/mod.rs | 53 | ||||
| -rw-r--r-- | src/geom/point.rs | 102 | ||||
| -rw-r--r-- | src/geom/relative.rs | 92 | ||||
| -rw-r--r-- | src/geom/sides.rs | 101 | ||||
| -rw-r--r-- | src/geom/size.rs | 119 | ||||
| -rw-r--r-- | src/geom/spec.rs | 105 |
12 files changed, 1225 insertions, 0 deletions
diff --git a/src/geom/align.rs b/src/geom/align.rs new file mode 100644 index 00000000..1030a133 --- /dev/null +++ b/src/geom/align.rs @@ -0,0 +1,48 @@ +use super::*; + +/// Where to align something along a directed axis. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Align { + /// Align at the start of the axis. + Start, + /// Align in the middle of the axis. + Center, + /// Align at the end of the axis. + End, +} + +impl Align { + /// Returns the position of this alignment in the given range. + pub fn apply(self, range: Range<Length>) -> Length { + match self { + Self::Start => range.start, + Self::Center => (range.start + range.end) / 2.0, + Self::End => range.end, + } + } + + /// The inverse alignment. + pub fn inv(self) -> Self { + match self { + Self::Start => Self::End, + Self::Center => Self::Center, + Self::End => Self::Start, + } + } +} + +impl Default for Align { + fn default() -> Self { + Self::Start + } +} + +impl Display for Align { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Start => "start", + Self::Center => "center", + Self::End => "end", + }) + } +} diff --git a/src/geom/dir.rs b/src/geom/dir.rs new file mode 100644 index 00000000..cfcb4c09 --- /dev/null +++ b/src/geom/dir.rs @@ -0,0 +1,83 @@ +use super::*; + +/// The four directions into which content can be laid out. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Dir { + /// Left to right. + LTR, + /// Right to left. + RTL, + /// Top to bottom. + TTB, + /// Bottom to top. + BTT, +} + +impl Dir { + /// The side this direction starts at. + pub fn start(self) -> Side { + match self { + Self::LTR => Side::Left, + Self::RTL => Side::Right, + Self::TTB => Side::Top, + Self::BTT => Side::Bottom, + } + } + + /// The side this direction ends at. + pub fn end(self) -> Side { + match self { + Self::LTR => Side::Right, + Self::RTL => Side::Left, + Self::TTB => Side::Bottom, + Self::BTT => Side::Top, + } + } + + /// The specific axis this direction belongs to. + pub fn axis(self) -> SpecAxis { + match self { + Self::LTR | Self::RTL => SpecAxis::Horizontal, + Self::TTB | Self::BTT => SpecAxis::Vertical, + } + } + + /// Whether this direction points into the positive coordinate direction. + /// + /// The positive directions are left-to-right and top-to-bottom. + pub fn is_positive(self) -> bool { + match self { + Self::LTR | Self::TTB => true, + Self::RTL | Self::BTT => false, + } + } + + /// The factor for this direction. + /// + /// - `1.0` if the direction is positive. + /// - `-1.0` if the direction is negative. + pub fn factor(self) -> f64 { + if self.is_positive() { 1.0 } else { -1.0 } + } + + /// The inverse direction. + pub fn inv(self) -> Self { + match self { + Self::LTR => Self::RTL, + Self::RTL => Self::LTR, + Self::TTB => Self::BTT, + Self::BTT => Self::TTB, + } + } +} + +impl Display for Dir { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::LTR => "ltr", + Self::RTL => "rtl", + Self::TTB => "ttb", + Self::BTT => "btt", + }) + } +} diff --git a/src/geom/gen.rs b/src/geom/gen.rs new file mode 100644 index 00000000..d877713b --- /dev/null +++ b/src/geom/gen.rs @@ -0,0 +1,90 @@ +use super::*; + +/// A container with a main and cross component. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Gen<T> { + /// The main component. + pub main: T, + /// The cross component. + pub cross: T, +} + +impl<T> Gen<T> { + /// Create a new instance from the two components. + pub fn new(main: T, cross: T) -> Self { + Self { main, cross } + } +} + +impl Gen<Length> { + /// The zero value. + pub const ZERO: Self = Self { main: Length::ZERO, cross: Length::ZERO }; +} + +impl<T> Get<GenAxis> for Gen<T> { + type Component = T; + + fn get(self, axis: GenAxis) -> T { + match axis { + GenAxis::Main => self.main, + GenAxis::Cross => self.cross, + } + } + + fn get_mut(&mut self, axis: GenAxis) -> &mut T { + match axis { + GenAxis::Main => &mut self.main, + GenAxis::Cross => &mut self.cross, + } + } +} + +impl<T> Switch for Gen<T> { + type Other = Spec<T>; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Spec::new(self.main, self.cross), + SpecAxis::Vertical => Spec::new(self.cross, self.main), + } + } +} + +/// The two generic layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum GenAxis { + /// The axis pages and paragraphs are set along. + Main, + /// The axis words and lines are set along. + Cross, +} + +impl GenAxis { + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Main => Self::Cross, + Self::Cross => Self::Main, + } + } +} + +impl Switch for GenAxis { + type Other = SpecAxis; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + match self { + Self::Main => dirs.main.axis(), + Self::Cross => dirs.cross.axis(), + } + } +} + +impl Display for GenAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Main => "main", + Self::Cross => "cross", + }) + } +} diff --git a/src/geom/length.rs b/src/geom/length.rs new file mode 100644 index 00000000..60ccce2b --- /dev/null +++ b/src/geom/length.rs @@ -0,0 +1,213 @@ +use super::*; + +/// An absolute length. +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +pub struct Length { + /// The length in raw units. + raw: f64, +} + +impl Length { + /// The zero length. + pub const ZERO: Self = Self { raw: 0.0 }; + + /// Create a length from a number of points. + pub fn pt(pt: f64) -> Self { + Self::with_unit(pt, Unit::Pt) + } + + /// Create a length from a number of millimeters. + pub fn mm(mm: f64) -> Self { + Self::with_unit(mm, Unit::Mm) + } + + /// Create a length from a number of centimeters. + pub fn cm(cm: f64) -> Self { + Self::with_unit(cm, Unit::Cm) + } + + /// Create a length from a number of inches. + pub fn inches(inches: f64) -> Self { + Self::with_unit(inches, Unit::In) + } + + /// Create a length from a number of raw units. + pub fn raw(raw: f64) -> Self { + Self { raw } + } + + /// Convert this to a number of points. + pub fn to_pt(self) -> f64 { + self.to_unit(Unit::Pt) + } + + /// Convert this to a number of millimeters. + pub fn to_mm(self) -> f64 { + self.to_unit(Unit::Mm) + } + + /// Convert this to a number of centimeters. + pub fn to_cm(self) -> f64 { + self.to_unit(Unit::Cm) + } + + /// Convert this to a number of inches. + pub fn to_inches(self) -> f64 { + self.to_unit(Unit::In) + } + + /// Get the value of this length in raw units. + pub fn to_raw(self) -> f64 { + self.raw + } + + /// Create a length from a value in a unit. + pub fn with_unit(val: f64, unit: Unit) -> Self { + Self { raw: val * unit.raw_scale() } + } + + /// Get the value of this length in unit. + pub fn to_unit(self, unit: Unit) -> f64 { + self.raw / unit.raw_scale() + } + + /// The minimum of this and another length. + pub fn min(self, other: Self) -> Self { + Self { raw: self.raw.min(other.raw) } + } + + /// The maximum of this and another length. + pub fn max(self, other: Self) -> Self { + Self { raw: self.raw.max(other.raw) } + } +} + +impl Display for Length { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + // Format small lengths as points and large ones as centimeters. + let (val, unit) = if self.to_pt().abs() < 25.0 { + (self.to_pt(), Unit::Pt) + } else { + (self.to_cm(), Unit::Cm) + }; + write!(f, "{:.2}{}", val, unit) + } +} + +impl Debug for Length { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Length { + type Output = Self; + + fn neg(self) -> Self { + Self { raw: -self.raw } + } +} + +impl Add for Length { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { raw: self.raw + other.raw } + } +} + +sub_impl!(Length - Length -> Length); + +impl Mul<f64> for Length { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { raw: self.raw * other } + } +} + +impl Mul<Length> for f64 { + type Output = Length; + + fn mul(self, other: Length) -> Length { + other * self + } +} + +impl Div<f64> for Length { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { raw: self.raw / other } + } +} + +assign_impl!(Length += Length); +assign_impl!(Length -= Length); +assign_impl!(Length *= f64); +assign_impl!(Length /= f64); + +impl Sum for Length { + fn sum<I: Iterator<Item = Length>>(iter: I) -> Self { + iter.fold(Length::ZERO, Add::add) + } +} + +/// Different units of measurement. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum Unit { + /// Points. + Pt, + /// Millimeters. + Mm, + /// Centimeters. + Cm, + /// Inches. + In, +} + +impl Unit { + /// How many raw units correspond to a value of `1.0` in this unit. + fn raw_scale(self) -> f64 { + match self { + Unit::Pt => 1.0, + Unit::Mm => 2.83465, + Unit::Cm => 28.3465, + Unit::In => 72.0, + } + } +} + +impl Display for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Unit::Mm => "mm", + Unit::Pt => "pt", + Unit::Cm => "cm", + Unit::In => "in", + }) + } +} + +impl Debug for Unit { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_length_formats_correctly() { + assert_eq!(Length::pt(-28.34).to_string(), "-1.00cm".to_string()); + assert_eq!(Length::pt(23.0).to_string(), "23.00pt".to_string()); + assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); + } + + #[test] + fn test_length_unit_conversion() { + assert!((Length::mm(150.0).to_cm() - 15.0) < 1e-4); + } +} diff --git a/src/geom/linear.rs b/src/geom/linear.rs new file mode 100644 index 00000000..2567d264 --- /dev/null +++ b/src/geom/linear.rs @@ -0,0 +1,172 @@ +use super::*; + +/// A combined relative and absolute length. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Linear { + /// The relative part. + pub rel: Relative, + /// The absolute part. + pub abs: Length, +} + +impl Linear { + /// The zero linear. + pub const ZERO: Linear = Linear { rel: Relative::ZERO, abs: Length::ZERO }; + + /// Create a new linear. + pub fn new(rel: Relative, abs: Length) -> Self { + Self { rel, abs } + } + + /// Evaluate the linear length with `one` being `100%` for the relative + /// part. + pub fn eval(self, one: Length) -> Length { + self.rel.eval(one) + self.abs + } + + /// Whether this linear's relative component is zero. + pub fn is_absolute(self) -> bool { + self.rel == Relative::ZERO + } +} + +impl Display for Linear { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{} + {}", self.rel, self.abs) + } +} + +impl Debug for Linear { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl From<Length> for Linear { + fn from(abs: Length) -> Self { + Self { rel: Relative::ZERO, abs } + } +} + +impl From<Relative> for Linear { + fn from(rel: Relative) -> Self { + Self { rel, abs: Length::ZERO } + } +} + +impl Neg for Linear { + type Output = Self; + + fn neg(self) -> Self { + Self { rel: -self.rel, abs: -self.abs } + } +} + +impl Add for Linear { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + rel: self.rel + other.rel, + abs: self.abs + other.abs, + } + } +} + +impl Add<Relative> for Length { + type Output = Linear; + + fn add(self, other: Relative) -> Linear { + Linear { rel: other, abs: self } + } +} + +impl Add<Length> for Relative { + type Output = Linear; + + fn add(self, other: Length) -> Linear { + other + self + } +} + +impl Add<Length> for Linear { + type Output = Self; + + fn add(self, other: Length) -> Self { + Self { rel: self.rel, abs: self.abs + other } + } +} + +impl Add<Linear> for Length { + type Output = Linear; + + fn add(self, other: Linear) -> Linear { + other + self + } +} + +impl Add<Relative> for Linear { + type Output = Self; + + fn add(self, other: Relative) -> Self { + Self { rel: self.rel + other, abs: self.abs } + } +} + +impl Add<Linear> for Relative { + type Output = Linear; + + fn add(self, other: Linear) -> Linear { + other + self + } +} + +sub_impl!(Linear - Linear -> Linear); +sub_impl!(Length - Relative -> Linear); +sub_impl!(Relative - Length -> Linear); +sub_impl!(Linear - Length -> Linear); +sub_impl!(Length - Linear -> Linear); +sub_impl!(Linear - Relative -> Linear); +sub_impl!(Relative - Linear -> Linear); + +impl Mul<f64> for Linear { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { + rel: self.rel * other, + abs: self.abs * other, + } + } +} + +impl Mul<Linear> for f64 { + type Output = Linear; + + fn mul(self, other: Linear) -> Linear { + Linear { + rel: self * other.rel, + abs: self * other.abs, + } + } +} + +impl Div<f64> for Linear { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { + rel: self.rel / other, + abs: self.abs / other, + } + } +} + +assign_impl!(Linear += Linear); +assign_impl!(Linear += Length); +assign_impl!(Linear += Relative); +assign_impl!(Linear -= Linear); +assign_impl!(Linear -= Length); +assign_impl!(Linear -= Relative); +assign_impl!(Linear *= f64); +assign_impl!(Linear /= f64); diff --git a/src/geom/macros.rs b/src/geom/macros.rs new file mode 100644 index 00000000..615eb31c --- /dev/null +++ b/src/geom/macros.rs @@ -0,0 +1,47 @@ +/// Implement the `Sub` trait based on existing `Neg` and `Add` impls. +macro_rules! sub_impl { + ($a:ident - $b:ident -> $c:ident) => { + impl Sub<$b> for $a { + type Output = $c; + + fn sub(self, other: $b) -> $c { + self + -other + } + } + }; +} + +/// Implement an assign trait based on an existing non-assign trait. +macro_rules! assign_impl { + ($a:ident += $b:ident) => { + impl AddAssign<$b> for $a { + fn add_assign(&mut self, other: $b) { + *self = *self + other; + } + } + }; + + ($a:ident -= $b:ident) => { + impl SubAssign<$b> for $a { + fn sub_assign(&mut self, other: $b) { + *self = *self - other; + } + } + }; + + ($a:ident *= $b:ident) => { + impl MulAssign<$b> for $a { + fn mul_assign(&mut self, other: $b) { + *self = *self * other; + } + } + }; + + ($a:ident /= $b:ident) => { + impl DivAssign<$b> for $a { + fn div_assign(&mut self, other: $b) { + *self = *self / other; + } + } + }; +} diff --git a/src/geom/mod.rs b/src/geom/mod.rs new file mode 100644 index 00000000..c9c3040c --- /dev/null +++ b/src/geom/mod.rs @@ -0,0 +1,53 @@ +//! Geometrical primitivies. + +#[macro_use] +mod macros; +mod align; +mod dir; +mod gen; +mod length; +mod linear; +mod point; +mod relative; +mod sides; +mod size; +mod spec; + +pub use align::*; +pub use dir::*; +pub use gen::*; +pub use length::*; +pub use linear::*; +pub use point::*; +pub use relative::*; +pub use sides::*; +pub use size::*; +pub use spec::*; + +use std::fmt::{self, Debug, Display, Formatter}; +use std::iter::Sum; +use std::ops::*; + +/// Generic access to a structure's components. +pub trait Get<Index> { + /// The structure's component type. + type Component; + + /// Return the component for the specified index. + fn get(self, index: Index) -> Self::Component; + + /// Borrow the component for the specified index mutably. + fn get_mut(&mut self, index: Index) -> &mut Self::Component; +} + +/// Switch between the specific and generic representations of a type. +/// +/// The generic representation deals with main and cross axes while the specific +/// representation deals with horizontal and vertical axes. +pub trait Switch { + /// The type of the other version. + type Other; + + /// The other version of this type based on the current directions. + fn switch(self, dirs: Gen<Dir>) -> Self::Other; +} diff --git a/src/geom/point.rs b/src/geom/point.rs new file mode 100644 index 00000000..31b84d81 --- /dev/null +++ b/src/geom/point.rs @@ -0,0 +1,102 @@ +use super::*; + +/// A point in 2D. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Point { + /// The x coordinate. + pub x: Length, + /// The y coordinate. + pub y: Length, +} + +impl Point { + /// The origin point. + pub const ZERO: Self = Self { x: Length::ZERO, y: Length::ZERO }; + + /// Create a new point from x and y coordinate. + pub fn new(x: Length, y: Length) -> Self { + Self { x, y } + } +} + +impl Get<SpecAxis> for Point { + type Component = Length; + + fn get(self, axis: SpecAxis) -> Length { + match axis { + SpecAxis::Horizontal => self.x, + SpecAxis::Vertical => self.y, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + match axis { + SpecAxis::Horizontal => &mut self.x, + SpecAxis::Vertical => &mut self.y, + } + } +} + +impl Switch for Point { + type Other = Gen<Length>; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.x, self.y), + SpecAxis::Vertical => Gen::new(self.y, self.x), + } + } +} + +impl Debug for Point { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({}, {})", self.x, self.y) + } +} + +impl Neg for Point { + type Output = Self; + + fn neg(self) -> Self { + Self { x: -self.x, y: -self.y } + } +} + +impl Add for Point { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { x: self.x + other.x, y: self.y + other.y } + } +} + +sub_impl!(Point - Point -> Point); + +impl Mul<f64> for Point { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { x: self.x * other, y: self.y * other } + } +} + +impl Mul<Point> for f64 { + type Output = Point; + + fn mul(self, other: Point) -> Point { + other * self + } +} + +impl Div<f64> for Point { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { x: self.x / other, y: self.y / other } + } +} + +assign_impl!(Point += Point); +assign_impl!(Point -= Point); +assign_impl!(Point *= f64); +assign_impl!(Point /= f64); diff --git a/src/geom/relative.rs b/src/geom/relative.rs new file mode 100644 index 00000000..037c83dc --- /dev/null +++ b/src/geom/relative.rs @@ -0,0 +1,92 @@ +use super::*; + +/// A relative length. +/// +/// _Note_: `50%` is represented as `0.5` here, but stored as `50.0` in the +/// corresponding [literal]. +/// +/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent +#[derive(Default, Copy, Clone, PartialEq, PartialOrd)] +pub struct Relative(f64); + +impl Relative { + /// A ratio of `0%` represented as `0.0`. + pub const ZERO: Self = Self(0.0); + + /// A ratio of `100%` represented as `1.0`. + pub const ONE: Self = Self(1.0); + + /// Create a new relative value. + pub fn new(ratio: f64) -> Self { + Self(ratio) + } + + /// Get the underlying ratio. + pub fn get(self) -> f64 { + self.0 + } + + /// Evaluate the relative length with `one` being `100%`. + pub fn eval(self, one: Length) -> Length { + self.get() * one + } +} + +impl Display for Relative { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{:.2}%", self.0) + } +} + +impl Debug for Relative { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Neg for Relative { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Relative { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +sub_impl!(Relative - Relative -> Relative); + +impl Mul<f64> for Relative { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul<Relative> for f64 { + type Output = Relative; + + fn mul(self, other: Relative) -> Relative { + other * self + } +} + +impl Div<f64> for Relative { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +assign_impl!(Relative += Relative); +assign_impl!(Relative -= Relative); +assign_impl!(Relative *= f64); +assign_impl!(Relative /= f64); diff --git a/src/geom/sides.rs b/src/geom/sides.rs new file mode 100644 index 00000000..770fad58 --- /dev/null +++ b/src/geom/sides.rs @@ -0,0 +1,101 @@ +use super::*; + +/// A container with left, top, right and bottom components. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Sides<T> { + /// The value for the left side. + pub left: T, + /// The value for the top side. + pub top: T, + /// The value for the right side. + pub right: T, + /// The value for the bottom side. + pub bottom: T, +} + +impl<T> Sides<T> { + /// Create a new box from four sizes. + pub fn new(left: T, top: T, right: T, bottom: T) -> Self { + Self { left, top, right, bottom } + } + + /// Create an instance with all four components set to the same `value`. + pub fn uniform(value: T) -> Self + where + T: Clone, + { + Self { + left: value.clone(), + top: value.clone(), + right: value.clone(), + bottom: value, + } + } +} + +impl Sides<Linear> { + /// Evaluate the linear values in this container. + pub fn eval(self, size: Size) -> Sides<Length> { + Sides { + left: self.left.eval(size.width), + top: self.top.eval(size.height), + right: self.right.eval(size.width), + bottom: self.bottom.eval(size.height), + } + } +} + +impl Sides<Length> { + /// A size with `left` and `right` summed into `width`, and `top` and + /// `bottom` summed into `height`. + pub fn size(self) -> Size { + Size::new(self.left + self.right, self.top + self.bottom) + } +} + +impl<T> Get<Side> for Sides<T> { + type Component = T; + + fn get(self, side: Side) -> T { + match side { + Side::Left => self.left, + Side::Top => self.top, + Side::Right => self.right, + Side::Bottom => self.bottom, + } + } + + fn get_mut(&mut self, side: Side) -> &mut T { + match side { + Side::Left => &mut self.left, + Side::Top => &mut self.top, + Side::Right => &mut self.right, + Side::Bottom => &mut self.bottom, + } + } +} + +/// The four sides of objects. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum Side { + /// The left side. + Left, + /// The top side. + Top, + /// The right side. + Right, + /// The bottom side. + Bottom, +} + +impl Side { + /// The opposite side. + pub fn inv(self) -> Self { + match self { + Self::Left => Self::Right, + Self::Top => Self::Bottom, + Self::Right => Self::Left, + Self::Bottom => Self::Top, + } + } +} diff --git a/src/geom/size.rs b/src/geom/size.rs new file mode 100644 index 00000000..8a3951f7 --- /dev/null +++ b/src/geom/size.rs @@ -0,0 +1,119 @@ +use super::*; + +/// A size in 2D. +#[derive(Default, Copy, Clone, PartialEq)] +pub struct Size { + /// The width. + pub width: Length, + /// The height. + pub height: Length, +} + +impl Size { + /// The zero size. + pub const ZERO: Self = Self { + width: Length::ZERO, + height: Length::ZERO, + }; + + /// Create a new size from width and height. + pub fn new(width: Length, height: Length) -> Self { + Self { width, height } + } + + /// Whether the other size fits into this one (smaller width and height). + pub fn fits(self, other: Self) -> bool { + self.width >= other.width && self.height >= other.height + } +} + +impl Get<SpecAxis> for Size { + type Component = Length; + + fn get(self, axis: SpecAxis) -> Length { + match axis { + SpecAxis::Horizontal => self.width, + SpecAxis::Vertical => self.height, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut Length { + match axis { + SpecAxis::Horizontal => &mut self.width, + SpecAxis::Vertical => &mut self.height, + } + } +} + +impl Switch for Size { + type Other = Gen<Length>; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.width, self.height), + SpecAxis::Vertical => Gen::new(self.height, self.width), + } + } +} + +impl Debug for Size { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "({} x {})", self.width, self.height) + } +} + +impl Neg for Size { + type Output = Self; + + fn neg(self) -> Self { + Self { width: -self.width, height: -self.height } + } +} + +impl Add for Size { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self { + width: self.width + other.width, + height: self.height + other.height, + } + } +} + +sub_impl!(Size - Size -> Size); + +impl Mul<f64> for Size { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self { + width: self.width * other, + height: self.height * other, + } + } +} + +impl Mul<Size> for f64 { + type Output = Size; + + fn mul(self, other: Size) -> Size { + other * self + } +} + +impl Div<f64> for Size { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self { + width: self.width / other, + height: self.height / other, + } + } +} + +assign_impl!(Size -= Size); +assign_impl!(Size += Size); +assign_impl!(Size *= f64); +assign_impl!(Size /= f64); diff --git a/src/geom/spec.rs b/src/geom/spec.rs new file mode 100644 index 00000000..8a9519bc --- /dev/null +++ b/src/geom/spec.rs @@ -0,0 +1,105 @@ +use super::*; + +/// A container with a horizontal and vertical component. +#[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] +pub struct Spec<T> { + /// The horizontal component. + pub horizontal: T, + /// The vertical component. + pub vertical: T, +} + +impl<T> Spec<T> { + /// Create a new instance from the two components. + pub fn new(horizontal: T, vertical: T) -> Self { + Self { horizontal, vertical } + } +} + +impl Spec<Length> { + /// The zero value. + pub const ZERO: Self = Self { + horizontal: Length::ZERO, + vertical: Length::ZERO, + }; + + /// Convert to a point. + pub fn to_point(self) -> Point { + Point::new(self.horizontal, self.vertical) + } + + /// Convert to a size. + pub fn to_size(self) -> Size { + Size::new(self.horizontal, self.vertical) + } +} + +impl<T> Get<SpecAxis> for Spec<T> { + type Component = T; + + fn get(self, axis: SpecAxis) -> T { + match axis { + SpecAxis::Horizontal => self.horizontal, + SpecAxis::Vertical => self.vertical, + } + } + + fn get_mut(&mut self, axis: SpecAxis) -> &mut T { + match axis { + SpecAxis::Horizontal => &mut self.horizontal, + SpecAxis::Vertical => &mut self.vertical, + } + } +} + +impl<T> Switch for Spec<T> { + type Other = Gen<T>; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + match dirs.main.axis() { + SpecAxis::Horizontal => Gen::new(self.horizontal, self.vertical), + SpecAxis::Vertical => Gen::new(self.vertical, self.horizontal), + } + } +} + +/// The two specific layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SpecAxis { + /// The vertical layouting axis. + Vertical, + /// The horizontal layouting axis. + Horizontal, +} + +impl SpecAxis { + /// The other axis. + pub fn other(self) -> Self { + match self { + Self::Horizontal => Self::Vertical, + Self::Vertical => Self::Horizontal, + } + } +} + +impl Switch for SpecAxis { + type Other = GenAxis; + + fn switch(self, dirs: Gen<Dir>) -> Self::Other { + if self == dirs.main.axis() { + GenAxis::Main + } else { + debug_assert_eq!(self, dirs.cross.axis()); + GenAxis::Cross + } + } +} + +impl Display for SpecAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Vertical => "vertical", + Self::Horizontal => "horizontal", + }) + } +} |
