1#![warn(clippy::all)]
2#![allow(clippy::type_complexity)]
3
4use core::{fmt::Display, str::FromStr};
5use egui::TextBuffer;
6
7mod impls;
8
9pub struct ValText<T, E> {
47 text: String,
48 parsed_val: Option<Result<T, E>>,
49 value_parser: Box<dyn Fn(&str) -> Result<T, E>>,
50 input_validator: Box<dyn Fn(&str, &str, usize) -> bool>,
56}
57
58impl<T, E> ValText<T, E> {
59 #[must_use]
60 pub fn new(
61 value_parser: impl Fn(&str) -> Result<T, E> + 'static,
62 input_validator: impl Fn(&str, &str, usize) -> bool + 'static,
63 ) -> Self {
64 ValText {
65 text: String::new(),
66 parsed_val: None,
67 value_parser: Box::new(value_parser),
68 input_validator: Box::new(input_validator),
69 }
70 }
71
72 #[must_use]
73 pub fn new_box(
74 value_parser: Box<dyn Fn(&str) -> Result<T, E>>,
75 input_validator: Box<dyn Fn(&str, &str, usize) -> bool>,
76 ) -> Self {
77 ValText {
78 text: String::new(),
79 parsed_val: None,
80 value_parser,
81 input_validator,
82 }
83 }
84
85 #[must_use]
86 pub fn with_parser(validator: impl Fn(&str) -> Result<T, E> + 'static) -> Self {
87 Self {
88 text: String::new(),
89 parsed_val: None,
90 value_parser: Box::new(validator),
91 input_validator: Box::new(|_, _, _| true),
92 }
93 }
94
95 #[must_use]
104 pub fn with_parser_fixed_charset(
105 parser: impl Fn(&str) -> Result<T, E> + 'static,
106 charset: &'static [char],
107 ) -> Self {
108 Self {
109 text: String::new(),
110 parsed_val: None,
111 value_parser: Box::new(parser),
112 input_validator: Box::new(|_, s, _| s.chars().all(|c| charset.contains(&c))),
113 }
114 }
115
116 pub const fn get_val(&self) -> Option<Result<&T, &E>> {
118 match self.parsed_val.as_ref() {
119 Some(res) => Some(res.as_ref()),
120 None => None,
121 }
122 }
123
124 pub fn is_valid(&self) -> bool {
125 self.parsed_val.as_ref().map(|res| res.is_ok())
126 .unwrap_or(false)
127 }
128}
129
130impl<T: FromStr> ValText<Option<T>, T::Err> {
131 pub fn option_parse() -> Self {
132 Self {
133 text: String::new(),
134 parsed_val: None,
135 value_parser: Box::new(|str| {
136 if str.is_empty() {
137 Ok(None)
138 } else {
139 str.parse::<T>().map(|t| Some(t))
140 }
141 }),
142 input_validator: Box::new(|_, _, _| true),
143 }
144 }
145}
146
147impl<T: Display, E> ValText<T, E> {
148 pub fn set_val(&mut self, val: T) {
149 self.text = val.to_string();
150 self.parsed_val = Some(Ok(val));
151 }
152}
153
154impl<T: FromStr> Default for ValText<T, T::Err> {
155 fn default() -> Self {
157 Self {
158 text: String::new(),
159 parsed_val: None,
160 value_parser: Box::new(|text| text.parse()),
161 input_validator: Box::new(|_, _, _| true),
162 }
163 }
164}
165
166impl<T: 'static, E> TextBuffer for ValText<T, E> {
167 fn is_mutable(&self) -> bool {
168 true
169 }
170
171 fn as_str(&self) -> &str {
172 self.text.as_str()
173 }
174
175 fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
176 if (self.input_validator)(&self.text, text, char_index) {
177 let n = self.text.insert_text(text, char_index);
178 self.parsed_val = Some((self.value_parser)(&self.text));
179 n
180 } else {
181 0
182 }
183 }
184
185 fn delete_char_range(&mut self, char_range: std::ops::Range<usize>) {
186 self.text.delete_char_range(char_range);
187 self.parsed_val = Some((self.value_parser)(&self.text));
188 }
189
190 fn clear(&mut self) {
191 self.parsed_val = None;
192 self.text.clear();
193 }
194
195 fn take(&mut self) -> String {
196 self.parsed_val = None;
197 self.text.take()
198 }
199
200 fn type_id(&self) -> std::any::TypeId {
201 std::any::TypeId::of::<T>()
202 }
203}