StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | use derive_more::{AddAssign, SubAssign}; |
| 2 | use ordered_float::OrderedFloat; |
| 3 | use serde::{Deserialize, Serialize}; |
| 4 | use std::fmt::{Display, Formatter}; |
| 5 | use std::ops::Neg; |
| 6 | |
| 7 | /// Newtype representing a position in line coordinates. See `to_pixel` to convert to pixel |
| 8 | /// coordinates. |
| 9 | #[derive( |
| 10 | Clone, |
| 11 | Copy, |
| 12 | Debug, |
| 13 | Default, |
| 14 | Eq, |
| 15 | PartialEq, |
| 16 | Ord, |
| 17 | PartialOrd, |
| 18 | AddAssign, |
| 19 | SubAssign, |
| 20 | num_derive::FromPrimitive, |
| 21 | num_derive::ToPrimitive, |
| 22 | num_derive::NumCast, |
| 23 | num_derive::NumOps, |
| 24 | num_derive::Zero, |
| 25 | num_derive::One, |
| 26 | num_derive::Num, |
| 27 | num_derive::Float, |
| 28 | )] |
| 29 | pub struct Lines(OrderedFloat<f64>); |
| 30 | |
| 31 | impl Lines { |
| 32 | /// The epsilon value used for approximate equality checks. |
| 33 | const APPROX_EQ_EPSILON: f64 = 0.000001; |
| 34 | |
| 35 | pub const fn new(lines: f64) -> Self { |
| 36 | Lines(OrderedFloat(lines)) |
| 37 | } |
| 38 | |
| 39 | pub fn to_pixels(self, line_height: impl Into<Pixels>) -> Pixels { |
| 40 | let line_height = line_height.into(); |
| 41 | Pixels(self.as_f64() as f32 * line_height.0) |
| 42 | } |
| 43 | |
| 44 | pub const fn zero() -> Self { |
| 45 | Self::new(0.) |
| 46 | } |
| 47 | |
| 48 | pub fn fract(&self) -> Lines { |
| 49 | self.0.fract().into_lines() |
| 50 | } |
| 51 | |
| 52 | pub fn as_f64(&self) -> f64 { |
| 53 | self.0 .0 |
| 54 | } |
| 55 | |
| 56 | pub fn max(self, other: Self) -> Self { |
| 57 | Self(self.0.max(other.0)) |
| 58 | } |
| 59 | |
| 60 | pub fn min(self, other: Self) -> Self { |
| 61 | Self(self.0.min(other.0)) |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | impl Neg for Lines { |
| 66 | type Output = Self; |
| 67 | |
| 68 | fn neg(self) -> Self::Output { |
| 69 | Self(self.0.neg()) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | #[cfg(any(test, feature = "test-util"))] |
| 74 | impl std::ops::Add<f64> for Lines { |
| 75 | type Output = Self; |
| 76 | |
| 77 | fn add(self, rhs: f64) -> Self::Output { |
| 78 | Self(self.0 + rhs) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | #[cfg(any(test, feature = "test-util"))] |
| 83 | impl std::ops::Sub<f64> for Lines { |
| 84 | type Output = Self; |
| 85 | |
| 86 | fn sub(self, rhs: f64) -> Self::Output { |
| 87 | Self(self.0 - rhs) |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | impl Display for Lines { |
| 92 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 93 | write!(f, "{}", self.0) |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | impl float_cmp::ApproxEq for Lines { |
| 98 | type Margin = float_cmp::F64Margin; |
| 99 | |
| 100 | fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool { |
| 101 | let margin: Self::Margin = margin.into(); |
| 102 | let margin = Self::Margin { |
| 103 | // Make sure we use an epsilon value that's at least APPROX_EQ_EPSILON. |
| 104 | epsilon: margin.epsilon.max(Self::APPROX_EQ_EPSILON), |
| 105 | ulps: margin.ulps, |
| 106 | }; |
| 107 | self.as_f64().approx_eq(other.as_f64(), margin) |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /// Newtype representing a position in pixel coordinates. See `to_lines` to convert to line |
| 112 | /// coordinates. |
| 113 | #[derive( |
| 114 | Clone, |
| 115 | Copy, |
| 116 | Debug, |
| 117 | Default, |
| 118 | PartialOrd, |
| 119 | PartialEq, |
| 120 | AddAssign, |
| 121 | SubAssign, |
| 122 | Serialize, |
| 123 | Deserialize, |
| 124 | num_derive::FromPrimitive, |
| 125 | num_derive::ToPrimitive, |
| 126 | num_derive::NumCast, |
| 127 | num_derive::NumOps, |
| 128 | num_derive::Zero, |
| 129 | num_derive::One, |
| 130 | num_derive::Num, |
| 131 | num_derive::Float, |
| 132 | )] |
| 133 | #[cfg_attr(feature = "schema_gen", derive(schemars::JsonSchema))] |
| 134 | #[cfg_attr(feature = "schema_gen", schemars(description = "A value in pixels."))] |
| 135 | pub struct Pixels(f32); |
| 136 | |
| 137 | impl Pixels { |
| 138 | pub const fn new(pixels: f32) -> Self { |
| 139 | Pixels(pixels) |
| 140 | } |
| 141 | |
| 142 | pub fn to_lines(self, line_height: Pixels) -> Lines { |
| 143 | Lines(OrderedFloat(self.0 as f64 / line_height.0 as f64)) |
| 144 | } |
| 145 | |
| 146 | pub fn fract(&self) -> Pixels { |
| 147 | self.0.fract().into_pixels() |
| 148 | } |
| 149 | |
| 150 | pub fn zero() -> Self { |
| 151 | Pixels(0.) |
| 152 | } |
| 153 | |
| 154 | pub fn as_f32(&self) -> f32 { |
| 155 | self.0 |
| 156 | } |
| 157 | |
| 158 | pub fn max(self, other: Self) -> Self { |
| 159 | Self(self.0.max(other.0)) |
| 160 | } |
| 161 | |
| 162 | pub fn min(self, other: Self) -> Self { |
| 163 | Self(self.0.min(other.0)) |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | impl Neg for Pixels { |
| 168 | type Output = Self; |
| 169 | |
| 170 | fn neg(self) -> Self::Output { |
| 171 | Self(self.0.neg()) |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | impl Display for Pixels { |
| 176 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
| 177 | self.0.fmt(f) |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | impl float_cmp::ApproxEq for Pixels { |
| 182 | type Margin = float_cmp::F32Margin; |
| 183 | |
| 184 | fn approx_eq<M: Into<Self::Margin>>(self, other: Self, margin: M) -> bool { |
| 185 | self.as_f32().approx_eq(other.as_f32(), margin) |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | /// Trait to convert an arbitrary type to `Pixels`. |
| 190 | pub trait IntoPixels { |
| 191 | fn into_pixels(self) -> Pixels; |
| 192 | } |
| 193 | |
| 194 | /// Trait to convert an arbitrary type to `Lines`. |
| 195 | pub trait IntoLines { |
| 196 | fn into_lines(self) -> Lines; |
| 197 | } |
| 198 | |
| 199 | impl IntoLines for Lines { |
| 200 | fn into_lines(self) -> Lines { |
| 201 | self |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | macro_rules! impl_into_pixels { |
| 206 | ($($t:ident)*) => ($(impl IntoPixels for $t { |
| 207 | fn into_pixels(self) -> Pixels { |
| 208 | Pixels(self as f32) |
| 209 | } |
| 210 | })*) |
| 211 | } |
| 212 | |
| 213 | macro_rules! impl_into_lines { |
| 214 | ($($t:ident)*) => ($(impl IntoLines for $t { |
| 215 | fn into_lines(self) -> Lines { |
| 216 | Lines(OrderedFloat(self as f64)) |
| 217 | } |
| 218 | })*) |
| 219 | } |
| 220 | |
| 221 | impl_into_pixels! { usize f32 f64 } |
| 222 | impl_into_lines! { usize i32 u32 u64 f32 f64 } |
| 223 |