egui_typed_input/
impls.rs1use core::str::FromStr;
2use egui::Color32;
3
4use crate::ValText;
5
6impl ValText<Color32, egui::ecolor::ParseHexColorError> {
7 pub fn color_hex() -> Self {
10 Self {
11 text: String::new(),
12 parsed_val: Some(Err(egui::ecolor::ParseHexColorError::MissingHash)),
13 value_parser: Box::new(Color32::from_hex),
14 input_validator: Box::new(|_, s, i| {
15 if i == 0 {
16 return s.starts_with('#');
17 }
18 s.chars().all(|c| c.is_ascii_hexdigit())
19 }),
20 }
21 }
22}
23
24impl<T: FromStr> ValText<T, T::Err> {
25 pub fn number() -> Self {
27 Self {
28 text: String::new(),
29 parsed_val: None,
30 value_parser: Box::new(|str| str.parse()),
31 input_validator: Box::new(|current_text, s, i| {
32 let current_has_no_dot = !current_text.contains('.');
33 (if i == 0 {
34 s.starts_with('+') || s.starts_with('-')
35 } else { false })
36 || s.chars().all(|c| {
37 (if current_has_no_dot { c == '.' } else { false })
38 || c.is_ascii_digit()
39 })
40 }),
41 }
42 }
43
44 pub fn number_int() -> Self {
46 Self {
47 text: String::new(),
48 parsed_val: None,
49 value_parser: Box::new(|str| str.parse()),
50 input_validator: Box::new(|_, s, i| {
51 (if i == 0 {
52 s.starts_with('+') || s.starts_with('-')
53 } else { false })
54 || s.chars().all(|c| c.is_ascii_digit())
55 })
56 }
57 }
58
59 pub fn number_uint() -> Self {
61 Self {
62 text: String::new(),
63 parsed_val: None,
64 value_parser: Box::new(|str| str.parse()),
65 input_validator: Box::new(|_, s, i| {
66 (if i == 0 {
67 s.starts_with('+')
68 } else { false })
69 || s.chars().all(|c| c.is_ascii_digit())
70 })
71 }
72 }
73}
74
75#[derive(Debug, thiserror::Error)]
76pub enum PercentageParseError {
77 #[error("number is more then 100")]
79 OutOfRangeHigh,
80 #[error("number is less then 0")]
82 Neg,
83 #[error(transparent)]
84 ParseFloat(#[from] core::num::ParseFloatError),
85 #[error(transparent)]
86 ParseInt(#[from] core::num::ParseIntError),
87}
88
89impl ValText<f64, PercentageParseError> {
92 pub fn percentage() -> Self {
96 Self {
97 text: String::new(),
98 parsed_val: None,
99 value_parser: Box::new(|str| {
100 let num = str.parse();
101 match num {
102 Ok(num) => {
103 if num > 100.0 {
104 Err(PercentageParseError::OutOfRangeHigh)
105 } else if num < 0.0 {
106 Err(PercentageParseError::Neg)
107 } else {
108 Ok(num)
109 }
110 },
111 Err(e) => Err(e.into()),
112 }
113 }),
114 input_validator: Box::new(|current_text, s, i| {
115 let current_text_no_des_len = current_text.split_once('.')
116 .map_or(
117 current_text.len(),
118 |(pre_dot, _)| pre_dot.len()
119 );
120 if current_text_no_des_len + s.len() > 3 && !current_text.contains('.') {
121 return false;
122 }
123
124 let current_has_no_dot = !current_text.contains('.');
125 let all_num_or_dot = s.chars().all(|c| {
126 (if current_has_no_dot { c == '.' } else { false })
127 || c.is_ascii_digit()
128 });
129
130 if !current_text.is_empty()
131 && current_text.as_bytes()[i.saturating_sub(1)] == b'.'
132 && all_num_or_dot
133 {
134 return true;
135 }
136
137 if current_text_no_des_len == 2 {
139 if s.starts_with('.') && all_num_or_dot {
140 return true;
141 } else if s == "0" {
142 return current_text.starts_with("10") && !current_text.contains('.');
143 }
144 return false;
145 }
146
147 (if i == 0 {
148 s.starts_with('+')
149 } else { false })
150 || all_num_or_dot
151 }),
152 }
153 }
154}
155
156impl ValText<f32, PercentageParseError> {
157 pub fn percentage() -> Self {
160 Self {
161 text: String::new(),
162 parsed_val: None,
163 value_parser: Box::new(|str| {
164 let num = str.parse();
165 match num {
166 Ok(num) => {
167 if num > 100.0 {
168 Err(PercentageParseError::OutOfRangeHigh)
169 } else if num < 0.0 {
170 Err(PercentageParseError::Neg)
171 } else {
172 Ok(num)
173 }
174 },
175 Err(e) => Err(e.into()),
176 }
177 }),
178 input_validator: Box::new(|current_text, s, i| {
179 let current_text_no_des_len = current_text.split_once('.')
180 .map_or(
181 current_text.len(),
182 |(pre_dot, _)| pre_dot.len()
183 );
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> {
222 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}