Skip to main content

kas_widgets/edit/
guard.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! The [`EditGuard`] trait and some implementations
7
8use super::Editor;
9use kas::prelude::*;
10use std::fmt::{Debug, Display};
11use std::marker::PhantomData;
12use std::str::FromStr;
13
14/// Event-handling *guard* for an [`Editor`]
15///
16/// This is the most generic interface; see also constructors of [`EditField`],
17/// [`EditBox`] for common use-cases.
18///
19/// All methods have a default implementation which does nothing.
20///
21/// [`EditBox`]: super::EditBox
22/// [`EditField`]: super::EditField
23pub trait EditGuard: Sized {
24    /// Data type
25    type Data;
26
27    /// Configure guard
28    ///
29    /// This function is called when the attached widget is configured.
30    fn configure(&mut self, edit: &mut Editor, cx: &mut ConfigCx) {
31        let _ = (edit, cx);
32    }
33
34    /// Update guard
35    ///
36    /// This function is called when input data is updated **and** the editor
37    /// does not have [input focus](Editor::has_input_focus).
38    ///
39    /// This method may also be called on loss of input focus (see
40    /// [`Self::focus_lost`]).
41    fn update(&mut self, edit: &mut Editor, cx: &mut ConfigCx, data: &Self::Data) {
42        let _ = (edit, cx, data);
43    }
44
45    /// Activation guard
46    ///
47    /// This function is called when the widget is "activated", for example by
48    /// the Enter/Return key for single-line edit boxes. Its result is returned
49    /// from `handle_event`.
50    ///
51    /// The default implementation:
52    ///
53    /// -   If the field is editable, calls [`Self::focus_lost`] and returns
54    ///     returns [`Used`].
55    /// -   If the field is not editable, returns [`Unused`].
56    fn activate(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &Self::Data) -> IsUsed {
57        if edit.is_editable() {
58            self.focus_lost(edit, cx, data);
59            Used
60        } else {
61            Unused
62        }
63    }
64
65    /// Focus-gained guard
66    ///
67    /// This function is called when the widget gains keyboard or IME focus.
68    fn focus_gained(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &Self::Data) {
69        let _ = (edit, cx, data);
70    }
71
72    /// Focus-lost guard
73    ///
74    /// This function is called after the widget has lost both keyboard and IME
75    /// focus.
76    ///
77    /// The default implementation calls [`Self::update`] since updates are
78    /// inhibited while the editor has input focus.
79    fn focus_lost(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &Self::Data) {
80        self.update(edit, cx, data);
81    }
82
83    /// Edit guard
84    ///
85    /// This function is called after the text is updated (including by keyboard
86    /// input, an undo action or by a message like
87    /// [`kas::messages::SetValueText`]). The exceptions are setter methods like
88    /// [`clear`](Editor::clear) and [`set_string`](Editor::set_string).
89    ///
90    /// The guard may call [`Editor::set_error`] here.
91    /// The error state is cleared immediately before calling this method.
92    fn edit(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &Self::Data) {
93        let _ = (edit, cx, data);
94    }
95}
96
97/// Ignore all events and data updates
98///
99/// This guard should probably not be used for a functional user-interface but
100/// may be useful in mock UIs.
101#[autoimpl(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
102pub struct DefaultGuard<A>(PhantomData<A>);
103impl<A: 'static> EditGuard for DefaultGuard<A> {
104    type Data = A;
105}
106
107#[impl_self]
108mod StringGuard {
109    /// An [`EditGuard`] for read-only strings
110    ///
111    /// This may be used with read-only edit fields, essentially resulting in a
112    /// fancier version of [`Text`](crate::Text) or
113    /// [`ScrollText`](crate::ScrollText).
114    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
115    pub struct StringGuard<A> {
116        value_fn: Box<dyn Fn(&A) -> String>,
117        on_afl: Option<Box<dyn Fn(&mut EventCx, &A, &str)>>,
118        edited: bool,
119    }
120
121    impl Self {
122        /// Construct with a value function
123        ///
124        /// On update, `value_fn` is used to extract a value from input data.
125        /// If, however, the input field has focus, the update is ignored.
126        ///
127        /// No other action happens unless [`Self::with_msg`] is used.
128        pub fn new(value_fn: impl Fn(&A) -> String + 'static) -> Self {
129            StringGuard {
130                value_fn: Box::new(value_fn),
131                on_afl: None,
132                edited: false,
133            }
134        }
135
136        /// Call the handler `f` on activation / focus loss
137        ///
138        /// On field **a**ctivation and **f**ocus **l**oss (AFL) after an edit,
139        /// `f` is called.
140        pub fn with(mut self, f: impl Fn(&mut EventCx, &A, &str) + 'static) -> Self {
141            debug_assert!(self.on_afl.is_none());
142            self.on_afl = Some(Box::new(f));
143            self
144        }
145
146        /// Send the message generated by `f` on activation / focus loss
147        ///
148        /// On field **a**ctivation and **f**ocus **l**oss (AFL) after an edit,
149        /// `f` is used to construct a message to be emitted via [`EventCx::push`].
150        pub fn with_msg<M: Debug + 'static>(self, f: impl Fn(&str) -> M + 'static) -> Self {
151            self.with(move |cx, _, value| cx.push(f(value)))
152        }
153    }
154
155    impl EditGuard for Self {
156        type Data = A;
157
158        fn update(&mut self, edit: &mut Editor, cx: &mut ConfigCx, data: &A) {
159            let string = (self.value_fn)(data);
160            edit.set_string(cx, string);
161        }
162
163        fn focus_lost(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &A) {
164            if self.edited {
165                self.edited = false;
166                if let Some(ref on_afl) = self.on_afl {
167                    on_afl(cx, data, edit.as_str());
168                }
169            } else {
170                // Reset data on focus loss (update is inhibited with focus).
171                self.update(edit, cx, data);
172            }
173        }
174
175        fn edit(&mut self, _: &mut Editor, _: &mut EventCx, _: &Self::Data) {
176            self.edited = true;
177        }
178    }
179}
180
181#[impl_self]
182mod ParseGuard {
183    /// An [`EditGuard`] for parsable types
184    ///
185    /// This guard displays a value formatted from input data, updates the error
186    /// state according to parse success on each keystroke, and sends a message
187    /// on focus loss (where successful parsing occurred).
188    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
189    pub struct ParseGuard<A, T: Debug + Display + FromStr> {
190        parsed: Option<T>,
191        value_fn: Box<dyn Fn(&A) -> T>,
192        on_afl: Box<dyn Fn(&mut EventCx, T)>,
193    }
194
195    impl Self {
196        /// Construct
197        ///
198        /// On update, `value_fn` is used to extract a value from input data
199        /// which is then formatted as a string via [`Display`].
200        /// If, however, the input field has focus, the update is ignored.
201        ///
202        /// On every edit, the guard attempts to parse the field's input as type
203        /// `T` via [`FromStr`], caching the result and setting the error state.
204        ///
205        /// On field activation and focus loss when a `T` value is cached (see
206        /// previous paragraph), `on_afl` is used to construct a message to be
207        /// emitted via [`EventCx::push`]. The cached value is then cleared to
208        /// avoid sending duplicate messages.
209        pub fn new<M: Debug + 'static>(
210            value_fn: impl Fn(&A) -> T + 'static,
211            on_afl: impl Fn(T) -> M + 'static,
212        ) -> Self {
213            ParseGuard {
214                parsed: None,
215                value_fn: Box::new(value_fn),
216                on_afl: Box::new(move |cx, value| cx.push(on_afl(value))),
217            }
218        }
219    }
220
221    impl EditGuard for Self {
222        type Data = A;
223
224        fn update(&mut self, edit: &mut Editor, cx: &mut ConfigCx, data: &A) {
225            let value = (self.value_fn)(data);
226            edit.set_string(cx, format!("{value}"));
227            self.parsed = None;
228        }
229
230        fn focus_lost(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &A) {
231            if let Some(value) = self.parsed.take() {
232                (self.on_afl)(cx, value);
233            } else {
234                // Reset data on focus loss (update is inhibited with focus).
235                self.update(edit, cx, data);
236            }
237        }
238
239        fn edit(&mut self, edit: &mut Editor, cx: &mut EventCx, _: &A) {
240            self.parsed = edit.as_str().parse().ok();
241            if self.parsed.is_none() {
242                edit.set_error(cx, Some("parse failure".into()));
243            }
244        }
245    }
246}
247
248#[impl_self]
249mod InstantParseGuard {
250    /// An as-you-type [`EditGuard`] for parsable types
251    ///
252    /// This guard displays a value formatted from input data, updates the error
253    /// state according to parse success on each keystroke, and sends a message
254    /// immediately (where successful parsing occurred).
255    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
256    pub struct InstantParseGuard<A, T: Debug + Display + FromStr> {
257        value_fn: Box<dyn Fn(&A) -> T>,
258        on_afl: Box<dyn Fn(&mut EventCx, T)>,
259    }
260
261    impl Self {
262        /// Construct
263        ///
264        /// On update, `value_fn` is used to extract a value from input data
265        /// which is then formatted as a string via [`Display`].
266        /// If, however, the input field has focus, the update is ignored.
267        ///
268        /// On every edit, the guard attempts to parse the field's input as type
269        /// `T` via [`FromStr`]. On success, the result is converted to a
270        /// message via `on_afl` then emitted via [`EventCx::push`].
271        pub fn new<M: Debug + 'static>(
272            value_fn: impl Fn(&A) -> T + 'static,
273            on_afl: impl Fn(T) -> M + 'static,
274        ) -> Self {
275            InstantParseGuard {
276                value_fn: Box::new(value_fn),
277                on_afl: Box::new(move |cx, value| cx.push(on_afl(value))),
278            }
279        }
280    }
281
282    impl EditGuard for Self {
283        type Data = A;
284
285        fn update(&mut self, edit: &mut Editor, cx: &mut ConfigCx, data: &A) {
286            let value = (self.value_fn)(data);
287            edit.set_string(cx, format!("{value}"));
288        }
289
290        fn focus_lost(&mut self, edit: &mut Editor, cx: &mut EventCx, data: &A) {
291            // Always reset data on focus loss
292            self.update(edit, cx, data);
293        }
294
295        fn edit(&mut self, edit: &mut Editor, cx: &mut EventCx, _: &A) {
296            let result = edit.as_str().parse();
297            if result.is_err() {
298                edit.set_error(cx, Some("parse failure".into()));
299            }
300            if let Ok(value) = result {
301                (self.on_afl)(cx, value);
302            }
303        }
304    }
305}