kas_widgets/
edit.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 [`EditField`] and [`EditBox`] widgets, plus supporting items
7
8use crate::{ScrollBar, ScrollMsg};
9use kas::event::components::{ScrollComponent, TextInput, TextInputAction};
10use kas::event::{CursorIcon, ElementState, FocusSource, ImePurpose, PhysicalKey, Scroll};
11use kas::geom::Vec2;
12use kas::messages::{ReplaceSelectedText, SetValueText};
13use kas::prelude::*;
14use kas::text::{NotReady, SelectionHelper};
15use kas::theme::{Background, FrameStyle, Text, TextClass};
16use std::fmt::{Debug, Display};
17use std::marker::PhantomData;
18use std::ops::Range;
19use std::str::FromStr;
20use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
21
22#[derive(Clone, Debug, Default, PartialEq)]
23enum LastEdit {
24    #[default]
25    None,
26    Insert,
27    Delete,
28    Paste,
29}
30
31enum EditAction {
32    None,
33    Activate,
34    Edit,
35}
36
37/// Event-handling *guard* for [`EditField`], [`EditBox`]
38///
39/// This is the most generic interface; see also constructors of [`EditField`],
40/// [`EditBox`] for common use-cases.
41///
42/// All methods on this trait are passed a reference to the [`EditField`] as
43/// parameter. The guard itself is a public field: `edit.guard`.
44///
45/// All methods have a default implementation which does nothing.
46pub trait EditGuard: Sized {
47    /// Data type
48    type Data;
49
50    /// Configure guard
51    ///
52    /// This function is called when the attached widget is configured.
53    fn configure(edit: &mut EditField<Self>, cx: &mut ConfigCx) {
54        let _ = (edit, cx);
55    }
56
57    /// Update guard
58    ///
59    /// This function is called when input data is updated.
60    ///
61    /// Note that this method may be called during editing as a result of a
62    /// message sent by [`Self::edit`] or another cause. It is recommended to
63    /// ignore updates for editable widgets with key focus
64    /// ([`EditField::has_edit_focus`]) to avoid overwriting user input;
65    /// [`Self::focus_lost`] may update the content instead.
66    /// For read-only fields this is not recommended (but `has_edit_focus` will
67    /// not be true anyway).
68    fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &Self::Data) {
69        let _ = (edit, cx, data);
70    }
71
72    /// Activation guard
73    ///
74    /// This function is called when the widget is "activated", for example by
75    /// the Enter/Return key for single-line edit boxes. Its result is returned
76    /// from `handle_event`.
77    ///
78    /// The default implementation:
79    ///
80    /// -   If the field is editable, calls [`Self::focus_lost`] and returns
81    ///     returns [`Used`].
82    /// -   If the field is not editable, returns [`Unused`].
83    fn activate(edit: &mut EditField<Self>, cx: &mut EventCx, data: &Self::Data) -> IsUsed {
84        if edit.editable {
85            Self::focus_lost(edit, cx, data);
86            Used
87        } else {
88            Unused
89        }
90    }
91
92    /// Focus-gained guard
93    ///
94    /// This function is called when the widget gains keyboard input focus.
95    fn focus_gained(edit: &mut EditField<Self>, cx: &mut EventCx, data: &Self::Data) {
96        let _ = (edit, cx, data);
97    }
98
99    /// Focus-lost guard
100    ///
101    /// This function is called when the widget loses keyboard input focus.
102    fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, data: &Self::Data) {
103        let _ = (edit, cx, data);
104    }
105
106    /// Edit guard
107    ///
108    /// This function is called when contents are updated by the user.
109    fn edit(edit: &mut EditField<Self>, cx: &mut EventCx, data: &Self::Data) {
110        let _ = (edit, cx, data);
111    }
112}
113
114/// Ignore all events and data updates
115///
116/// This guard should probably not be used for a functional user-interface but
117/// may be useful in mock UIs.
118#[autoimpl(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
119pub struct DefaultGuard<A>(PhantomData<A>);
120impl<A: 'static> EditGuard for DefaultGuard<A> {
121    type Data = A;
122}
123
124#[impl_self]
125mod StringGuard {
126    /// An [`EditGuard`] for read-only strings
127    ///
128    /// This may be used with read-only edit fields, essentially resulting in a
129    /// fancier version of [`Text`](crate::Text) or
130    /// [`ScrollText`](crate::ScrollText).
131    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
132    pub struct StringGuard<A> {
133        value_fn: Box<dyn Fn(&A) -> String>,
134        on_afl: Option<Box<dyn Fn(&mut EventCx, &A, &str)>>,
135        edited: bool,
136    }
137
138    impl Self {
139        /// Construct with a value function
140        ///
141        /// On update, `value_fn` is used to extract a value from input data.
142        /// If, however, the input field has focus, the update is ignored.
143        ///
144        /// No other action happens unless [`Self::with_msg`] is used.
145        pub fn new(value_fn: impl Fn(&A) -> String + 'static) -> Self {
146            StringGuard {
147                value_fn: Box::new(value_fn),
148                on_afl: None,
149                edited: false,
150            }
151        }
152
153        /// Call the handler `f` on activation / focus loss
154        ///
155        /// On field **a**ctivation and **f**ocus **l**oss (AFL) after an edit,
156        /// `f` is called.
157        pub fn with(mut self, f: impl Fn(&mut EventCx, &A, &str) + 'static) -> Self {
158            debug_assert!(self.on_afl.is_none());
159            self.on_afl = Some(Box::new(f));
160            self
161        }
162
163        /// Send the message generated by `f` on activation / focus loss
164        ///
165        /// On field **a**ctivation and **f**ocus **l**oss (AFL) after an edit,
166        /// `f` is used to construct a message to be emitted via [`EventCx::push`].
167        pub fn with_msg<M: Debug + 'static>(self, f: impl Fn(&str) -> M + 'static) -> Self {
168            self.with(move |cx, _, value| cx.push(f(value)))
169        }
170    }
171
172    impl EditGuard for Self {
173        type Data = A;
174
175        fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, data: &A) {
176            if edit.guard.edited {
177                edit.guard.edited = false;
178                if let Some(ref on_afl) = edit.guard.on_afl {
179                    return on_afl(cx, data, edit.as_str());
180                }
181            }
182
183            // Reset data on focus loss (update is inhibited with focus).
184            // No need if we just sent a message (should cause an update).
185            let string = (edit.guard.value_fn)(data);
186            edit.set_string(cx, string);
187        }
188
189        fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &A) {
190            if !edit.has_edit_focus() {
191                let string = (edit.guard.value_fn)(data);
192                edit.set_string(cx, string);
193            }
194        }
195
196        fn edit(edit: &mut EditField<Self>, _: &mut EventCx, _: &Self::Data) {
197            edit.guard.edited = true;
198        }
199    }
200}
201
202#[impl_self]
203mod ParseGuard {
204    /// An [`EditGuard`] for parsable types
205    ///
206    /// This guard displays a value formatted from input data, updates the error
207    /// state according to parse success on each keystroke, and sends a message
208    /// on focus loss (where successful parsing occurred).
209    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
210    pub struct ParseGuard<A, T: Debug + Display + FromStr> {
211        parsed: Option<T>,
212        value_fn: Box<dyn Fn(&A) -> T>,
213        on_afl: Box<dyn Fn(&mut EventCx, T)>,
214    }
215
216    impl Self {
217        /// Construct
218        ///
219        /// On update, `value_fn` is used to extract a value from input data
220        /// which is then formatted as a string via [`Display`].
221        /// If, however, the input field has focus, the update is ignored.
222        ///
223        /// On every edit, the guard attempts to parse the field's input as type
224        /// `T` via [`FromStr`], caching the result and setting the error state.
225        ///
226        /// On field activation and focus loss when a `T` value is cached (see
227        /// previous paragraph), `on_afl` is used to construct a message to be
228        /// emitted via [`EventCx::push`]. The cached value is then cleared to
229        /// avoid sending duplicate messages.
230        pub fn new<M: Debug + 'static>(
231            value_fn: impl Fn(&A) -> T + 'static,
232            on_afl: impl Fn(T) -> M + 'static,
233        ) -> Self {
234            ParseGuard {
235                parsed: None,
236                value_fn: Box::new(value_fn),
237                on_afl: Box::new(move |cx, value| cx.push(on_afl(value))),
238            }
239        }
240    }
241
242    impl EditGuard for Self {
243        type Data = A;
244
245        fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, data: &A) {
246            if let Some(value) = edit.guard.parsed.take() {
247                (edit.guard.on_afl)(cx, value);
248            } else {
249                // Reset data on focus loss (update is inhibited with focus).
250                // No need if we just sent a message (should cause an update).
251                let value = (edit.guard.value_fn)(data);
252                edit.set_string(cx, format!("{value}"));
253            }
254        }
255
256        fn edit(edit: &mut EditField<Self>, cx: &mut EventCx, _: &A) {
257            edit.guard.parsed = edit.as_str().parse().ok();
258            edit.set_error_state(cx, edit.guard.parsed.is_none());
259        }
260
261        fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &A) {
262            if !edit.has_edit_focus() {
263                let value = (edit.guard.value_fn)(data);
264                edit.set_string(cx, format!("{value}"));
265                edit.guard.parsed = None;
266            }
267        }
268    }
269}
270
271#[impl_self]
272mod InstantParseGuard {
273    /// An as-you-type [`EditGuard`] for parsable types
274    ///
275    /// This guard displays a value formatted from input data, updates the error
276    /// state according to parse success on each keystroke, and sends a message
277    /// immediately (where successful parsing occurred).
278    #[autoimpl(Debug ignore self.value_fn, self.on_afl)]
279    pub struct InstantParseGuard<A, T: Debug + Display + FromStr> {
280        value_fn: Box<dyn Fn(&A) -> T>,
281        on_afl: Box<dyn Fn(&mut EventCx, T)>,
282    }
283
284    impl Self {
285        /// Construct
286        ///
287        /// On update, `value_fn` is used to extract a value from input data
288        /// which is then formatted as a string via [`Display`].
289        /// If, however, the input field has focus, the update is ignored.
290        ///
291        /// On every edit, the guard attempts to parse the field's input as type
292        /// `T` via [`FromStr`]. On success, the result is converted to a
293        /// message via `on_afl` then emitted via [`EventCx::push`].
294        pub fn new<M: Debug + 'static>(
295            value_fn: impl Fn(&A) -> T + 'static,
296            on_afl: impl Fn(T) -> M + 'static,
297        ) -> Self {
298            InstantParseGuard {
299                value_fn: Box::new(value_fn),
300                on_afl: Box::new(move |cx, value| cx.push(on_afl(value))),
301            }
302        }
303    }
304
305    impl EditGuard for Self {
306        type Data = A;
307
308        fn focus_lost(edit: &mut EditField<Self>, cx: &mut EventCx, data: &A) {
309            // Always reset data on focus loss
310            let value = (edit.guard.value_fn)(data);
311            edit.set_string(cx, format!("{value}"));
312        }
313
314        fn edit(edit: &mut EditField<Self>, cx: &mut EventCx, _: &A) {
315            let result = edit.as_str().parse();
316            edit.set_error_state(cx, result.is_err());
317            if let Ok(value) = result {
318                (edit.guard.on_afl)(cx, value);
319            }
320        }
321
322        fn update(edit: &mut EditField<Self>, cx: &mut ConfigCx, data: &A) {
323            if !edit.has_edit_focus() {
324                let value = (edit.guard.value_fn)(data);
325                edit.set_string(cx, format!("{value}"));
326            }
327        }
328    }
329}
330
331#[impl_self]
332mod EditBox {
333    /// A text-edit box
334    ///
335    /// A single- or multi-line editor for unformatted text.
336    /// See also notes on [`EditField`].
337    ///
338    /// By default, the editor supports a single-line only;
339    /// [`Self::with_multi_line`] and [`Self::with_class`] can be used to change this.
340    ///
341    /// ### Messages
342    ///
343    /// [`SetValueText`] may be used to replace the entire text and
344    /// [`ReplaceSelectedText`] may be used to replace selected text, where
345    /// [`Self::is_editable`]. This triggers the action handlers
346    /// [`EditGuard::edit`] followed by [`EditGuard::activate`].
347    ///
348    /// [`kas::messages::SetScrollOffset`] may be used to set the scroll offset.
349    #[autoimpl(Clone, Default, Debug where G: trait)]
350    #[widget]
351    pub struct EditBox<G: EditGuard = DefaultGuard<()>> {
352        core: widget_core!(),
353        scroll: ScrollComponent,
354        #[widget]
355        inner: EditField<G>,
356        #[widget(&())]
357        vert_bar: ScrollBar<kas::dir::Down>,
358        frame_offset: Offset,
359        frame_size: Size,
360        frame_offset_ex_margin: Offset,
361        inner_margin: i32,
362        clip_rect: Rect,
363    }
364
365    impl Layout for Self {
366        fn size_rules(&mut self, sizer: SizeCx, mut axis: AxisInfo) -> SizeRules {
367            axis.sub_other(self.frame_size.extract(axis.flipped()));
368
369            let mut rules = self.inner.size_rules(sizer.re(), axis);
370            let bar_rules = self.vert_bar.size_rules(sizer.re(), axis);
371            if axis.is_horizontal() && self.multi_line() {
372                self.inner_margin = rules.margins_i32().1.max(bar_rules.margins_i32().0);
373                rules.append(bar_rules);
374            }
375
376            let frame_rules = sizer.frame(FrameStyle::EditBox, axis);
377            self.frame_offset_ex_margin
378                .set_component(axis, frame_rules.size());
379            let (rules, offset, size) = frame_rules.surround(rules);
380            self.frame_offset.set_component(axis, offset);
381            self.frame_size.set_component(axis, size);
382            rules
383        }
384
385        fn set_rect(&mut self, cx: &mut ConfigCx, outer_rect: Rect, hints: AlignHints) {
386            widget_set_rect!(outer_rect);
387            let mut rect = outer_rect;
388
389            self.clip_rect = Rect {
390                pos: rect.pos + self.frame_offset_ex_margin,
391                size: rect.size - (self.frame_offset_ex_margin * 2).cast(),
392            };
393
394            rect.pos += self.frame_offset;
395            rect.size -= self.frame_size;
396
397            let mut bar_rect = Rect::ZERO;
398            if self.multi_line() {
399                let bar_width = cx.size_cx().scroll_bar_width();
400                let x1 = rect.pos.0 + rect.size.0;
401                let x0 = x1 - bar_width;
402                bar_rect = Rect::new(Coord(x0, rect.pos.1), Size(bar_width, rect.size.1));
403                rect.size.0 = (rect.size.0 - bar_width - self.inner_margin).max(0);
404            }
405            self.vert_bar.set_rect(cx, bar_rect, AlignHints::NONE);
406
407            self.inner.set_rect(cx, rect, hints);
408            let _ = self.scroll.set_sizes(rect.size, self.inner.typeset_size());
409            self.update_scroll_bar(cx);
410        }
411
412        fn draw(&self, mut draw: DrawCx) {
413            let mut draw_inner = draw.re();
414            draw_inner.set_id(self.inner.id());
415            let bg = if self.inner.has_error() {
416                Background::Error
417            } else {
418                Background::Default
419            };
420            draw_inner.frame(self.rect(), FrameStyle::EditBox, bg);
421
422            self.inner
423                .draw_with_offset(draw.re(), self.clip_rect, self.scroll.offset());
424
425            if self.scroll.max_offset().1 > 0 {
426                self.vert_bar.draw(draw.re());
427            }
428        }
429    }
430
431    impl Tile for Self {
432        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
433            Role::ScrollRegion {
434                offset: self.scroll_offset(),
435                max_offset: self.max_scroll_offset(),
436            }
437        }
438
439        fn translation(&self, index: usize) -> Offset {
440            if index == widget_index!(self.inner) {
441                self.scroll.offset()
442            } else {
443                Offset::ZERO
444            }
445        }
446
447        fn probe(&self, coord: Coord) -> Id {
448            if self.scroll.max_offset().1 > 0 {
449                if let Some(id) = self.vert_bar.try_probe(coord) {
450                    return id;
451                }
452            }
453
454            // If coord is over self but not over self.vert_bar, we assign
455            // the event to self.inner without further question.
456            self.inner.id()
457        }
458    }
459
460    impl Events for Self {
461        type Data = G::Data;
462
463        fn handle_event(&mut self, cx: &mut EventCx, _: &Self::Data, event: Event) -> IsUsed {
464            let rect = Rect {
465                pos: self.rect().pos + self.frame_offset,
466                size: self.rect().size - self.frame_size,
467            };
468            let used = self.scroll.scroll_by_event(cx, event, self.id(), rect);
469            self.update_scroll_bar(cx);
470            used
471        }
472
473        fn handle_messages(&mut self, cx: &mut EventCx<'_>, data: &G::Data) {
474            if cx.last_child() == Some(widget_index![self.vert_bar])
475                && let Some(ScrollMsg(y)) = cx.try_pop()
476            {
477                let offset = Offset(self.scroll.offset().0, y);
478                let action = self.scroll.set_offset(offset);
479                cx.action(&self, action);
480                self.update_scroll_bar(cx);
481            } else if self.is_editable()
482                && let Some(SetValueText(string)) = cx.try_pop()
483            {
484                self.set_string(cx, string);
485                G::edit(&mut self.inner, cx, data);
486                G::activate(&mut self.inner, cx, data);
487            } else if let Some(kas::messages::SetScrollOffset(offset)) = cx.try_pop() {
488                self.set_scroll_offset(cx, offset);
489            }
490            // TODO: pass ReplaceSelectedText to inner widget?
491        }
492
493        fn handle_scroll(&mut self, cx: &mut EventCx<'_>, _: &G::Data, scroll: Scroll) {
494            // Inner may have resized itself, hence we update sizes now.
495            let rect = Rect {
496                pos: self.rect().pos + self.frame_offset,
497                size: self.rect().size - self.frame_size,
498            };
499            let _ = self.scroll.set_sizes(rect.size, self.inner.typeset_size());
500            self.scroll.scroll(cx, self.id(), rect, scroll);
501            self.update_scroll_bar(cx);
502        }
503    }
504
505    impl Scrollable for Self {
506        fn content_size(&self) -> Size {
507            self.inner.rect().size
508        }
509
510        fn max_scroll_offset(&self) -> Offset {
511            self.scroll.max_offset()
512        }
513
514        fn scroll_offset(&self) -> Offset {
515            self.scroll.offset()
516        }
517
518        fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
519            let action = self.scroll.set_offset(offset);
520            let offset = self.scroll.offset();
521            if !action.is_empty() {
522                cx.action(&self, action);
523                self.vert_bar.set_value(cx, offset.1);
524            }
525            offset
526        }
527    }
528
529    impl Self {
530        /// Construct an `EditBox` with an [`EditGuard`]
531        #[inline]
532        pub fn new(guard: G) -> Self {
533            EditBox {
534                core: Default::default(),
535                scroll: Default::default(),
536                inner: EditField::new(guard),
537                vert_bar: Default::default(),
538                frame_offset: Default::default(),
539                frame_size: Default::default(),
540                frame_offset_ex_margin: Default::default(),
541                inner_margin: Default::default(),
542                clip_rect: Default::default(),
543            }
544        }
545
546        fn update_scroll_bar(&mut self, cx: &mut EventState) {
547            let max_offset = self.scroll.max_offset().1;
548            self.vert_bar
549                .set_limits(cx, max_offset, self.inner.rect().size.1);
550            self.vert_bar.set_value(cx, self.scroll.offset().1);
551        }
552
553        /// Get text contents
554        #[inline]
555        pub fn as_str(&self) -> &str {
556            self.inner.as_str()
557        }
558
559        /// Get the text contents as a `String`
560        #[inline]
561        pub fn clone_string(&self) -> String {
562            self.inner.clone_string()
563        }
564
565        // Set text contents from a `str`
566        #[inline]
567        pub fn set_str(&mut self, cx: &mut EventState, text: &str) {
568            self.inner.set_str(cx, text);
569        }
570
571        /// Set text contents from a `String`
572        ///
573        /// This method does not call action handlers on the [`EditGuard`].
574        #[inline]
575        pub fn set_string(&mut self, cx: &mut EventState, text: String) {
576            self.inner.set_string(cx, text);
577        }
578
579        /// Access the edit guard
580        #[inline]
581        pub fn guard(&self) -> &G {
582            &self.inner.guard
583        }
584    }
585}
586
587impl<A: 'static> EditBox<DefaultGuard<A>> {
588    /// Construct an `EditBox` with the given inital `text` (no event handling)
589    #[inline]
590    pub fn text<S: ToString>(text: S) -> Self {
591        EditBox {
592            inner: EditField::text(text),
593            ..Default::default()
594        }
595    }
596
597    /// Construct a read-only `EditBox` displaying some `String` value
598    #[inline]
599    pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditBox<StringGuard<A>> {
600        EditBox::new(StringGuard::new(value_fn)).with_editable(false)
601    }
602
603    /// Construct an `EditBox` for a parsable value (e.g. a number)
604    ///
605    /// On update, `value_fn` is used to extract a value from input data
606    /// which is then formatted as a string via [`Display`].
607    /// If, however, the input field has focus, the update is ignored.
608    ///
609    /// On every edit, the guard attempts to parse the field's input as type
610    /// `T` via [`FromStr`], caching the result and setting the error state.
611    ///
612    /// On field activation and focus loss when a `T` value is cached (see
613    /// previous paragraph), `on_afl` is used to construct a message to be
614    /// emitted via [`EventCx::push`]. The cached value is then cleared to
615    /// avoid sending duplicate messages.
616    #[inline]
617    pub fn parser<T: Debug + Display + FromStr, M: Debug + 'static>(
618        value_fn: impl Fn(&A) -> T + 'static,
619        msg_fn: impl Fn(T) -> M + 'static,
620    ) -> EditBox<ParseGuard<A, T>> {
621        EditBox::new(ParseGuard::new(value_fn, msg_fn))
622    }
623
624    /// Construct an `EditBox` for a parsable value (e.g. a number)
625    ///
626    /// On update, `value_fn` is used to extract a value from input data
627    /// which is then formatted as a string via [`Display`].
628    /// If, however, the input field has focus, the update is ignored.
629    ///
630    /// On every edit, the guard attempts to parse the field's input as type
631    /// `T` via [`FromStr`]. On success, the result is converted to a
632    /// message via `on_afl` then emitted via [`EventCx::push`].
633    pub fn instant_parser<T: Debug + Display + FromStr, M: Debug + 'static>(
634        value_fn: impl Fn(&A) -> T + 'static,
635        msg_fn: impl Fn(T) -> M + 'static,
636    ) -> EditBox<InstantParseGuard<A, T>> {
637        EditBox::new(InstantParseGuard::new(value_fn, msg_fn))
638    }
639}
640
641impl<A: 'static> EditBox<StringGuard<A>> {
642    /// Assign a message function for a `String` value
643    ///
644    /// The `msg_fn` is called when the field is activated (<kbd>Enter</kbd>)
645    /// and when it loses focus after content is changed.
646    ///
647    /// This method sets self as editable (see [`Self::with_editable`]).
648    #[must_use]
649    pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + 'static) -> Self
650    where
651        M: Debug + 'static,
652    {
653        self.inner.guard = self.inner.guard.with_msg(msg_fn);
654        self.inner.editable = true;
655        self
656    }
657}
658
659impl<G: EditGuard> EditBox<G> {
660    /// Set the initial text (inline)
661    ///
662    /// This method should only be used on a new `EditBox`.
663    #[inline]
664    #[must_use]
665    pub fn with_text(mut self, text: impl ToString) -> Self {
666        self.inner = self.inner.with_text(text);
667        self
668    }
669
670    /// Set whether this widget is editable (inline)
671    #[inline]
672    #[must_use]
673    pub fn with_editable(mut self, editable: bool) -> Self {
674        self.inner = self.inner.with_editable(editable);
675        self
676    }
677
678    /// Get whether this `EditField` is editable
679    #[inline]
680    pub fn is_editable(&self) -> bool {
681        self.inner.is_editable()
682    }
683
684    /// Set whether this `EditField` is editable
685    #[inline]
686    pub fn set_editable(&mut self, editable: bool) {
687        self.inner.set_editable(editable);
688    }
689
690    /// Set whether this `EditBox` uses multi-line mode
691    ///
692    /// This setting has two effects: the vertical size allocation is increased
693    /// and wrapping is enabled if true. Default: false.
694    ///
695    /// This method is ineffective if the text class is set by
696    /// [`Self::with_class`] to anything other than [`TextClass::Edit`].
697    #[inline]
698    #[must_use]
699    pub fn with_multi_line(mut self, multi_line: bool) -> Self {
700        self.inner = self.inner.with_multi_line(multi_line);
701        self
702    }
703
704    /// True if the editor uses multi-line mode
705    ///
706    /// See also: [`Self::with_multi_line`]
707    #[inline]
708    pub fn multi_line(&self) -> bool {
709        self.inner.multi_line()
710    }
711
712    /// Set the text class used
713    #[inline]
714    #[must_use]
715    pub fn with_class(mut self, class: TextClass) -> Self {
716        self.inner = self.inner.with_class(class);
717        self
718    }
719
720    /// Get the text class used
721    #[inline]
722    pub fn class(&self) -> TextClass {
723        self.inner.class()
724    }
725
726    /// Adjust the height allocation
727    #[inline]
728    pub fn set_lines(&mut self, min_lines: f32, ideal_lines: f32) {
729        self.inner.set_lines(min_lines, ideal_lines);
730    }
731
732    /// Adjust the height allocation (inline)
733    #[inline]
734    #[must_use]
735    pub fn with_lines(mut self, min_lines: f32, ideal_lines: f32) -> Self {
736        self.set_lines(min_lines, ideal_lines);
737        self
738    }
739
740    /// Adjust the width allocation
741    #[inline]
742    pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
743        self.inner.set_width_em(min_em, ideal_em);
744    }
745
746    /// Adjust the width allocation (inline)
747    #[inline]
748    #[must_use]
749    pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
750        self.set_width_em(min_em, ideal_em);
751        self
752    }
753
754    /// Get whether the widget has edit focus
755    ///
756    /// This is true when the widget is editable and has keyboard focus.
757    #[inline]
758    pub fn has_edit_focus(&self) -> bool {
759        self.inner.has_edit_focus()
760    }
761
762    /// Get whether the input state is erroneous
763    #[inline]
764    pub fn has_error(&self) -> bool {
765        self.inner.has_error()
766    }
767
768    /// Set the error state
769    ///
770    /// When true, the input field's background is drawn red.
771    /// This state is cleared by [`Self::set_string`].
772    pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) {
773        self.inner.set_error_state(cx, error_state);
774    }
775}
776
777/// Used to track ongoing incompatible actions
778#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
779enum CurrentAction {
780    #[default]
781    None,
782    DragSelect,
783    ImeStart,
784    ImeEdit,
785}
786
787impl CurrentAction {
788    fn is_select(self) -> bool {
789        matches!(self, CurrentAction::DragSelect)
790    }
791
792    fn is_ime(self) -> bool {
793        matches!(self, CurrentAction::ImeStart | CurrentAction::ImeEdit)
794    }
795
796    fn is_active_ime(self) -> bool {
797        false
798    }
799
800    fn clear_active(&mut self) {
801        if matches!(self, CurrentAction::DragSelect | CurrentAction::ImeEdit) {
802            *self = CurrentAction::None;
803        }
804    }
805
806    fn clear_selection(&mut self) {
807        if matches!(self, CurrentAction::DragSelect) {
808            *self = CurrentAction::None;
809        }
810    }
811}
812
813#[impl_self]
814mod EditField {
815    /// A text-edit field (single- or multi-line)
816    ///
817    /// The [`EditBox`] widget should be preferred in most cases; this widget
818    /// is a component of `EditBox` and has some special behaviour.
819    ///
820    /// By default, the editor supports a single-line only;
821    /// [`Self::with_multi_line`] and [`Self::with_class`] can be used to change this.
822    ///
823    /// ### Event handling
824    ///
825    /// This widget attempts to handle all standard text-editor input and scroll
826    /// events.
827    ///
828    /// Key events for moving the edit cursor (e.g. arrow keys) are consumed
829    /// only if the edit cursor is moved while key events for adjusting or using
830    /// the selection (e.g. `Command::Copy` and `Command::Deselect`)
831    /// are consumed only when a selection exists. In contrast, key events for
832    /// inserting or deleting text are always consumed.
833    ///
834    /// [`Command::Enter`] inserts a line break in multi-line mode, but in
835    /// single-line mode or if the <kbd>Shift</kbd> key is held it is treated
836    /// the same as [`Command::Activate`].
837    ///
838    /// ### Performance and limitations
839    ///
840    /// Text representation is via a single [`String`]. Edit operations are
841    /// `O(n)` where `n` is the length of text (with text layout algorithms
842    /// having greater cost than copying bytes in the backing [`String`]).
843    /// This isn't necessarily *slow*; when run with optimizations the type can
844    /// handle type-setting around 20kB of UTF-8 in under 10ms (with significant
845    /// scope for optimization, given that currently layout is re-run from
846    /// scratch on each key stroke). Regardless, this approach is not designed
847    /// to scale to handle large documents via a single `EditField` widget.
848    ///
849    /// ### Messages
850    ///
851    /// [`SetValueText`] may be used to replace the entire text and
852    /// [`ReplaceSelectedText`] may be used to replace selected text, where
853    /// [`Self::is_editable`]. This triggers the action handlers
854    /// [`EditGuard::edit`] followed by [`EditGuard::activate`].
855    #[autoimpl(Clone, Debug where G: trait)]
856    #[widget]
857    pub struct EditField<G: EditGuard = DefaultGuard<()>> {
858        core: widget_core!(),
859        editable: bool,
860        width: (f32, f32),
861        lines: (f32, f32),
862        text: Text<String>,
863        selection: SelectionHelper,
864        edit_x_coord: Option<f32>,
865        old_state: Option<(String, usize, usize)>,
866        last_edit: LastEdit,
867        has_key_focus: bool,
868        current: CurrentAction,
869        error_state: bool,
870        input_handler: TextInput,
871        /// The associated [`EditGuard`] implementation
872        pub guard: G,
873    }
874
875    impl Layout for Self {
876        #[inline]
877        fn rect(&self) -> Rect {
878            self.text.rect()
879        }
880
881        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
882            let (min, mut ideal): (i32, i32);
883            if axis.is_horizontal() {
884                let dpem = sizer.dpem();
885                min = (self.width.0 * dpem).cast_ceil();
886                ideal = (self.width.1 * dpem).cast_ceil();
887            } else {
888                // TODO: line height depends on the font; 1em is not a good
889                // approximation. This code also misses inter-line spacing.
890                let dpem = sizer.dpem();
891                min = (self.lines.0 * dpem).cast_ceil();
892                ideal = (self.lines.1 * dpem).cast_ceil();
893            };
894
895            let rules = self.text.size_rules(sizer.re(), axis);
896            ideal = ideal.max(rules.ideal_size());
897
898            let margins = sizer.text_margins().extract(axis);
899            let stretch = if axis.is_horizontal() || self.multi_line() {
900                Stretch::High
901            } else {
902                Stretch::None
903            };
904            SizeRules::new(min, ideal, margins, stretch)
905        }
906
907        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, mut hints: AlignHints) {
908            hints.vert = Some(if self.multi_line() {
909                Align::Default
910            } else {
911                Align::Center
912            });
913            self.text.set_rect(cx, rect, hints);
914            self.text.ensure_no_left_overhang();
915            if self.current.is_ime() {
916                self.set_ime_cursor_area(cx);
917            }
918        }
919
920        fn draw(&self, draw: DrawCx) {
921            self.draw_with_offset(draw, self.rect(), Offset::ZERO);
922        }
923    }
924
925    impl Tile for Self {
926        fn navigable(&self) -> bool {
927            true
928        }
929
930        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
931            Role::TextInput {
932                text: self.text.as_str(),
933                multi_line: self.multi_line(),
934                cursor: self.selection.edit_index(),
935                sel_index: self.selection.sel_index(),
936            }
937        }
938
939        fn probe(&self, _: Coord) -> Id {
940            self.id()
941        }
942    }
943
944    impl Events for Self {
945        const REDRAW_ON_MOUSE_OVER: bool = true;
946
947        type Data = G::Data;
948
949        #[inline]
950        fn mouse_over_icon(&self) -> Option<CursorIcon> {
951            Some(CursorIcon::Text)
952        }
953
954        fn configure(&mut self, cx: &mut ConfigCx) {
955            cx.text_configure(&mut self.text);
956            G::configure(self, cx);
957        }
958
959        fn update(&mut self, cx: &mut ConfigCx, data: &G::Data) {
960            G::update(self, cx, data);
961        }
962
963        fn handle_event(&mut self, cx: &mut EventCx, data: &G::Data, event: Event) -> IsUsed {
964            match event {
965                Event::NavFocus(source) if source == FocusSource::Key => {
966                    if !self.has_key_focus && !self.current.is_select() {
967                        let ime = Some(ImePurpose::Normal);
968                        cx.request_key_focus(self.id(), ime, source);
969                    }
970                    Used
971                }
972                Event::NavFocus(_) => Used,
973                Event::LostNavFocus => Used,
974                Event::SelFocus(source) => {
975                    // NOTE: sel focus implies key focus since we only request
976                    // the latter. We must set before calling self.set_primary.
977                    self.has_key_focus = true;
978                    if source == FocusSource::Pointer {
979                        self.set_primary(cx);
980                    }
981                    Used
982                }
983                Event::KeyFocus => {
984                    self.has_key_focus = true;
985                    self.set_view_offset_from_cursor(cx);
986                    G::focus_gained(self, cx, data);
987                    Used
988                }
989                Event::ImeFocus => {
990                    self.current = CurrentAction::ImeStart;
991                    self.set_ime_cursor_area(cx);
992                    Used
993                }
994                Event::LostImeFocus => {
995                    if self.current.is_ime() {
996                        self.current = CurrentAction::None;
997                    }
998                    Used
999                }
1000                Event::LostKeyFocus => {
1001                    self.has_key_focus = false;
1002                    cx.redraw(&self);
1003                    G::focus_lost(self, cx, data);
1004                    Used
1005                }
1006                Event::LostSelFocus => {
1007                    // IME focus without selection focus is impossible, so we can clear all current actions
1008                    self.current = CurrentAction::None;
1009                    self.selection.set_empty();
1010                    cx.redraw(self);
1011                    Used
1012                }
1013                Event::Command(cmd, code) => match self.control_key(cx, data, cmd, code) {
1014                    Ok(r) => r,
1015                    Err(NotReady) => Used,
1016                },
1017                Event::Key(event, false) if event.state == ElementState::Pressed => {
1018                    if let Some(text) = &event.text {
1019                        let used = self.received_text(cx, text);
1020                        G::edit(self, cx, data);
1021                        used
1022                    } else {
1023                        let opt_cmd = cx
1024                            .config()
1025                            .shortcuts()
1026                            .try_match(cx.modifiers(), &event.logical_key);
1027                        if let Some(cmd) = opt_cmd {
1028                            match self.control_key(cx, data, cmd, Some(event.physical_key)) {
1029                                Ok(r) => r,
1030                                Err(NotReady) => Used,
1031                            }
1032                        } else {
1033                            Unused
1034                        }
1035                    }
1036                }
1037                Event::ImePreedit(text, cursor) => {
1038                    if self.current != CurrentAction::ImeEdit {
1039                        if cursor.is_some() {
1040                            self.selection.set_anchor_to_range_start();
1041                            self.current = CurrentAction::ImeEdit;
1042                        } else {
1043                            return Used;
1044                        }
1045                    }
1046
1047                    let range = self.selection.anchor_to_edit_range();
1048                    self.text.replace_range(range.clone(), text);
1049
1050                    if let Some((start, end)) = cursor {
1051                        self.selection.set_sel_index_only(range.start + start);
1052                        self.selection.set_edit_index(range.start + end);
1053                    } else {
1054                        self.selection.set_all(range.start + text.len());
1055                    }
1056                    self.edit_x_coord = None;
1057                    self.prepare_text(cx);
1058                    Used
1059                }
1060                Event::ImeCommit(text) => {
1061                    if self.current != CurrentAction::ImeEdit {
1062                        self.selection.set_anchor_to_range_start();
1063                    }
1064                    self.current = CurrentAction::None;
1065
1066                    let range = self.selection.anchor_to_edit_range();
1067                    self.text.replace_range(range.clone(), text);
1068
1069                    self.selection.set_all(range.start + text.len());
1070                    self.edit_x_coord = None;
1071                    self.prepare_text(cx);
1072                    Used
1073                }
1074                Event::PressStart(press) if press.is_tertiary() => {
1075                    press.grab_click(self.id()).complete(cx)
1076                }
1077                Event::PressEnd { press, .. } if press.is_tertiary() => {
1078                    if let Some(content) = cx.get_primary() {
1079                        self.set_cursor_from_coord(cx, press.coord);
1080                        self.current.clear_selection();
1081                        self.selection.set_empty();
1082                        let index = self.selection.edit_index();
1083                        let range = self.trim_paste(&content);
1084                        let len = range.len();
1085
1086                        self.old_state =
1087                            Some((self.text.clone_string(), index, self.selection.sel_index()));
1088                        self.last_edit = LastEdit::Paste;
1089
1090                        self.text.replace_range(index..index, &content[range]);
1091                        self.selection.set_all(index + len);
1092                        self.edit_x_coord = None;
1093                        self.prepare_text(cx);
1094
1095                        G::edit(self, cx, data);
1096                    }
1097                    Used
1098                }
1099                event => match self.input_handler.handle(cx, self.id(), event) {
1100                    TextInputAction::Used => Used,
1101                    TextInputAction::Unused => Unused,
1102                    TextInputAction::Focus { coord, action }
1103                        if self.current.is_select() || action.anchor =>
1104                    {
1105                        if self.current.is_ime() {
1106                            cx.cancel_ime_focus(self.id());
1107                        }
1108                        self.current = CurrentAction::DragSelect;
1109                        self.set_cursor_from_coord(cx, coord);
1110                        self.selection.action(&self.text, action);
1111
1112                        if self.has_key_focus {
1113                            self.set_primary(cx);
1114                        }
1115                        Used
1116                    }
1117                    TextInputAction::Finish if self.current.is_select() => {
1118                        self.current = CurrentAction::None;
1119                        let ime = Some(ImePurpose::Normal);
1120                        cx.request_key_focus(self.id(), ime, FocusSource::Pointer);
1121                        Used
1122                    }
1123                    _ => Used,
1124                },
1125            }
1126        }
1127
1128        fn handle_messages(&mut self, cx: &mut EventCx, data: &G::Data) {
1129            if !self.editable {
1130                return;
1131            }
1132
1133            if let Some(SetValueText(string)) = cx.try_pop() {
1134                self.set_string(cx, string);
1135                G::edit(self, cx, data);
1136                G::activate(self, cx, data);
1137            } else if let Some(ReplaceSelectedText(text)) = cx.try_pop() {
1138                self.received_text(cx, &text);
1139                G::edit(self, cx, data);
1140                G::activate(self, cx, data);
1141            }
1142        }
1143    }
1144
1145    impl Default for Self
1146    where
1147        G: Default,
1148    {
1149        #[inline]
1150        fn default() -> Self {
1151            EditField::new(G::default())
1152        }
1153    }
1154
1155    impl Self {
1156        /// Construct an `EditBox` with an [`EditGuard`]
1157        #[inline]
1158        pub fn new(guard: G) -> EditField<G> {
1159            EditField {
1160                core: Default::default(),
1161                editable: true,
1162                width: (8.0, 16.0),
1163                lines: (1.0, 1.0),
1164                text: Text::default().with_class(TextClass::Edit(false)),
1165                selection: Default::default(),
1166                edit_x_coord: None,
1167                old_state: None,
1168                last_edit: Default::default(),
1169                has_key_focus: false,
1170                current: CurrentAction::None,
1171                error_state: false,
1172                input_handler: Default::default(),
1173                guard,
1174            }
1175        }
1176
1177        /// Get text contents
1178        #[inline]
1179        pub fn as_str(&self) -> &str {
1180            self.text.as_str()
1181        }
1182
1183        /// Get the text contents as a `String`
1184        #[inline]
1185        pub fn clone_string(&self) -> String {
1186            self.text.clone_string()
1187        }
1188
1189        // Set text contents from a `str`
1190        #[inline]
1191        pub fn set_str(&mut self, cx: &mut EventState, text: &str) {
1192            if self.text.as_str() != text {
1193                self.set_string(cx, text.to_string());
1194            }
1195        }
1196
1197        /// Set text contents from a `String`
1198        ///
1199        /// This method does not call action handlers on the [`EditGuard`].
1200        pub fn set_string(&mut self, cx: &mut EventState, string: String) {
1201            if !self.text.set_string(string) || !self.text.prepare() {
1202                return;
1203            }
1204
1205            self.current.clear_active();
1206            self.selection.set_max_len(self.text.str_len());
1207            cx.redraw(&self);
1208            if self.current.is_ime() {
1209                self.set_ime_cursor_area(cx);
1210            }
1211            self.set_error_state(cx, false);
1212        }
1213
1214        /// Replace selected text
1215        ///
1216        /// This method does not call action handlers on the [`EditGuard`].
1217        pub fn replace_selection(&mut self, cx: &mut EventCx, text: &str) {
1218            self.received_text(cx, text);
1219        }
1220
1221        // Call only if self.ime_focus
1222        fn set_ime_cursor_area(&self, cx: &mut EventState) {
1223            if let Ok(display) = self.text.display() {
1224                if let Some(mut rect) = self.selection.cursor_rect(display) {
1225                    rect.pos += Offset::conv(self.rect().pos);
1226                    cx.set_ime_cursor_area(self.id_ref(), rect);
1227                }
1228            }
1229        }
1230
1231        /// Get the size of the type-set text
1232        ///
1233        /// `EditField` ensures text has no left or top overhang.
1234        #[inline]
1235        pub fn typeset_size(&self) -> Size {
1236            let mut size = self.rect().size;
1237            if let Ok((tl, br)) = self.text.bounding_box() {
1238                size.1 = size.1.max((br.1 - tl.1).cast_ceil());
1239                size.0 = size.0.max((br.0 - tl.0).cast_ceil());
1240            }
1241            size
1242        }
1243
1244        /// Draw with an offset
1245        ///
1246        /// Draws at position `self.rect() - offset`.
1247        ///
1248        /// This may be called instead of [`Layout::draw`].
1249        pub fn draw_with_offset(&self, mut draw: DrawCx, rect: Rect, offset: Offset) {
1250            let pos = self.rect().pos - offset;
1251
1252            draw.text_selected(pos, rect, &self.text, self.selection.range());
1253
1254            if self.editable && draw.ev_state().has_key_focus(self.id_ref()).0 {
1255                draw.text_cursor(pos, rect, &self.text, self.selection.edit_index());
1256            }
1257        }
1258    }
1259}
1260
1261impl<A: 'static> EditField<DefaultGuard<A>> {
1262    /// Construct an `EditField` with the given inital `text` (no event handling)
1263    #[inline]
1264    pub fn text<S: ToString>(text: S) -> Self {
1265        let text = text.to_string();
1266        let len = text.len();
1267        EditField {
1268            editable: true,
1269            text: Text::new(text, TextClass::Edit(false)),
1270            selection: SelectionHelper::new(len, len),
1271            ..Default::default()
1272        }
1273    }
1274
1275    /// Construct a read-only `EditField` displaying some `String` value
1276    #[inline]
1277    pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditField<StringGuard<A>> {
1278        EditField::new(StringGuard::new(value_fn)).with_editable(false)
1279    }
1280
1281    /// Construct an `EditField` for a parsable value (e.g. a number)
1282    ///
1283    /// On update, `value_fn` is used to extract a value from input data
1284    /// which is then formatted as a string via [`Display`].
1285    /// If, however, the input field has focus, the update is ignored.
1286    ///
1287    /// On every edit, the guard attempts to parse the field's input as type
1288    /// `T` via [`FromStr`], caching the result and setting the error state.
1289    ///
1290    /// On field activation and focus loss when a `T` value is cached (see
1291    /// previous paragraph), `on_afl` is used to construct a message to be
1292    /// emitted via [`EventCx::push`]. The cached value is then cleared to
1293    /// avoid sending duplicate messages.
1294    #[inline]
1295    pub fn parser<T: Debug + Display + FromStr, M: Debug + 'static>(
1296        value_fn: impl Fn(&A) -> T + 'static,
1297        msg_fn: impl Fn(T) -> M + 'static,
1298    ) -> EditField<ParseGuard<A, T>> {
1299        EditField::new(ParseGuard::new(value_fn, msg_fn))
1300    }
1301
1302    /// Construct an `EditField` for a parsable value (e.g. a number)
1303    ///
1304    /// On update, `value_fn` is used to extract a value from input data
1305    /// which is then formatted as a string via [`Display`].
1306    /// If, however, the input field has focus, the update is ignored.
1307    ///
1308    /// On every edit, the guard attempts to parse the field's input as type
1309    /// `T` via [`FromStr`]. On success, the result is converted to a
1310    /// message via `on_afl` then emitted via [`EventCx::push`].
1311    pub fn instant_parser<T: Debug + Display + FromStr, M: Debug + 'static>(
1312        value_fn: impl Fn(&A) -> T + 'static,
1313        msg_fn: impl Fn(T) -> M + 'static,
1314    ) -> EditField<InstantParseGuard<A, T>> {
1315        EditField::new(InstantParseGuard::new(value_fn, msg_fn))
1316    }
1317}
1318
1319impl<A: 'static> EditField<StringGuard<A>> {
1320    /// Assign a message function for a `String` value
1321    ///
1322    /// The `msg_fn` is called when the field is activated (<kbd>Enter</kbd>)
1323    /// and when it loses focus after content is changed.
1324    ///
1325    /// This method sets self as editable (see [`Self::with_editable`]).
1326    #[must_use]
1327    pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + 'static) -> Self
1328    where
1329        M: Debug + 'static,
1330    {
1331        self.guard = self.guard.with_msg(msg_fn);
1332        self.editable = true;
1333        self
1334    }
1335}
1336
1337impl<G: EditGuard> EditField<G> {
1338    /// Set the initial text (inline)
1339    ///
1340    /// This method should only be used on a new `EditField`.
1341    #[inline]
1342    #[must_use]
1343    pub fn with_text(mut self, text: impl ToString) -> Self {
1344        debug_assert!(self.current == CurrentAction::None);
1345        let text = text.to_string();
1346        let len = text.len();
1347        self.text.set_string(text);
1348        self.selection.set_all(len);
1349        self
1350    }
1351
1352    /// Set whether this `EditField` is editable (inline)
1353    #[inline]
1354    #[must_use]
1355    pub fn with_editable(mut self, editable: bool) -> Self {
1356        self.editable = editable;
1357        self
1358    }
1359
1360    /// Get whether this `EditField` is editable
1361    #[inline]
1362    pub fn is_editable(&self) -> bool {
1363        self.editable
1364    }
1365
1366    /// Set whether this `EditField` is editable
1367    #[inline]
1368    pub fn set_editable(&mut self, editable: bool) {
1369        self.editable = editable;
1370    }
1371
1372    /// Set whether this `EditField` uses multi-line mode
1373    ///
1374    /// This method does two things:
1375    ///
1376    /// -   Changes the text class (see [`Self::with_class`])
1377    /// -   Changes the vertical height allocation (see [`Self::with_lines`])
1378    #[inline]
1379    #[must_use]
1380    pub fn with_multi_line(mut self, multi_line: bool) -> Self {
1381        self.text.set_class(TextClass::Edit(multi_line));
1382        self.lines = match multi_line {
1383            false => (1.0, 1.0),
1384            true => (4.0, 7.0),
1385        };
1386        self
1387    }
1388
1389    /// True if the editor uses multi-line mode
1390    ///
1391    /// See also: [`Self::with_multi_line`]
1392    #[inline]
1393    pub fn multi_line(&self) -> bool {
1394        self.class().multi_line()
1395    }
1396
1397    /// Set the text class used
1398    #[inline]
1399    #[must_use]
1400    pub fn with_class(mut self, class: TextClass) -> Self {
1401        self.text.set_class(class);
1402        self
1403    }
1404
1405    /// Get the text class used
1406    #[inline]
1407    pub fn class(&self) -> TextClass {
1408        self.text.class()
1409    }
1410
1411    /// Adjust the height allocation
1412    #[inline]
1413    pub fn set_lines(&mut self, min_lines: f32, ideal_lines: f32) {
1414        self.lines = (min_lines, ideal_lines);
1415    }
1416
1417    /// Adjust the height allocation (inline)
1418    #[inline]
1419    #[must_use]
1420    pub fn with_lines(mut self, min_lines: f32, ideal_lines: f32) -> Self {
1421        self.set_lines(min_lines, ideal_lines);
1422        self
1423    }
1424
1425    /// Adjust the width allocation
1426    #[inline]
1427    pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
1428        self.width = (min_em, ideal_em);
1429    }
1430
1431    /// Adjust the width allocation (inline)
1432    #[inline]
1433    #[must_use]
1434    pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
1435        self.set_width_em(min_em, ideal_em);
1436        self
1437    }
1438
1439    /// Get whether the widget has edit focus
1440    ///
1441    /// This is true when the widget is editable and has keyboard focus.
1442    #[inline]
1443    pub fn has_edit_focus(&self) -> bool {
1444        self.editable && self.has_key_focus
1445    }
1446
1447    /// Get whether the input state is erroneous
1448    #[inline]
1449    pub fn has_error(&self) -> bool {
1450        self.error_state
1451    }
1452
1453    /// Set the error state
1454    ///
1455    /// When true, the input field's background is drawn red.
1456    /// This state is cleared by [`Self::set_string`].
1457    // TODO: possibly change type to Option<String> and display the error
1458    pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) {
1459        self.error_state = error_state;
1460        cx.redraw(self);
1461    }
1462
1463    fn prepare_text(&mut self, cx: &mut EventCx) {
1464        if self.text.prepare() {
1465            self.text.ensure_no_left_overhang();
1466            cx.redraw(&self);
1467        }
1468
1469        self.set_view_offset_from_cursor(cx);
1470    }
1471
1472    fn trim_paste(&self, text: &str) -> Range<usize> {
1473        let mut end = text.len();
1474        if !self.multi_line() {
1475            // We cut the content short on control characters and
1476            // ignore them (preventing line-breaks and ignoring any
1477            // actions such as recursive-paste).
1478            for (i, c) in text.char_indices() {
1479                if c < '\u{20}' || ('\u{7f}'..='\u{9f}').contains(&c) {
1480                    end = i;
1481                    break;
1482                }
1483            }
1484        }
1485        0..end
1486    }
1487
1488    fn received_text(&mut self, cx: &mut EventCx, text: &str) -> IsUsed {
1489        if !self.editable || self.current.is_active_ime() {
1490            return Unused;
1491        }
1492
1493        self.current.clear_selection();
1494        let index = self.selection.edit_index();
1495        let selection = self.selection.range();
1496        let have_sel = selection.start < selection.end;
1497        if self.last_edit != LastEdit::Insert || have_sel {
1498            self.old_state = Some((self.text.clone_string(), index, self.selection.sel_index()));
1499            self.last_edit = LastEdit::Insert;
1500        }
1501        if have_sel {
1502            self.text.replace_range(selection.clone(), text);
1503            self.selection.set_all(selection.start + text.len());
1504        } else {
1505            // TODO(kas-text) support the following:
1506            // self.text.insert_str(index, text);
1507            let mut s = self.text.clone_string();
1508            s.insert_str(index, text);
1509            self.text.set_text(s);
1510            // END workaround
1511            self.selection.set_all(index + text.len());
1512        }
1513        self.edit_x_coord = None;
1514
1515        self.prepare_text(cx);
1516        Used
1517    }
1518
1519    fn control_key(
1520        &mut self,
1521        cx: &mut EventCx,
1522        data: &G::Data,
1523        cmd: Command,
1524        code: Option<PhysicalKey>,
1525    ) -> Result<IsUsed, NotReady> {
1526        let editable = self.editable;
1527        let mut shift = cx.modifiers().shift_key();
1528        let mut buf = [0u8; 4];
1529        let cursor = self.selection.edit_index();
1530        let len = self.text.str_len();
1531        let multi_line = self.multi_line();
1532        let selection = self.selection.range();
1533        let have_sel = selection.end > selection.start;
1534        let string;
1535
1536        enum Action<'a> {
1537            None,
1538            Activate,
1539            Edit,
1540            Insert(&'a str, LastEdit),
1541            Delete(Range<usize>),
1542            Move(usize, Option<f32>),
1543        }
1544
1545        let action = match cmd {
1546            Command::Escape | Command::Deselect
1547                if !self.current.is_active_ime() && !selection.is_empty() =>
1548            {
1549                self.current.clear_selection();
1550                self.selection.set_empty();
1551                cx.redraw(&self);
1552                Action::None
1553            }
1554            Command::Activate => Action::Activate,
1555            Command::Enter if shift || !multi_line => Action::Activate,
1556            Command::Enter if editable && multi_line => {
1557                Action::Insert('\n'.encode_utf8(&mut buf), LastEdit::Insert)
1558            }
1559            // NOTE: we might choose to optionally handle Tab in the future,
1560            // but without some workaround it prevents keyboard navigation.
1561            // Command::Tab => Action::Insert('\t'.encode_utf8(&mut buf), LastEdit::Insert),
1562            Command::Left | Command::Home if !shift && have_sel => {
1563                Action::Move(selection.start, None)
1564            }
1565            Command::Left if cursor > 0 => {
1566                let mut cursor = GraphemeCursor::new(cursor, len, true);
1567                cursor
1568                    .prev_boundary(self.text.text(), 0)
1569                    .unwrap()
1570                    .map(|index| Action::Move(index, None))
1571                    .unwrap_or(Action::None)
1572            }
1573            Command::Right | Command::End if !shift && have_sel => {
1574                Action::Move(selection.end, None)
1575            }
1576            Command::Right if cursor < len => {
1577                let mut cursor = GraphemeCursor::new(cursor, len, true);
1578                cursor
1579                    .next_boundary(self.text.text(), 0)
1580                    .unwrap()
1581                    .map(|index| Action::Move(index, None))
1582                    .unwrap_or(Action::None)
1583            }
1584            Command::WordLeft if cursor > 0 => {
1585                let mut iter = self.text.text()[0..cursor].split_word_bound_indices();
1586                let mut p = iter.next_back().map(|(index, _)| index).unwrap_or(0);
1587                while self.text.text()[p..]
1588                    .chars()
1589                    .next()
1590                    .map(|c| c.is_whitespace())
1591                    .unwrap_or(false)
1592                {
1593                    if let Some((index, _)) = iter.next_back() {
1594                        p = index;
1595                    } else {
1596                        break;
1597                    }
1598                }
1599                Action::Move(p, None)
1600            }
1601            Command::WordRight if cursor < len => {
1602                let mut iter = self.text.text()[cursor..]
1603                    .split_word_bound_indices()
1604                    .skip(1);
1605                let mut p = iter.next().map(|(index, _)| cursor + index).unwrap_or(len);
1606                while self.text.text()[p..]
1607                    .chars()
1608                    .next()
1609                    .map(|c| c.is_whitespace())
1610                    .unwrap_or(false)
1611                {
1612                    if let Some((index, _)) = iter.next() {
1613                        p = cursor + index;
1614                    } else {
1615                        break;
1616                    }
1617                }
1618                Action::Move(p, None)
1619            }
1620            // Avoid use of unused navigation keys (e.g. by ScrollComponent):
1621            Command::Left | Command::Right | Command::WordLeft | Command::WordRight => Action::None,
1622            Command::Up | Command::Down if multi_line => {
1623                let x = match self.edit_x_coord {
1624                    Some(x) => x,
1625                    None => self
1626                        .text
1627                        .text_glyph_pos(cursor)?
1628                        .next_back()
1629                        .map(|r| r.pos.0)
1630                        .unwrap_or(0.0),
1631                };
1632                let mut line = self.text.find_line(cursor)?.map(|r| r.0).unwrap_or(0);
1633                // We can tolerate invalid line numbers here!
1634                line = match cmd {
1635                    Command::Up => line.wrapping_sub(1),
1636                    Command::Down => line.wrapping_add(1),
1637                    _ => unreachable!(),
1638                };
1639                const HALF: usize = usize::MAX / 2;
1640                let nearest_end = match line {
1641                    0..=HALF => len,
1642                    _ => 0,
1643                };
1644                self.text
1645                    .line_index_nearest(line, x)?
1646                    .map(|index| Action::Move(index, Some(x)))
1647                    .unwrap_or(Action::Move(nearest_end, None))
1648            }
1649            Command::Home if cursor > 0 => {
1650                let index = self.text.find_line(cursor)?.map(|r| r.1.start).unwrap_or(0);
1651                Action::Move(index, None)
1652            }
1653            Command::End if cursor < len => {
1654                let index = self.text.find_line(cursor)?.map(|r| r.1.end).unwrap_or(len);
1655                Action::Move(index, None)
1656            }
1657            Command::DocHome if cursor > 0 => Action::Move(0, None),
1658            Command::DocEnd if cursor < len => Action::Move(len, None),
1659            // Avoid use of unused navigation keys (e.g. by ScrollComponent):
1660            Command::Home | Command::End | Command::DocHome | Command::DocEnd => Action::None,
1661            Command::PageUp | Command::PageDown if multi_line => {
1662                let mut v = self
1663                    .text
1664                    .text_glyph_pos(cursor)?
1665                    .next_back()
1666                    .map(|r| r.pos.into())
1667                    .unwrap_or(Vec2::ZERO);
1668                if let Some(x) = self.edit_x_coord {
1669                    v.0 = x;
1670                }
1671                const FACTOR: f32 = 2.0 / 3.0;
1672                let mut h_dist = f32::conv(self.text.rect().size.1) * FACTOR;
1673                if cmd == Command::PageUp {
1674                    h_dist *= -1.0;
1675                }
1676                v.1 += h_dist;
1677                Action::Move(self.text.text_index_nearest(v)?, Some(v.0))
1678            }
1679            Command::Delete | Command::DelBack if editable && have_sel => {
1680                Action::Delete(selection.clone())
1681            }
1682            Command::Delete if editable => GraphemeCursor::new(cursor, len, true)
1683                .next_boundary(self.text.text(), 0)
1684                .unwrap()
1685                .map(|next| Action::Delete(cursor..next))
1686                .unwrap_or(Action::None),
1687            Command::DelBack if editable => {
1688                // We always delete one code-point, not one grapheme cluster:
1689                let prev = self.text.text()[0..cursor]
1690                    .char_indices()
1691                    .next_back()
1692                    .map(|(i, _)| i)
1693                    .unwrap_or(0);
1694                Action::Delete(prev..cursor)
1695            }
1696            Command::DelWord if editable => {
1697                let next = self.text.text()[cursor..]
1698                    .split_word_bound_indices()
1699                    .nth(1)
1700                    .map(|(index, _)| cursor + index)
1701                    .unwrap_or(len);
1702                Action::Delete(cursor..next)
1703            }
1704            Command::DelWordBack if editable => {
1705                let prev = self.text.text()[0..cursor]
1706                    .split_word_bound_indices()
1707                    .next_back()
1708                    .map(|(index, _)| index)
1709                    .unwrap_or(0);
1710                Action::Delete(prev..cursor)
1711            }
1712            Command::SelectAll => {
1713                self.selection.set_sel_index(0);
1714                shift = true; // hack
1715                Action::Move(len, None)
1716            }
1717            Command::Cut if editable && have_sel => {
1718                cx.set_clipboard((self.text.text()[selection.clone()]).into());
1719                Action::Delete(selection.clone())
1720            }
1721            Command::Copy if have_sel => {
1722                cx.set_clipboard((self.text.text()[selection.clone()]).into());
1723                Action::None
1724            }
1725            Command::Paste if editable => {
1726                if let Some(content) = cx.get_clipboard() {
1727                    let range = self.trim_paste(&content);
1728                    string = content;
1729                    Action::Insert(&string[range], LastEdit::Paste)
1730                } else {
1731                    Action::None
1732                }
1733            }
1734            Command::Undo | Command::Redo if editable => {
1735                // TODO: maintain full edit history (externally?)
1736                if let Some((state, c2, sel)) = self.old_state.as_mut() {
1737                    self.text.swap_string(state);
1738                    self.selection.set_edit_index(*c2);
1739                    *c2 = cursor;
1740                    let index = *sel;
1741                    *sel = self.selection.sel_index();
1742                    self.selection.set_sel_index(index);
1743                    self.edit_x_coord = None;
1744                    self.last_edit = LastEdit::None;
1745                }
1746                Action::Edit
1747            }
1748            _ => return Ok(Unused),
1749        };
1750
1751        if !self.has_key_focus {
1752            // This can happen if we still had selection focus, then received
1753            // e.g. Command::Copy.
1754            let ime = Some(ImePurpose::Normal);
1755            cx.request_key_focus(self.id(), ime, FocusSource::Synthetic);
1756        }
1757
1758        if !matches!(action, Action::None) {
1759            self.current = CurrentAction::None;
1760        }
1761
1762        let result = match action {
1763            Action::None => EditAction::None,
1764            Action::Activate => EditAction::Activate,
1765            Action::Edit => EditAction::Edit,
1766            Action::Insert(s, edit) => {
1767                let mut index = cursor;
1768                if have_sel {
1769                    self.old_state =
1770                        Some((self.text.clone_string(), index, self.selection.sel_index()));
1771                    self.last_edit = edit;
1772
1773                    self.text.replace_range(selection.clone(), s);
1774                    index = selection.start;
1775                } else {
1776                    if self.last_edit != edit {
1777                        self.old_state =
1778                            Some((self.text.clone_string(), index, self.selection.sel_index()));
1779                        self.last_edit = edit;
1780                    }
1781
1782                    self.text.replace_range(index..index, s);
1783                }
1784                self.selection.set_all(index + s.len());
1785                self.edit_x_coord = None;
1786                EditAction::Edit
1787            }
1788            Action::Delete(sel) => {
1789                if self.last_edit != LastEdit::Delete {
1790                    self.old_state =
1791                        Some((self.text.clone_string(), cursor, self.selection.sel_index()));
1792                    self.last_edit = LastEdit::Delete;
1793                }
1794
1795                self.text.replace_range(sel.clone(), "");
1796                self.selection.set_all(sel.start);
1797                self.edit_x_coord = None;
1798                EditAction::Edit
1799            }
1800            Action::Move(index, x_coord) => {
1801                self.selection.set_edit_index(index);
1802                if !shift {
1803                    self.selection.set_empty();
1804                } else {
1805                    self.set_primary(cx);
1806                }
1807                self.edit_x_coord = x_coord;
1808                cx.redraw(&self);
1809                EditAction::None
1810            }
1811        };
1812
1813        self.prepare_text(cx);
1814
1815        Ok(match result {
1816            EditAction::None => Used,
1817            EditAction::Activate => {
1818                cx.depress_with_key(&self, code);
1819                G::activate(self, cx, data)
1820            }
1821            EditAction::Edit => {
1822                G::edit(self, cx, data);
1823                Used
1824            }
1825        })
1826    }
1827
1828    fn set_cursor_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
1829        let rel_pos = (coord - self.rect().pos).cast();
1830        if let Ok(index) = self.text.text_index_nearest(rel_pos) {
1831            if index != self.selection.edit_index() {
1832                self.selection.set_edit_index(index);
1833                self.set_view_offset_from_cursor(cx);
1834                self.edit_x_coord = None;
1835                cx.redraw(self);
1836            }
1837        }
1838    }
1839
1840    fn set_primary(&self, cx: &mut EventCx) {
1841        if self.has_key_focus && !self.selection.is_empty() && cx.has_primary() {
1842            let range = self.selection.range();
1843            cx.set_primary(String::from(&self.text.as_str()[range]));
1844        }
1845    }
1846
1847    /// Update view_offset after the cursor index changes
1848    ///
1849    /// A redraw is assumed since the cursor moved.
1850    fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx) {
1851        let cursor = self.selection.edit_index();
1852        if let Some(marker) = self
1853            .text
1854            .text_glyph_pos(cursor)
1855            .ok()
1856            .and_then(|mut m| m.next_back())
1857        {
1858            let y0 = (marker.pos.1 - marker.ascent).cast_floor();
1859            let pos = self.rect().pos + Offset(marker.pos.0.cast_nearest(), y0);
1860            let size = Size(0, i32::conv_ceil(marker.pos.1 - marker.descent) - y0);
1861            cx.set_scroll(Scroll::Rect(Rect { pos, size }));
1862        }
1863    }
1864}