1#![warn(clippy::all)]
2#![allow(clippy::type_complexity)]
3
4use core::{fmt::Display, str::FromStr};
5use egui::TextBuffer;
6
7mod impls;
8
9#[must_use = "The input parsing buffer must be used in a ui input"]
47pub struct ValText<T, E> {
48 text: String,
50 parsed_val: Option<Result<T, E>>,
52 value_parser: Box<dyn Fn(&str) -> Result<T, E>>,
54 input_validator: Box<dyn Fn(&str, &str, usize) -> bool>,
60}
61
62impl<T, E> ValText<T, E> {
63 pub fn new(
64 value_parser: impl Fn(&str) -> Result<T, E> + 'static,
65 input_validator: impl Fn(&str, &str, usize) -> bool + 'static,
66 ) -> Self {
67 ValText {
68 text: String::new(),
69 parsed_val: None,
70 value_parser: Box::new(value_parser),
71 input_validator: Box::new(input_validator),
72 }
73 }
74
75 pub fn new_box(
76 value_parser: Box<dyn Fn(&str) -> Result<T, E>>,
77 input_validator: Box<dyn Fn(&str, &str, usize) -> bool>,
78 ) -> Self {
79 ValText {
80 text: String::new(),
81 parsed_val: None,
82 value_parser,
83 input_validator,
84 }
85 }
86
87 pub fn with_parser(validator: impl Fn(&str) -> Result<T, E> + 'static) -> Self {
88 Self {
89 text: String::new(),
90 parsed_val: None,
91 value_parser: Box::new(validator),
92 input_validator: Box::new(|_, _, _| true),
93 }
94 }
95
96 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().is_some_and(Result::is_ok)
126 }
127}
128
129impl<T: FromStr> ValText<Option<T>, T::Err> {
130 pub fn option_parse() -> Self {
131 Self {
132 text: String::new(),
133 parsed_val: None,
134 value_parser: Box::new(|str| {
135 if str.is_empty() {
136 Ok(None)
137 } else {
138 str.parse::<T>().map(|t| Some(t))
139 }
140 }),
141 input_validator: Box::new(|_, _, _| true),
142 }
143 }
144}
145
146impl<T: Display, E> ValText<T, E> {
147 pub fn set_val(&mut self, val: T) {
148 self.text = val.to_string();
149 self.parsed_val = Some(Ok(val));
150 }
151}
152
153impl<T: FromStr> Default for ValText<T, T::Err> {
154 fn default() -> Self {
156 Self {
157 text: String::new(),
158 parsed_val: None,
159 value_parser: Box::new(|text| text.parse()),
160 input_validator: Box::new(|_, _, _| true),
161 }
162 }
163}
164
165impl<T: 'static, E> TextBuffer for ValText<T, E> {
166 fn is_mutable(&self) -> bool {
167 true
168 }
169
170 fn as_str(&self) -> &str {
171 self.text.as_str()
172 }
173
174 fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
175 if (self.input_validator)(&self.text, text, char_index) {
176 let n = self.text.insert_text(text, char_index);
177 self.parsed_val = Some((self.value_parser)(&self.text));
178 n
179 } else {
180 0
181 }
182 }
183
184 fn delete_char_range(&mut self, char_range: std::ops::Range<usize>) {
185 self.text.delete_char_range(char_range);
186 self.parsed_val = Some((self.value_parser)(&self.text));
187 }
188
189 fn clear(&mut self) {
190 self.parsed_val = None;
191 self.text.clear();
192 }
193
194 fn take(&mut self) -> String {
195 self.parsed_val = None;
196 self.text.take()
197 }
198
199 fn type_id(&self) -> std::any::TypeId {
200 std::any::TypeId::of::<T>()
201 }
202}