egui_typed_input/
lib.rs

1#![warn(clippy::all)]
2#![allow(clippy::type_complexity)]
3
4use core::{fmt::Display, str::FromStr};
5use egui::TextBuffer;
6
7mod impls;
8
9/// A mutable `TextBuffer` that will validate it's contents when changed.\
10/// And check an input before adding it to the text.
11///
12/// The default validator will simply attempt to parse the text as `T`,
13/// but a custom validator function can be provided.
14///
15/// ## Usage
16/// ```
17/// use egui_typed_input::ValText;
18///
19/// # fn main() {
20/// let mut alphabetical_order: ValText<Vec<char>, ()> = ValText::new(
21///     // parser
22///     (|str| Ok(str.chars().collect::<Vec<_>>())),
23///     // input validator
24///     (|current_text, input, index| {
25///         if input.chars().all(|c| c.is_ascii_alphabetic()) {
26///             input.chars().all(|c| {
27///                 c.to_ascii_lowercase() >= current_text.chars().skip(index.saturating_sub(1)).take(1).last().unwrap_or('a')
28///             })
29///         } else { false }
30///     }),
31/// );
32///
33/// # eframe::run_simple_native(
34/// #    "alphabetical order input",
35/// #    eframe::NativeOptions::default(),
36/// #    move |ctx, _frame| {
37/// #        egui::CentralPanel::default().show(ctx, |ui| {
38/// ui.text_edit_singleline(&mut alphabetical_order);
39/// println!("alphabetical_order: {:?}", alphabetical_order.get_val());
40/// #        });
41/// #    }
42/// # ).unwrap();
43/// # }
44/// ```
45/// See hex color example (color_hex.rs) and number examples (number.rs) for more
46pub 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    /// Whether a user input should be added to the string at index
51    ///
52    /// The signature is `(current_text, input, insertion_index) -> should_add_to_text`
53    ///
54    /// Note: insertion_index is a character index, not a byte index.
55    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    /// Only chars in `charset` can be input
96    /// ## Usage
97    /// ```
98    /// # use egui_typed_input::ValText;
99    /// # let _: ValText<_, ()> =
100    /// ValText::with_parser_fixed_charset(|str| Ok(str.to_owned()), &['a', 'c']);
101    /// ```
102    /// Would allow 'a' and 'c' but no others.
103    #[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    /// `ValText` must be used before getting value
117    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    /// Parse the text using `FromStr`
156    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}