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}