egui_typed_input/
impls.rs1use core::str::FromStr;
2use egui::Color32;
3
4use crate::ValText;
5
6impl ValText<Color32, egui::ecolor::ParseHexColorError> {
7 #[must_use]
10 pub fn color_hex() -> Self {
11 Self {
12 text: String::new(),
13 parsed_val: Some(Err(egui::ecolor::ParseHexColorError::MissingHash)),
14 value_parser: Box::new(|str| Color32::from_hex(str)),
15 input_validator: Box::new(|_, s, i| {
16 if i == 0 {
17 return s.starts_with('#');
18 }
19 s.chars().all(|c| c.is_ascii_hexdigit())
20 }),
21 }
22 }
23}
24
25impl<T: FromStr> ValText<T, T::Err> {
26 #[must_use]
28 pub fn number() -> Self {
29 Self {
30 text: String::new(),
31 parsed_val: None,
32 value_parser: Box::new(|str| str.parse()),
33 input_validator: Box::new(|current_text, s, i| {
34 let current_has_no_dot = !current_text.contains('.');
35 (if i == 0 {
36 s.starts_with('+') || s.starts_with('-')
37 } else { false })
38 || s.chars().all(|c| {
39 (if current_has_no_dot { c == '.' } else { false })
40 || c.is_ascii_digit()
41 })
42 }),
43 }
44 }
45
46 #[must_use]
48 pub fn number_int() -> Self {
49 Self {
50 text: String::new(),
51 parsed_val: None,
52 value_parser: Box::new(|str| str.parse()),
53 input_validator: Box::new(|_, s, i| {
54 (if i == 0 {
55 s.starts_with('+') || s.starts_with('-')
56 } else { false })
57 || s.chars().all(|c| c.is_ascii_digit())
58 })
59 }
60 }
61
62 #[must_use]
64 pub fn number_uint() -> Self {
65 Self {
66 text: String::new(),
67 parsed_val: None,
68 value_parser: Box::new(|str| str.parse()),
69 input_validator: Box::new(|_, s, i| {
70 (if i == 0 {
71 s.starts_with('+')
72 } else { false })
73 || s.chars().all(|c| c.is_ascii_digit())
74 })
75 }
76 }
77}
78
79#[derive(Debug, thiserror::Error)]
80pub enum PercentageParseError {
81 #[error("number is more then 100")]
83 OutOfRangeHigh,
84 #[error("number is less then 0")]
86 Neg,
87 #[error(transparent)]
88 ParseFloat(#[from] core::num::ParseFloatError),
89 #[error(transparent)]
90 ParseInt(#[from] core::num::ParseIntError),
91}
92
93impl ValText<f64, PercentageParseError> {
94 #[must_use]
98 pub fn percentage() -> Self {
99 Self {
100 text: String::new(),
101 parsed_val: None,
102 value_parser: Box::new(|str| {
103 let num = str.parse();
104 match num {
105 Ok(num) => {
106 if num > 100.0 {
107 Err(PercentageParseError::OutOfRangeHigh)
108 } else if num < 0.0 {
109 Err(PercentageParseError::Neg)
110 } else {
111 Ok(num)
112 }
113 },
114 Err(e) => Err(e.into()),
115 }
116 }),
117 input_validator: Box::new(|current_text, s, i| {
118 let current_text_no_des_len = current_text.split_once('.')
119 .map(|(pre_dot, _)| pre_dot.len())
120 .unwrap_or(current_text.len());
121 if current_text_no_des_len + s.len() > 3 && !current_text.contains('.') {
122 return false;
123 }
124
125 let current_has_no_dot = !current_text.contains('.');
126 let all_num_or_dot = s.chars().all(|c| {
127 (if current_has_no_dot { c == '.' } else { false })
128 || c.is_ascii_digit()
129 });
130
131 if !current_text.is_empty()
132 && current_text.as_bytes()[i.saturating_sub(1)] == b'.'
133 && all_num_or_dot
134 {
135 return true;
136 }
137
138 if current_text_no_des_len == 2 {
140 if s.starts_with('.') && all_num_or_dot {
141 return true;
142 } else if s == "0" {
143 return current_text.starts_with("10") && !current_text.contains('.');
144 }
145 return false;
146 }
147
148 (if i == 0 {
149 s.starts_with('+')
150 } else { false })
151 || all_num_or_dot
152 }),
153 }
154 }
155}
156
157impl ValText<f32, PercentageParseError> {
158 #[must_use]
161 pub fn percentage() -> Self {
162 Self {
163 text: String::new(),
164 parsed_val: None,
165 value_parser: Box::new(|str| {
166 let num = str.parse();
167 match num {
168 Ok(num) => {
169 if num > 100.0 {
170 Err(PercentageParseError::OutOfRangeHigh)
171 } else if num < 0.0 {
172 Err(PercentageParseError::Neg)
173 } else {
174 Ok(num)
175 }
176 },
177 Err(e) => Err(e.into()),
178 }
179 }),
180 input_validator: Box::new(|current_text, s, i| {
181 let current_text_no_des_len = current_text.split_once('.')
182 .map(|(pre_dot, _)| pre_dot.len())
183 .unwrap_or(current_text.len());
184 if current_text_no_des_len + s.len() > 3 && !current_text.contains('.') {
185 return false;
186 }
187
188 let current_has_no_dot = !current_text.contains('.');
189 let all_num_or_dot = s.chars().all(|c| {
190 (if current_has_no_dot { c == '.' } else { false })
191 || c.is_ascii_digit()
192 });
193
194 if !current_text.is_empty()
195 && current_text.as_bytes()[i.saturating_sub(1)] == b'.'
196 && all_num_or_dot
197 {
198 return true;
199 }
200
201 if current_text_no_des_len == 2 {
203 if s.starts_with('.') && all_num_or_dot {
204 return true;
205 } else if s == "0" {
206 return current_text.starts_with("10") && !current_text.contains('.');
207 }
208 return false;
209 }
210
211 (if i == 0 {
212 s.starts_with('+')
213 } else { false })
214 || all_num_or_dot
215 }),
216 }
217 }
218}
219
220impl ValText<u32, PercentageParseError> {
221 #[must_use]
224 pub fn percentage_uint() -> Self {
225 Self {
226 text: String::new(),
227 parsed_val: None,
228 value_parser: Box::new(|str| {
229 let num = str.parse();
230 match num {
231 Ok(num) => {
232 if num > 100 {
233 Err(PercentageParseError::OutOfRangeHigh)
234 } else {
235 Ok(num)
236 }
237 },
238 Err(e) => Err(e.into()),
239 }
240 }),
241 input_validator: Box::new(|current_text, s, i| {
242 if current_text.len() + s.len() > 3 {
243 return false;
244 }
245
246 if current_text.len() == 2 {
248 if s == "0" {
249 return current_text.starts_with("10");
250 }
251 return false;
252 }
253
254 (if i == 0 {
255 s.starts_with('+')
256 } else { false })
257 || s.chars().all(|c| c.is_ascii_digit())
258 }),
259 }
260 }
261}