ferroui_controls/
grid_length.rs1use ferroui_base::utilities::span_helpers::{parse_double, InvariantF64};
2use ferroui_base::utilities::{FormatError, SpanStringTokenizer};
3use std::fmt;
4use std::hash::{Hash, Hasher};
5use std::str::FromStr;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
9#[repr(i32)]
10pub enum GridUnitType {
11 #[default]
13 Auto = 0,
14
15 Pixel = 1,
17
18 Star = 2,
20}
21
22#[derive(Clone, Copy, Debug, Default)]
26pub struct GridLength {
27 type_: GridUnitType,
28 value: f64,
29}
30
31impl GridLength {
32 pub const AUTO: GridLength = GridLength {
35 type_: GridUnitType::Auto,
36 value: 0.0,
37 };
38
39 pub const STAR: GridLength = GridLength {
41 type_: GridUnitType::Star,
42 value: 1.0,
43 };
44
45 pub fn from_pixels(value: f64) -> Self {
49 Self::new(value, GridUnitType::Pixel)
50 }
51
52 pub fn new(value: f64, type_: GridUnitType) -> Self {
56 if !Self::is_valid_value(value) {
57 panic!("Invalid value");
58 }
59
60 Self { type_, value }
61 }
62
63 fn is_valid_value(value: f64) -> bool {
64 !(value < 0.0 || value.is_nan() || value.is_infinite())
65 }
66
67 #[inline]
70 pub const fn auto() -> Self {
71 Self::AUTO
72 }
73
74 #[inline]
76 pub const fn star() -> Self {
77 Self::STAR
78 }
79
80 #[inline]
82 pub const fn grid_unit_type(&self) -> GridUnitType {
83 self.type_
84 }
85
86 #[inline]
88 pub fn is_absolute(&self) -> bool {
89 self.type_ == GridUnitType::Pixel
90 }
91
92 #[inline]
94 pub fn is_auto(&self) -> bool {
95 self.type_ == GridUnitType::Auto
96 }
97
98 #[inline]
100 pub fn is_star(&self) -> bool {
101 self.type_ == GridUnitType::Star
102 }
103
104 #[inline]
106 pub const fn value(&self) -> f64 {
107 self.value
108 }
109
110 pub fn parse(s: &str) -> Result<GridLength, FormatError> {
112 let s = s.to_uppercase();
113
114 if s == "AUTO" {
115 Ok(Self::AUTO)
116 } else if let Some(value_string) = s.strip_suffix('*') {
117 let value_string = value_string.trim();
118 let value = if !value_string.is_empty() {
119 Self::parse_value(value_string)?
120 } else {
121 1.0
122 };
123 Ok(GridLength {
124 type_: GridUnitType::Star,
125 value,
126 })
127 } else {
128 let value = Self::parse_value(&s)?;
129 Ok(GridLength {
130 type_: GridUnitType::Pixel,
131 value,
132 })
133 }
134 }
135
136 fn parse_value(s: &str) -> Result<f64, FormatError> {
137 match parse_double(s) {
138 Some(value) if Self::is_valid_value(value) => Ok(value),
139 _ => Err(FormatError::invalid_input(s)),
140 }
141 }
142
143 pub fn parse_lengths(s: &str) -> Result<Vec<GridLength>, FormatError> {
145 let mut result = Vec::new();
146
147 SpanStringTokenizer::new(s).scope(|tokenizer| {
148 while let Some(item) = tokenizer.try_read_string()? {
149 result.push(Self::parse(item)?);
150 }
151 Ok(())
152 })?;
153
154 Ok(result)
155 }
156}
157
158impl PartialEq for GridLength {
159 fn eq(&self, other: &Self) -> bool {
160 (self.is_auto() && other.is_auto()) || (self.value == other.value && self.type_ == other.type_)
161 }
162}
163
164impl Hash for GridLength {
165 fn hash<H: Hasher>(&self, state: &mut H) {
166 if !self.is_auto() {
168 (self.value + 0.0).to_bits().hash(state);
170 }
171 self.type_.hash(state);
172 }
173}
174
175impl fmt::Display for GridLength {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 if self.is_auto() {
178 return f.write_str("Auto");
179 }
180
181 InvariantF64(self.value).fmt(f)?;
182 if self.is_star() {
183 f.write_str("*")?;
184 }
185 Ok(())
186 }
187}
188
189impl FromStr for GridLength {
190 type Err = FormatError;
191
192 fn from_str(s: &str) -> Result<Self, Self::Err> {
193 Self::parse(s)
194 }
195}