//! Contains numerical helper traits and functions #![allow(clippy::manual_clamp)] use crate::geometry::Size; use crate::style::AvailableSpace; /// A trait to conveniently calculate minimums and maximums when some data may not be defined /// /// If the left-hand value is [`None`], these operations return [`None`]. /// If the right-hand value is [`None`], it is treated as zero. pub trait MaybeMath { /// Returns the minimum of `self` and `rhs` fn maybe_min(self, rhs: In) -> Out; /// Returns the maximum of `self` and `rhs` fn maybe_max(self, rhs: In) -> Out; /// Returns `self` clamped between `min` and `max` fn maybe_clamp(self, min: In, max: In) -> Out; /// Adds `self` and `rhs`. fn maybe_add(self, rhs: In) -> Out; /// Subtracts rhs from `self`, treating [`None`] values as default fn maybe_sub(self, rhs: In) -> Out; } impl MaybeMath, Option> for Option { fn maybe_min(self, rhs: Option) -> Option { match (self, rhs) { (Some(l), Some(r)) => Some(l.min(r)), (Some(_l), None) => self, (None, Some(_r)) => None, (None, None) => None, } } fn maybe_max(self, rhs: Option) -> Option { match (self, rhs) { (Some(l), Some(r)) => Some(l.max(r)), (Some(_l), None) => self, (None, Some(_r)) => None, (None, None) => None, } } fn maybe_clamp(self, min: Option, max: Option) -> Option { match (self, min, max) { (Some(base), Some(min), Some(max)) => Some(base.min(max).max(min)), (Some(base), None, Some(max)) => Some(base.min(max)), (Some(base), Some(min), None) => Some(base.max(min)), (Some(_), None, None) => self, (None, _, _) => None, } } fn maybe_add(self, rhs: Option) -> Option { match (self, rhs) { (Some(l), Some(r)) => Some(l + r), (Some(_l), None) => self, (None, Some(_r)) => None, (None, None) => None, } } fn maybe_sub(self, rhs: Option) -> Option { match (self, rhs) { (Some(l), Some(r)) => Some(l - r), (Some(_l), None) => self, (None, Some(_r)) => None, (None, None) => None, } } } impl MaybeMath> for Option { fn maybe_min(self, rhs: f32) -> Option { self.map(|val| val.min(rhs)) } fn maybe_max(self, rhs: f32) -> Option { self.map(|val| val.max(rhs)) } fn maybe_clamp(self, min: f32, max: f32) -> Option { self.map(|val| val.min(max).max(min)) } fn maybe_add(self, rhs: f32) -> Option { self.map(|val| val + rhs) } fn maybe_sub(self, rhs: f32) -> Option { self.map(|val| val - rhs) } } impl MaybeMath, f32> for f32 { fn maybe_min(self, rhs: Option) -> f32 { match rhs { Some(val) => self.min(val), None => self, } } fn maybe_max(self, rhs: Option) -> f32 { match rhs { Some(val) => self.max(val), None => self, } } fn maybe_clamp(self, min: Option, max: Option) -> f32 { match (min, max) { (Some(min), Some(max)) => self.min(max).max(min), (None, Some(max)) => self.min(max), (Some(min), None) => self.max(min), (None, None) => self, } } fn maybe_add(self, rhs: Option) -> f32 { match rhs { Some(val) => self + val, None => self, } } fn maybe_sub(self, rhs: Option) -> f32 { match rhs { Some(val) => self - val, None => self, } } } impl MaybeMath for AvailableSpace { fn maybe_min(self, rhs: f32) -> AvailableSpace { match self { AvailableSpace::Definite(val) => AvailableSpace::Definite(val.min(rhs)), AvailableSpace::MinContent => AvailableSpace::Definite(rhs), AvailableSpace::MaxContent => AvailableSpace::Definite(rhs), } } fn maybe_max(self, rhs: f32) -> AvailableSpace { match self { AvailableSpace::Definite(val) => AvailableSpace::Definite(val.max(rhs)), AvailableSpace::MinContent => AvailableSpace::MinContent, AvailableSpace::MaxContent => AvailableSpace::MaxContent, } } fn maybe_clamp(self, min: f32, max: f32) -> AvailableSpace { match self { AvailableSpace::Definite(val) => AvailableSpace::Definite(val.min(max).max(min)), AvailableSpace::MinContent => AvailableSpace::MinContent, AvailableSpace::MaxContent => AvailableSpace::MaxContent, } } fn maybe_add(self, rhs: f32) -> AvailableSpace { match self { AvailableSpace::Definite(val) => AvailableSpace::Definite(val + rhs), AvailableSpace::MinContent => AvailableSpace::MinContent, AvailableSpace::MaxContent => AvailableSpace::MaxContent, } } fn maybe_sub(self, rhs: f32) -> AvailableSpace { match self { AvailableSpace::Definite(val) => AvailableSpace::Definite(val - rhs), AvailableSpace::MinContent => AvailableSpace::MinContent, AvailableSpace::MaxContent => AvailableSpace::MaxContent, } } } impl MaybeMath, AvailableSpace> for AvailableSpace { fn maybe_min(self, rhs: Option) -> AvailableSpace { match (self, rhs) { (AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val.min(rhs)), (AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val), (AvailableSpace::MinContent, Some(rhs)) => AvailableSpace::Definite(rhs), (AvailableSpace::MinContent, None) => AvailableSpace::MinContent, (AvailableSpace::MaxContent, Some(rhs)) => AvailableSpace::Definite(rhs), (AvailableSpace::MaxContent, None) => AvailableSpace::MaxContent, } } fn maybe_max(self, rhs: Option) -> AvailableSpace { match (self, rhs) { (AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val.max(rhs)), (AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val), (AvailableSpace::MinContent, _) => AvailableSpace::MinContent, (AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent, } } fn maybe_clamp(self, min: Option, max: Option) -> AvailableSpace { match (self, min, max) { (AvailableSpace::Definite(val), Some(min), Some(max)) => AvailableSpace::Definite(val.min(max).max(min)), (AvailableSpace::Definite(val), None, Some(max)) => AvailableSpace::Definite(val.min(max)), (AvailableSpace::Definite(val), Some(min), None) => AvailableSpace::Definite(val.max(min)), (AvailableSpace::Definite(val), None, None) => AvailableSpace::Definite(val), (AvailableSpace::MinContent, _, _) => AvailableSpace::MinContent, (AvailableSpace::MaxContent, _, _) => AvailableSpace::MaxContent, } } fn maybe_add(self, rhs: Option) -> AvailableSpace { match (self, rhs) { (AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val + rhs), (AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val), (AvailableSpace::MinContent, _) => AvailableSpace::MinContent, (AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent, } } fn maybe_sub(self, rhs: Option) -> AvailableSpace { match (self, rhs) { (AvailableSpace::Definite(val), Some(rhs)) => AvailableSpace::Definite(val - rhs), (AvailableSpace::Definite(val), None) => AvailableSpace::Definite(val), (AvailableSpace::MinContent, _) => AvailableSpace::MinContent, (AvailableSpace::MaxContent, _) => AvailableSpace::MaxContent, } } } impl> MaybeMath, Size> for Size { fn maybe_min(self, rhs: Size) -> Size { Size { width: self.width.maybe_min(rhs.width), height: self.height.maybe_min(rhs.height) } } fn maybe_max(self, rhs: Size) -> Size { Size { width: self.width.maybe_max(rhs.width), height: self.height.maybe_max(rhs.height) } } fn maybe_clamp(self, min: Size, max: Size) -> Size { Size { width: self.width.maybe_clamp(min.width, max.width), height: self.height.maybe_clamp(min.height, max.height), } } fn maybe_add(self, rhs: Size) -> Size { Size { width: self.width.maybe_add(rhs.width), height: self.height.maybe_add(rhs.height) } } fn maybe_sub(self, rhs: Size) -> Size { Size { width: self.width.maybe_sub(rhs.width), height: self.height.maybe_sub(rhs.height) } } } #[cfg(test)] mod tests { mod lhs_option_f32_rhs_option_f32 { use crate::util::MaybeMath; #[test] fn test_maybe_min() { assert_eq!(Some(3.0).maybe_min(Some(5.0)), Some(3.0)); assert_eq!(Some(5.0).maybe_min(Some(3.0)), Some(3.0)); assert_eq!(Some(3.0).maybe_min(None), Some(3.0)); assert_eq!(None.maybe_min(Some(3.0)), None); assert_eq!(None.maybe_min(None), None); } #[test] fn test_maybe_max() { assert_eq!(Some(3.0).maybe_max(Some(5.0)), Some(5.0)); assert_eq!(Some(5.0).maybe_max(Some(3.0)), Some(5.0)); assert_eq!(Some(3.0).maybe_max(None), Some(3.0)); assert_eq!(None.maybe_max(Some(3.0)), None); assert_eq!(None.maybe_max(None), None); } #[test] fn test_maybe_add() { assert_eq!(Some(3.0).maybe_add(Some(5.0)), Some(8.0)); assert_eq!(Some(5.0).maybe_add(Some(3.0)), Some(8.0)); assert_eq!(Some(3.0).maybe_add(None), Some(3.0)); assert_eq!(None.maybe_add(Some(3.0)), None); assert_eq!(None.maybe_add(None), None); } #[test] fn test_maybe_sub() { assert_eq!(Some(3.0).maybe_sub(Some(5.0)), Some(-2.0)); assert_eq!(Some(5.0).maybe_sub(Some(3.0)), Some(2.0)); assert_eq!(Some(3.0).maybe_sub(None), Some(3.0)); assert_eq!(None.maybe_sub(Some(3.0)), None); assert_eq!(None.maybe_sub(None), None); } } mod lhs_option_f32_rhs_f32 { use crate::util::MaybeMath; #[test] fn test_maybe_min() { assert_eq!(Some(3.0).maybe_min(5.0), Some(3.0)); assert_eq!(Some(5.0).maybe_min(3.0), Some(3.0)); assert_eq!(None.maybe_min(3.0), None); } #[test] fn test_maybe_max() { assert_eq!(Some(3.0).maybe_max(5.0), Some(5.0)); assert_eq!(Some(5.0).maybe_max(3.0), Some(5.0)); assert_eq!(None.maybe_max(3.0), None); } #[test] fn test_maybe_add() { assert_eq!(Some(3.0).maybe_add(5.0), Some(8.0)); assert_eq!(Some(5.0).maybe_add(3.0), Some(8.0)); assert_eq!(None.maybe_add(3.0), None); } #[test] fn test_maybe_sub() { assert_eq!(Some(3.0).maybe_sub(5.0), Some(-2.0)); assert_eq!(Some(5.0).maybe_sub(3.0), Some(2.0)); assert_eq!(None.maybe_sub(3.0), None); } } mod lhs_f32_rhs_option_f32 { use crate::util::MaybeMath; #[test] fn test_maybe_min() { assert_eq!(3.0.maybe_min(Some(5.0)), 3.0); assert_eq!(5.0.maybe_min(Some(3.0)), 3.0); assert_eq!(3.0.maybe_min(None), 3.0); } #[test] fn test_maybe_max() { assert_eq!(3.0.maybe_max(Some(5.0)), 5.0); assert_eq!(5.0.maybe_max(Some(3.0)), 5.0); assert_eq!(3.0.maybe_max(None), 3.0); } #[test] fn test_maybe_add() { assert_eq!(3.0.maybe_add(Some(5.0)), 8.0); assert_eq!(5.0.maybe_add(Some(3.0)), 8.0); assert_eq!(3.0.maybe_add(None), 3.0); } #[test] fn test_maybe_sub() { assert_eq!(3.0.maybe_sub(Some(5.0)), -2.0); assert_eq!(5.0.maybe_sub(Some(3.0)), 2.0); assert_eq!(3.0.maybe_sub(None), 3.0); } } }