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 pos = self.rect().pos + self.frame_offset;
496            let size = self.update_content_size();
497            let rect = Rect { pos, size };
498            self.scroll.scroll(cx, self.id(), rect, scroll);
499            self.update_scroll_bar(cx);
500        }
501    }
502
503    impl Scrollable for Self {
504        fn content_size(&self) -> Size {
505            self.inner.rect().size
506        }
507
508        fn max_scroll_offset(&self) -> Offset {
509            self.scroll.max_offset()
510        }
511
512        fn scroll_offset(&self) -> Offset {
513            self.scroll.offset()
514        }
515
516        fn set_scroll_offset(&mut self, cx: &mut EventCx, offset: Offset) -> Offset {
517            let action = self.scroll.set_offset(offset);
518            let offset = self.scroll.offset();
519            if !action.is_empty() {
520                cx.action(&self, action);
521                self.vert_bar.set_value(cx, offset.1);
522            }
523            offset
524        }
525    }
526
527    impl Self {
528        /// Construct an `EditBox` with an [`EditGuard`]
529        #[inline]
530        pub fn new(guard: G) -> Self {
531            EditBox {
532                core: Default::default(),
533                scroll: Default::default(),
534                inner: EditField::new(guard),
535                vert_bar: Default::default(),
536                frame_offset: Default::default(),
537                frame_size: Default::default(),
538                frame_offset_ex_margin: Default::default(),
539                inner_margin: Default::default(),
540                clip_rect: Default::default(),
541            }
542        }
543
544        fn update_content_size(&mut self) -> Size {
545            let size = self.rect().size - self.frame_size;
546            let _ = self.scroll.set_sizes(size, self.inner.typeset_size());
547            size
548        }
549
550        fn update_scroll_bar(&mut self, cx: &mut EventState) {
551            let max_offset = self.scroll.max_offset().1;
552            self.vert_bar
553                .set_limits(cx, max_offset, self.inner.rect().size.1);
554            self.vert_bar.set_value(cx, self.scroll.offset().1);
555        }
556
557        /// Get text contents
558        #[inline]
559        pub fn as_str(&self) -> &str {
560            self.inner.as_str()
561        }
562
563        /// Get the text contents as a `String`
564        #[inline]
565        pub fn clone_string(&self) -> String {
566            self.inner.clone_string()
567        }
568
569        // Set text contents from a `str`
570        #[inline]
571        pub fn set_str(&mut self, cx: &mut EventState, text: &str) {
572            if self.inner.set_str(cx, text) {
573                self.update_content_size();
574                self.update_scroll_bar(cx);
575            }
576        }
577
578        /// Set text contents from a `String`
579        ///
580        /// This method does not call action handlers on the [`EditGuard`].
581        #[inline]
582        pub fn set_string(&mut self, cx: &mut EventState, text: String) {
583            if self.inner.set_string(cx, text) {
584                self.update_content_size();
585                self.update_scroll_bar(cx);
586            }
587        }
588
589        /// Access the edit guard
590        #[inline]
591        pub fn guard(&self) -> &G {
592            &self.inner.guard
593        }
594
595        /// Access the edit guard mutably
596        #[inline]
597        pub fn guard_mut(&mut self) -> &mut G {
598            &mut self.inner.guard
599        }
600    }
601}
602
603impl<A: 'static> EditBox<DefaultGuard<A>> {
604    /// Construct an `EditBox` with the given inital `text` (no event handling)
605    #[inline]
606    pub fn text<S: ToString>(text: S) -> Self {
607        EditBox {
608            inner: EditField::text(text),
609            ..Default::default()
610        }
611    }
612
613    /// Construct a read-only `EditBox` displaying some `String` value
614    #[inline]
615    pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditBox<StringGuard<A>> {
616        EditBox::new(StringGuard::new(value_fn)).with_editable(false)
617    }
618
619    /// Construct an `EditBox` for a parsable value (e.g. a number)
620    ///
621    /// On update, `value_fn` is used to extract a value from input data
622    /// which is then formatted as a string via [`Display`].
623    /// If, however, the input field has focus, the update is ignored.
624    ///
625    /// On every edit, the guard attempts to parse the field's input as type
626    /// `T` via [`FromStr`], caching the result and setting the error state.
627    ///
628    /// On field activation and focus loss when a `T` value is cached (see
629    /// previous paragraph), `on_afl` is used to construct a message to be
630    /// emitted via [`EventCx::push`]. The cached value is then cleared to
631    /// avoid sending duplicate messages.
632    #[inline]
633    pub fn 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<ParseGuard<A, T>> {
637        EditBox::new(ParseGuard::new(value_fn, msg_fn))
638    }
639
640    /// Construct an `EditBox` for a parsable value (e.g. a number)
641    ///
642    /// On update, `value_fn` is used to extract a value from input data
643    /// which is then formatted as a string via [`Display`].
644    /// If, however, the input field has focus, the update is ignored.
645    ///
646    /// On every edit, the guard attempts to parse the field's input as type
647    /// `T` via [`FromStr`]. On success, the result is converted to a
648    /// message via `on_afl` then emitted via [`EventCx::push`].
649    pub fn instant_parser<T: Debug + Display + FromStr, M: Debug + 'static>(
650        value_fn: impl Fn(&A) -> T + 'static,
651        msg_fn: impl Fn(T) -> M + 'static,
652    ) -> EditBox<InstantParseGuard<A, T>> {
653        EditBox::new(InstantParseGuard::new(value_fn, msg_fn))
654    }
655}
656
657impl<A: 'static> EditBox<StringGuard<A>> {
658    /// Assign a message function for a `String` value
659    ///
660    /// The `msg_fn` is called when the field is activated (<kbd>Enter</kbd>)
661    /// and when it loses focus after content is changed.
662    ///
663    /// This method sets self as editable (see [`Self::with_editable`]).
664    #[must_use]
665    pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + 'static) -> Self
666    where
667        M: Debug + 'static,
668    {
669        self.inner.guard = self.inner.guard.with_msg(msg_fn);
670        self.inner.editable = true;
671        self
672    }
673}
674
675impl<G: EditGuard> EditBox<G> {
676    /// Set the initial text (inline)
677    ///
678    /// This method should only be used on a new `EditBox`.
679    #[inline]
680    #[must_use]
681    pub fn with_text(mut self, text: impl ToString) -> Self {
682        self.inner = self.inner.with_text(text);
683        self
684    }
685
686    /// Set whether this widget is editable (inline)
687    #[inline]
688    #[must_use]
689    pub fn with_editable(mut self, editable: bool) -> Self {
690        self.inner = self.inner.with_editable(editable);
691        self
692    }
693
694    /// Get whether this `EditField` is editable
695    #[inline]
696    pub fn is_editable(&self) -> bool {
697        self.inner.is_editable()
698    }
699
700    /// Set whether this `EditField` is editable
701    #[inline]
702    pub fn set_editable(&mut self, editable: bool) {
703        self.inner.set_editable(editable);
704    }
705
706    /// Set whether this `EditBox` uses multi-line mode
707    ///
708    /// This setting has two effects: the vertical size allocation is increased
709    /// and wrapping is enabled if true. Default: false.
710    ///
711    /// This method is ineffective if the text class is set by
712    /// [`Self::with_class`] to anything other than [`TextClass::Edit`].
713    #[inline]
714    #[must_use]
715    pub fn with_multi_line(mut self, multi_line: bool) -> Self {
716        self.inner = self.inner.with_multi_line(multi_line);
717        self
718    }
719
720    /// True if the editor uses multi-line mode
721    ///
722    /// See also: [`Self::with_multi_line`]
723    #[inline]
724    pub fn multi_line(&self) -> bool {
725        self.inner.multi_line()
726    }
727
728    /// Set the text class used
729    #[inline]
730    #[must_use]
731    pub fn with_class(mut self, class: TextClass) -> Self {
732        self.inner = self.inner.with_class(class);
733        self
734    }
735
736    /// Get the text class used
737    #[inline]
738    pub fn class(&self) -> TextClass {
739        self.inner.class()
740    }
741
742    /// Adjust the height allocation
743    #[inline]
744    pub fn set_lines(&mut self, min_lines: f32, ideal_lines: f32) {
745        self.inner.set_lines(min_lines, ideal_lines);
746    }
747
748    /// Adjust the height allocation (inline)
749    #[inline]
750    #[must_use]
751    pub fn with_lines(mut self, min_lines: f32, ideal_lines: f32) -> Self {
752        self.set_lines(min_lines, ideal_lines);
753        self
754    }
755
756    /// Adjust the width allocation
757    #[inline]
758    pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
759        self.inner.set_width_em(min_em, ideal_em);
760    }
761
762    /// Adjust the width allocation (inline)
763    #[inline]
764    #[must_use]
765    pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
766        self.set_width_em(min_em, ideal_em);
767        self
768    }
769
770    /// Get whether the widget has edit focus
771    ///
772    /// This is true when the widget is editable and has keyboard focus.
773    #[inline]
774    pub fn has_edit_focus(&self) -> bool {
775        self.inner.has_edit_focus()
776    }
777
778    /// Get whether the input state is erroneous
779    #[inline]
780    pub fn has_error(&self) -> bool {
781        self.inner.has_error()
782    }
783
784    /// Set the error state
785    ///
786    /// When true, the input field's background is drawn red.
787    /// This state is cleared by [`Self::set_string`].
788    pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) {
789        self.inner.set_error_state(cx, error_state);
790    }
791}
792
793/// Used to track ongoing incompatible actions
794#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
795enum CurrentAction {
796    #[default]
797    None,
798    DragSelect,
799    ImeStart,
800    ImeEdit,
801}
802
803impl CurrentAction {
804    fn is_select(self) -> bool {
805        matches!(self, CurrentAction::DragSelect)
806    }
807
808    fn is_ime(self) -> bool {
809        matches!(self, CurrentAction::ImeStart | CurrentAction::ImeEdit)
810    }
811
812    fn is_active_ime(self) -> bool {
813        false
814    }
815
816    fn clear_active(&mut self) {
817        if matches!(self, CurrentAction::DragSelect | CurrentAction::ImeEdit) {
818            *self = CurrentAction::None;
819        }
820    }
821
822    fn clear_selection(&mut self) {
823        if matches!(self, CurrentAction::DragSelect) {
824            *self = CurrentAction::None;
825        }
826    }
827}
828
829#[impl_self]
830mod EditField {
831    /// A text-edit field (single- or multi-line)
832    ///
833    /// The [`EditBox`] widget should be preferred in most cases; this widget
834    /// is a component of `EditBox` and has some special behaviour.
835    ///
836    /// By default, the editor supports a single-line only;
837    /// [`Self::with_multi_line`] and [`Self::with_class`] can be used to change this.
838    ///
839    /// ### Event handling
840    ///
841    /// This widget attempts to handle all standard text-editor input and scroll
842    /// events.
843    ///
844    /// Key events for moving the edit cursor (e.g. arrow keys) are consumed
845    /// only if the edit cursor is moved while key events for adjusting or using
846    /// the selection (e.g. `Command::Copy` and `Command::Deselect`)
847    /// are consumed only when a selection exists. In contrast, key events for
848    /// inserting or deleting text are always consumed.
849    ///
850    /// [`Command::Enter`] inserts a line break in multi-line mode, but in
851    /// single-line mode or if the <kbd>Shift</kbd> key is held it is treated
852    /// the same as [`Command::Activate`].
853    ///
854    /// ### Performance and limitations
855    ///
856    /// Text representation is via a single [`String`]. Edit operations are
857    /// `O(n)` where `n` is the length of text (with text layout algorithms
858    /// having greater cost than copying bytes in the backing [`String`]).
859    /// This isn't necessarily *slow*; when run with optimizations the type can
860    /// handle type-setting around 20kB of UTF-8 in under 10ms (with significant
861    /// scope for optimization, given that currently layout is re-run from
862    /// scratch on each key stroke). Regardless, this approach is not designed
863    /// to scale to handle large documents via a single `EditField` widget.
864    ///
865    /// ### Messages
866    ///
867    /// [`SetValueText`] may be used to replace the entire text and
868    /// [`ReplaceSelectedText`] may be used to replace selected text, where
869    /// [`Self::is_editable`]. This triggers the action handlers
870    /// [`EditGuard::edit`] followed by [`EditGuard::activate`].
871    #[autoimpl(Clone, Debug where G: trait)]
872    #[widget]
873    pub struct EditField<G: EditGuard = DefaultGuard<()>> {
874        core: widget_core!(),
875        editable: bool,
876        width: (f32, f32),
877        lines: (f32, f32),
878        text: Text<String>,
879        selection: SelectionHelper,
880        edit_x_coord: Option<f32>,
881        old_state: Option<(String, usize, usize)>,
882        last_edit: LastEdit,
883        has_key_focus: bool,
884        current: CurrentAction,
885        error_state: bool,
886        input_handler: TextInput,
887        /// The associated [`EditGuard`] implementation
888        pub guard: G,
889    }
890
891    impl Layout for Self {
892        #[inline]
893        fn rect(&self) -> Rect {
894            self.text.rect()
895        }
896
897        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
898            let (min, mut ideal): (i32, i32);
899            if axis.is_horizontal() {
900                let dpem = sizer.dpem();
901                min = (self.width.0 * dpem).cast_ceil();
902                ideal = (self.width.1 * dpem).cast_ceil();
903            } else {
904                // TODO: line height depends on the font; 1em is not a good
905                // approximation. This code also misses inter-line spacing.
906                let dpem = sizer.dpem();
907                min = (self.lines.0 * dpem).cast_ceil();
908                ideal = (self.lines.1 * dpem).cast_ceil();
909            };
910
911            let rules = self.text.size_rules(sizer.re(), axis);
912            ideal = ideal.max(rules.ideal_size());
913
914            let margins = sizer.text_margins().extract(axis);
915            let stretch = if axis.is_horizontal() || self.multi_line() {
916                Stretch::High
917            } else {
918                Stretch::None
919            };
920            SizeRules::new(min, ideal, margins, stretch)
921        }
922
923        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, mut hints: AlignHints) {
924            hints.vert = Some(if self.multi_line() {
925                Align::Default
926            } else {
927                Align::Center
928            });
929            self.text.set_rect(cx, rect, hints);
930            self.text.ensure_no_left_overhang();
931            if self.current.is_ime() {
932                self.set_ime_cursor_area(cx);
933            }
934        }
935
936        fn draw(&self, draw: DrawCx) {
937            self.draw_with_offset(draw, self.rect(), Offset::ZERO);
938        }
939    }
940
941    impl Tile for Self {
942        fn navigable(&self) -> bool {
943            true
944        }
945
946        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
947            Role::TextInput {
948                text: self.text.as_str(),
949                multi_line: self.multi_line(),
950                cursor: self.selection.edit_index(),
951                sel_index: self.selection.sel_index(),
952            }
953        }
954
955        fn probe(&self, _: Coord) -> Id {
956            self.id()
957        }
958    }
959
960    impl Events for Self {
961        const REDRAW_ON_MOUSE_OVER: bool = true;
962
963        type Data = G::Data;
964
965        #[inline]
966        fn mouse_over_icon(&self) -> Option<CursorIcon> {
967            Some(CursorIcon::Text)
968        }
969
970        fn configure(&mut self, cx: &mut ConfigCx) {
971            cx.text_configure(&mut self.text);
972            G::configure(self, cx);
973        }
974
975        fn update(&mut self, cx: &mut ConfigCx, data: &G::Data) {
976            G::update(self, cx, data);
977        }
978
979        fn handle_event(&mut self, cx: &mut EventCx, data: &G::Data, event: Event) -> IsUsed {
980            match event {
981                Event::NavFocus(source) if source == FocusSource::Key => {
982                    if !self.has_key_focus && !self.current.is_select() {
983                        let ime = Some(ImePurpose::Normal);
984                        cx.request_key_focus(self.id(), ime, source);
985                    }
986                    Used
987                }
988                Event::NavFocus(_) => Used,
989                Event::LostNavFocus => Used,
990                Event::SelFocus(source) => {
991                    // NOTE: sel focus implies key focus since we only request
992                    // the latter. We must set before calling self.set_primary.
993                    self.has_key_focus = true;
994                    if source == FocusSource::Pointer {
995                        self.set_primary(cx);
996                    }
997                    Used
998                }
999                Event::KeyFocus => {
1000                    self.has_key_focus = true;
1001                    self.set_view_offset_from_cursor(cx);
1002                    G::focus_gained(self, cx, data);
1003                    Used
1004                }
1005                Event::ImeFocus => {
1006                    self.current = CurrentAction::ImeStart;
1007                    self.set_ime_cursor_area(cx);
1008                    Used
1009                }
1010                Event::LostImeFocus => {
1011                    if self.current.is_ime() {
1012                        self.current = CurrentAction::None;
1013                    }
1014                    Used
1015                }
1016                Event::LostKeyFocus => {
1017                    self.has_key_focus = false;
1018                    cx.redraw(&self);
1019                    G::focus_lost(self, cx, data);
1020                    Used
1021                }
1022                Event::LostSelFocus => {
1023                    // IME focus without selection focus is impossible, so we can clear all current actions
1024                    self.current = CurrentAction::None;
1025                    self.selection.set_empty();
1026                    cx.redraw(self);
1027                    Used
1028                }
1029                Event::Command(cmd, code) => match self.control_key(cx, data, cmd, code) {
1030                    Ok(r) => r,
1031                    Err(NotReady) => Used,
1032                },
1033                Event::Key(event, false) if event.state == ElementState::Pressed => {
1034                    if let Some(text) = &event.text {
1035                        let used = self.received_text(cx, text);
1036                        G::edit(self, cx, data);
1037                        used
1038                    } else {
1039                        let opt_cmd = cx
1040                            .config()
1041                            .shortcuts()
1042                            .try_match(cx.modifiers(), &event.logical_key);
1043                        if let Some(cmd) = opt_cmd {
1044                            match self.control_key(cx, data, cmd, Some(event.physical_key)) {
1045                                Ok(r) => r,
1046                                Err(NotReady) => Used,
1047                            }
1048                        } else {
1049                            Unused
1050                        }
1051                    }
1052                }
1053                Event::ImePreedit(text, cursor) => {
1054                    if self.current != CurrentAction::ImeEdit {
1055                        if cursor.is_some() {
1056                            self.selection.set_anchor_to_range_start();
1057                            self.current = CurrentAction::ImeEdit;
1058                        } else {
1059                            return Used;
1060                        }
1061                    }
1062
1063                    let range = self.selection.anchor_to_edit_range();
1064                    self.text.replace_range(range.clone(), text);
1065
1066                    if let Some((start, end)) = cursor {
1067                        self.selection.set_sel_index_only(range.start + start);
1068                        self.selection.set_edit_index(range.start + end);
1069                    } else {
1070                        self.selection.set_all(range.start + text.len());
1071                    }
1072                    self.edit_x_coord = None;
1073                    self.prepare_text(cx);
1074                    Used
1075                }
1076                Event::ImeCommit(text) => {
1077                    if self.current != CurrentAction::ImeEdit {
1078                        self.selection.set_anchor_to_range_start();
1079                    }
1080                    self.current = CurrentAction::None;
1081
1082                    let range = self.selection.anchor_to_edit_range();
1083                    self.text.replace_range(range.clone(), text);
1084
1085                    self.selection.set_all(range.start + text.len());
1086                    self.edit_x_coord = None;
1087                    self.prepare_text(cx);
1088                    Used
1089                }
1090                Event::PressStart(press) if press.is_tertiary() => {
1091                    press.grab_click(self.id()).complete(cx)
1092                }
1093                Event::PressEnd { press, .. } if press.is_tertiary() => {
1094                    if let Some(content) = cx.get_primary() {
1095                        self.set_cursor_from_coord(cx, press.coord);
1096                        self.current.clear_selection();
1097                        self.selection.set_empty();
1098                        let index = self.selection.edit_index();
1099                        let range = self.trim_paste(&content);
1100                        let len = range.len();
1101
1102                        self.old_state =
1103                            Some((self.text.clone_string(), index, self.selection.sel_index()));
1104                        self.last_edit = LastEdit::Paste;
1105
1106                        self.text.replace_range(index..index, &content[range]);
1107                        self.selection.set_all(index + len);
1108                        self.edit_x_coord = None;
1109                        self.prepare_text(cx);
1110
1111                        G::edit(self, cx, data);
1112                    }
1113                    Used
1114                }
1115                event => match self.input_handler.handle(cx, self.id(), event) {
1116                    TextInputAction::Used => Used,
1117                    TextInputAction::Unused => Unused,
1118                    TextInputAction::Focus { coord, action }
1119                        if self.current.is_select() || action.anchor =>
1120                    {
1121                        if self.current.is_ime() {
1122                            cx.cancel_ime_focus(self.id());
1123                        }
1124                        self.current = CurrentAction::DragSelect;
1125                        self.set_cursor_from_coord(cx, coord);
1126                        self.selection.action(&self.text, action);
1127
1128                        if self.has_key_focus {
1129                            self.set_primary(cx);
1130                        }
1131                        Used
1132                    }
1133                    TextInputAction::Finish if self.current.is_select() => {
1134                        self.current = CurrentAction::None;
1135                        let ime = Some(ImePurpose::Normal);
1136                        cx.request_key_focus(self.id(), ime, FocusSource::Pointer);
1137                        Used
1138                    }
1139                    _ => Used,
1140                },
1141            }
1142        }
1143
1144        fn handle_messages(&mut self, cx: &mut EventCx, data: &G::Data) {
1145            if !self.editable {
1146                return;
1147            }
1148
1149            if let Some(SetValueText(string)) = cx.try_pop() {
1150                self.set_string(cx, string);
1151                G::edit(self, cx, data);
1152                G::activate(self, cx, data);
1153            } else if let Some(ReplaceSelectedText(text)) = cx.try_pop() {
1154                self.received_text(cx, &text);
1155                G::edit(self, cx, data);
1156                G::activate(self, cx, data);
1157            }
1158        }
1159    }
1160
1161    impl Default for Self
1162    where
1163        G: Default,
1164    {
1165        #[inline]
1166        fn default() -> Self {
1167            EditField::new(G::default())
1168        }
1169    }
1170
1171    impl Self {
1172        /// Construct an `EditBox` with an [`EditGuard`]
1173        #[inline]
1174        pub fn new(guard: G) -> EditField<G> {
1175            EditField {
1176                core: Default::default(),
1177                editable: true,
1178                width: (8.0, 16.0),
1179                lines: (1.0, 1.0),
1180                text: Text::default().with_class(TextClass::Edit(false)),
1181                selection: Default::default(),
1182                edit_x_coord: None,
1183                old_state: None,
1184                last_edit: Default::default(),
1185                has_key_focus: false,
1186                current: CurrentAction::None,
1187                error_state: false,
1188                input_handler: Default::default(),
1189                guard,
1190            }
1191        }
1192
1193        /// Get text contents
1194        #[inline]
1195        pub fn as_str(&self) -> &str {
1196            self.text.as_str()
1197        }
1198
1199        /// Get the text contents as a `String`
1200        #[inline]
1201        pub fn clone_string(&self) -> String {
1202            self.text.clone_string()
1203        }
1204
1205        /// Set text contents from a `str`
1206        ///
1207        /// Returns `true` if the text may have changed.
1208        #[inline]
1209        pub fn set_str(&mut self, cx: &mut EventState, text: &str) -> bool {
1210            if self.text.as_str() != text {
1211                self.set_string(cx, text.to_string());
1212                true
1213            } else {
1214                false
1215            }
1216        }
1217
1218        /// Set text contents from a `String`
1219        ///
1220        /// This method does not call action handlers on the [`EditGuard`].
1221        ///
1222        /// Returns `true` if the text is ready and may have changed.
1223        pub fn set_string(&mut self, cx: &mut EventState, string: String) -> bool {
1224            if !self.text.set_string(string) || !self.text.prepare() {
1225                return false;
1226            }
1227
1228            self.current.clear_active();
1229            self.selection.set_max_len(self.text.str_len());
1230            cx.redraw(&self);
1231            if self.current.is_ime() {
1232                self.set_ime_cursor_area(cx);
1233            }
1234            self.set_error_state(cx, false);
1235            true
1236        }
1237
1238        /// Replace selected text
1239        ///
1240        /// This method does not call action handlers on the [`EditGuard`].
1241        pub fn replace_selection(&mut self, cx: &mut EventCx, text: &str) {
1242            self.received_text(cx, text);
1243        }
1244
1245        // Call only if self.ime_focus
1246        fn set_ime_cursor_area(&self, cx: &mut EventState) {
1247            if let Ok(display) = self.text.display() {
1248                if let Some(mut rect) = self.selection.cursor_rect(display) {
1249                    rect.pos += Offset::conv(self.rect().pos);
1250                    cx.set_ime_cursor_area(self.id_ref(), rect);
1251                }
1252            }
1253        }
1254
1255        /// Get the size of the type-set text
1256        ///
1257        /// `EditField` ensures text has no left or top overhang.
1258        #[inline]
1259        pub fn typeset_size(&self) -> Size {
1260            let mut size = self.rect().size;
1261            if let Ok((tl, br)) = self.text.bounding_box() {
1262                size.1 = size.1.max((br.1 - tl.1).cast_ceil());
1263                size.0 = size.0.max((br.0 - tl.0).cast_ceil());
1264            }
1265            size
1266        }
1267
1268        /// Draw with an offset
1269        ///
1270        /// Draws at position `self.rect() - offset`.
1271        ///
1272        /// This may be called instead of [`Layout::draw`].
1273        pub fn draw_with_offset(&self, mut draw: DrawCx, rect: Rect, offset: Offset) {
1274            let pos = self.rect().pos - offset;
1275
1276            draw.text_selected(pos, rect, &self.text, self.selection.range());
1277
1278            if self.editable && draw.ev_state().has_key_focus(self.id_ref()).0 {
1279                draw.text_cursor(pos, rect, &self.text, self.selection.edit_index());
1280            }
1281        }
1282    }
1283}
1284
1285impl<A: 'static> EditField<DefaultGuard<A>> {
1286    /// Construct an `EditField` with the given inital `text` (no event handling)
1287    #[inline]
1288    pub fn text<S: ToString>(text: S) -> Self {
1289        let text = text.to_string();
1290        let len = text.len();
1291        EditField {
1292            editable: true,
1293            text: Text::new(text, TextClass::Edit(false)),
1294            selection: SelectionHelper::new(len, len),
1295            ..Default::default()
1296        }
1297    }
1298
1299    /// Construct a read-only `EditField` displaying some `String` value
1300    #[inline]
1301    pub fn string(value_fn: impl Fn(&A) -> String + 'static) -> EditField<StringGuard<A>> {
1302        EditField::new(StringGuard::new(value_fn)).with_editable(false)
1303    }
1304
1305    /// Construct an `EditField` for a parsable value (e.g. a number)
1306    ///
1307    /// On update, `value_fn` is used to extract a value from input data
1308    /// which is then formatted as a string via [`Display`].
1309    /// If, however, the input field has focus, the update is ignored.
1310    ///
1311    /// On every edit, the guard attempts to parse the field's input as type
1312    /// `T` via [`FromStr`], caching the result and setting the error state.
1313    ///
1314    /// On field activation and focus loss when a `T` value is cached (see
1315    /// previous paragraph), `on_afl` is used to construct a message to be
1316    /// emitted via [`EventCx::push`]. The cached value is then cleared to
1317    /// avoid sending duplicate messages.
1318    #[inline]
1319    pub fn parser<T: Debug + Display + FromStr, M: Debug + 'static>(
1320        value_fn: impl Fn(&A) -> T + 'static,
1321        msg_fn: impl Fn(T) -> M + 'static,
1322    ) -> EditField<ParseGuard<A, T>> {
1323        EditField::new(ParseGuard::new(value_fn, msg_fn))
1324    }
1325
1326    /// Construct an `EditField` for a parsable value (e.g. a number)
1327    ///
1328    /// On update, `value_fn` is used to extract a value from input data
1329    /// which is then formatted as a string via [`Display`].
1330    /// If, however, the input field has focus, the update is ignored.
1331    ///
1332    /// On every edit, the guard attempts to parse the field's input as type
1333    /// `T` via [`FromStr`]. On success, the result is converted to a
1334    /// message via `on_afl` then emitted via [`EventCx::push`].
1335    pub fn instant_parser<T: Debug + Display + FromStr, M: Debug + 'static>(
1336        value_fn: impl Fn(&A) -> T + 'static,
1337        msg_fn: impl Fn(T) -> M + 'static,
1338    ) -> EditField<InstantParseGuard<A, T>> {
1339        EditField::new(InstantParseGuard::new(value_fn, msg_fn))
1340    }
1341}
1342
1343impl<A: 'static> EditField<StringGuard<A>> {
1344    /// Assign a message function for a `String` value
1345    ///
1346    /// The `msg_fn` is called when the field is activated (<kbd>Enter</kbd>)
1347    /// and when it loses focus after content is changed.
1348    ///
1349    /// This method sets self as editable (see [`Self::with_editable`]).
1350    #[must_use]
1351    pub fn with_msg<M>(mut self, msg_fn: impl Fn(&str) -> M + 'static) -> Self
1352    where
1353        M: Debug + 'static,
1354    {
1355        self.guard = self.guard.with_msg(msg_fn);
1356        self.editable = true;
1357        self
1358    }
1359}
1360
1361impl<G: EditGuard> EditField<G> {
1362    /// Set the initial text (inline)
1363    ///
1364    /// This method should only be used on a new `EditField`.
1365    #[inline]
1366    #[must_use]
1367    pub fn with_text(mut self, text: impl ToString) -> Self {
1368        debug_assert!(self.current == CurrentAction::None);
1369        let text = text.to_string();
1370        let len = text.len();
1371        self.text.set_string(text);
1372        self.selection.set_all(len);
1373        self
1374    }
1375
1376    /// Set whether this `EditField` is editable (inline)
1377    #[inline]
1378    #[must_use]
1379    pub fn with_editable(mut self, editable: bool) -> Self {
1380        self.editable = editable;
1381        self
1382    }
1383
1384    /// Get whether this `EditField` is editable
1385    #[inline]
1386    pub fn is_editable(&self) -> bool {
1387        self.editable
1388    }
1389
1390    /// Set whether this `EditField` is editable
1391    #[inline]
1392    pub fn set_editable(&mut self, editable: bool) {
1393        self.editable = editable;
1394    }
1395
1396    /// Set whether this `EditField` uses multi-line mode
1397    ///
1398    /// This method does two things:
1399    ///
1400    /// -   Changes the text class (see [`Self::with_class`])
1401    /// -   Changes the vertical height allocation (see [`Self::with_lines`])
1402    #[inline]
1403    #[must_use]
1404    pub fn with_multi_line(mut self, multi_line: bool) -> Self {
1405        self.text.set_class(TextClass::Edit(multi_line));
1406        self.lines = match multi_line {
1407            false => (1.0, 1.0),
1408            true => (4.0, 7.0),
1409        };
1410        self
1411    }
1412
1413    /// True if the editor uses multi-line mode
1414    ///
1415    /// See also: [`Self::with_multi_line`]
1416    #[inline]
1417    pub fn multi_line(&self) -> bool {
1418        self.class().multi_line()
1419    }
1420
1421    /// Set the text class used
1422    #[inline]
1423    #[must_use]
1424    pub fn with_class(mut self, class: TextClass) -> Self {
1425        self.text.set_class(class);
1426        self
1427    }
1428
1429    /// Get the text class used
1430    #[inline]
1431    pub fn class(&self) -> TextClass {
1432        self.text.class()
1433    }
1434
1435    /// Adjust the height allocation
1436    #[inline]
1437    pub fn set_lines(&mut self, min_lines: f32, ideal_lines: f32) {
1438        self.lines = (min_lines, ideal_lines);
1439    }
1440
1441    /// Adjust the height allocation (inline)
1442    #[inline]
1443    #[must_use]
1444    pub fn with_lines(mut self, min_lines: f32, ideal_lines: f32) -> Self {
1445        self.set_lines(min_lines, ideal_lines);
1446        self
1447    }
1448
1449    /// Adjust the width allocation
1450    #[inline]
1451    pub fn set_width_em(&mut self, min_em: f32, ideal_em: f32) {
1452        self.width = (min_em, ideal_em);
1453    }
1454
1455    /// Adjust the width allocation (inline)
1456    #[inline]
1457    #[must_use]
1458    pub fn with_width_em(mut self, min_em: f32, ideal_em: f32) -> Self {
1459        self.set_width_em(min_em, ideal_em);
1460        self
1461    }
1462
1463    /// Get whether the widget has edit focus
1464    ///
1465    /// This is true when the widget is editable and has keyboard focus.
1466    #[inline]
1467    pub fn has_edit_focus(&self) -> bool {
1468        self.editable && self.has_key_focus
1469    }
1470
1471    /// Get whether the input state is erroneous
1472    #[inline]
1473    pub fn has_error(&self) -> bool {
1474        self.error_state
1475    }
1476
1477    /// Set the error state
1478    ///
1479    /// When true, the input field's background is drawn red.
1480    /// This state is cleared by [`Self::set_string`].
1481    // TODO: possibly change type to Option<String> and display the error
1482    pub fn set_error_state(&mut self, cx: &mut EventState, error_state: bool) {
1483        self.error_state = error_state;
1484        cx.redraw(self);
1485    }
1486
1487    fn prepare_text(&mut self, cx: &mut EventCx) {
1488        if self.text.prepare() {
1489            self.text.ensure_no_left_overhang();
1490            cx.redraw(&self);
1491        }
1492
1493        self.set_view_offset_from_cursor(cx);
1494    }
1495
1496    fn trim_paste(&self, text: &str) -> Range<usize> {
1497        let mut end = text.len();
1498        if !self.multi_line() {
1499            // We cut the content short on control characters and
1500            // ignore them (preventing line-breaks and ignoring any
1501            // actions such as recursive-paste).
1502            for (i, c) in text.char_indices() {
1503                if c < '\u{20}' || ('\u{7f}'..='\u{9f}').contains(&c) {
1504                    end = i;
1505                    break;
1506                }
1507            }
1508        }
1509        0..end
1510    }
1511
1512    fn received_text(&mut self, cx: &mut EventCx, text: &str) -> IsUsed {
1513        if !self.editable || self.current.is_active_ime() {
1514            return Unused;
1515        }
1516
1517        self.current.clear_selection();
1518        let index = self.selection.edit_index();
1519        let selection = self.selection.range();
1520        let have_sel = selection.start < selection.end;
1521        if self.last_edit != LastEdit::Insert || have_sel {
1522            self.old_state = Some((self.text.clone_string(), index, self.selection.sel_index()));
1523            self.last_edit = LastEdit::Insert;
1524        }
1525        if have_sel {
1526            self.text.replace_range(selection.clone(), text);
1527            self.selection.set_all(selection.start + text.len());
1528        } else {
1529            // TODO(kas-text) support the following:
1530            // self.text.insert_str(index, text);
1531            let mut s = self.text.clone_string();
1532            s.insert_str(index, text);
1533            self.text.set_text(s);
1534            // END workaround
1535            self.selection.set_all(index + text.len());
1536        }
1537        self.edit_x_coord = None;
1538
1539        self.prepare_text(cx);
1540        Used
1541    }
1542
1543    fn control_key(
1544        &mut self,
1545        cx: &mut EventCx,
1546        data: &G::Data,
1547        cmd: Command,
1548        code: Option<PhysicalKey>,
1549    ) -> Result<IsUsed, NotReady> {
1550        let editable = self.editable;
1551        let mut shift = cx.modifiers().shift_key();
1552        let mut buf = [0u8; 4];
1553        let cursor = self.selection.edit_index();
1554        let len = self.text.str_len();
1555        let multi_line = self.multi_line();
1556        let selection = self.selection.range();
1557        let have_sel = selection.end > selection.start;
1558        let string;
1559
1560        enum Action<'a> {
1561            None,
1562            Activate,
1563            Edit,
1564            Insert(&'a str, LastEdit),
1565            Delete(Range<usize>),
1566            Move(usize, Option<f32>),
1567        }
1568
1569        let action = match cmd {
1570            Command::Escape | Command::Deselect
1571                if !self.current.is_active_ime() && !selection.is_empty() =>
1572            {
1573                self.current.clear_selection();
1574                self.selection.set_empty();
1575                cx.redraw(&self);
1576                Action::None
1577            }
1578            Command::Activate => Action::Activate,
1579            Command::Enter if shift || !multi_line => Action::Activate,
1580            Command::Enter if editable && multi_line => {
1581                Action::Insert('\n'.encode_utf8(&mut buf), LastEdit::Insert)
1582            }
1583            // NOTE: we might choose to optionally handle Tab in the future,
1584            // but without some workaround it prevents keyboard navigation.
1585            // Command::Tab => Action::Insert('\t'.encode_utf8(&mut buf), LastEdit::Insert),
1586            Command::Left | Command::Home if !shift && have_sel => {
1587                Action::Move(selection.start, None)
1588            }
1589            Command::Left if cursor > 0 => {
1590                let mut cursor = GraphemeCursor::new(cursor, len, true);
1591                cursor
1592                    .prev_boundary(self.text.text(), 0)
1593                    .unwrap()
1594                    .map(|index| Action::Move(index, None))
1595                    .unwrap_or(Action::None)
1596            }
1597            Command::Right | Command::End if !shift && have_sel => {
1598                Action::Move(selection.end, None)
1599            }
1600            Command::Right if cursor < len => {
1601                let mut cursor = GraphemeCursor::new(cursor, len, true);
1602                cursor
1603                    .next_boundary(self.text.text(), 0)
1604                    .unwrap()
1605                    .map(|index| Action::Move(index, None))
1606                    .unwrap_or(Action::None)
1607            }
1608            Command::WordLeft if cursor > 0 => {
1609                let mut iter = self.text.text()[0..cursor].split_word_bound_indices();
1610                let mut p = iter.next_back().map(|(index, _)| index).unwrap_or(0);
1611                while self.text.text()[p..]
1612                    .chars()
1613                    .next()
1614                    .map(|c| c.is_whitespace())
1615                    .unwrap_or(false)
1616                {
1617                    if let Some((index, _)) = iter.next_back() {
1618                        p = index;
1619                    } else {
1620                        break;
1621                    }
1622                }
1623                Action::Move(p, None)
1624            }
1625            Command::WordRight if cursor < len => {
1626                let mut iter = self.text.text()[cursor..]
1627                    .split_word_bound_indices()
1628                    .skip(1);
1629                let mut p = iter.next().map(|(index, _)| cursor + index).unwrap_or(len);
1630                while self.text.text()[p..]
1631                    .chars()
1632                    .next()
1633                    .map(|c| c.is_whitespace())
1634                    .unwrap_or(false)
1635                {
1636                    if let Some((index, _)) = iter.next() {
1637                        p = cursor + index;
1638                    } else {
1639                        break;
1640                    }
1641                }
1642                Action::Move(p, None)
1643            }
1644            // Avoid use of unused navigation keys (e.g. by ScrollComponent):
1645            Command::Left | Command::Right | Command::WordLeft | Command::WordRight => Action::None,
1646            Command::Up | Command::Down if multi_line => {
1647                let x = match self.edit_x_coord {
1648                    Some(x) => x,
1649                    None => self
1650                        .text
1651                        .text_glyph_pos(cursor)?
1652                        .next_back()
1653                        .map(|r| r.pos.0)
1654                        .unwrap_or(0.0),
1655                };
1656                let mut line = self.text.find_line(cursor)?.map(|r| r.0).unwrap_or(0);
1657                // We can tolerate invalid line numbers here!
1658                line = match cmd {
1659                    Command::Up => line.wrapping_sub(1),
1660                    Command::Down => line.wrapping_add(1),
1661                    _ => unreachable!(),
1662                };
1663                const HALF: usize = usize::MAX / 2;
1664                let nearest_end = match line {
1665                    0..=HALF => len,
1666                    _ => 0,
1667                };
1668                self.text
1669                    .line_index_nearest(line, x)?
1670                    .map(|index| Action::Move(index, Some(x)))
1671                    .unwrap_or(Action::Move(nearest_end, None))
1672            }
1673            Command::Home if cursor > 0 => {
1674                let index = self.text.find_line(cursor)?.map(|r| r.1.start).unwrap_or(0);
1675                Action::Move(index, None)
1676            }
1677            Command::End if cursor < len => {
1678                let index = self.text.find_line(cursor)?.map(|r| r.1.end).unwrap_or(len);
1679                Action::Move(index, None)
1680            }
1681            Command::DocHome if cursor > 0 => Action::Move(0, None),
1682            Command::DocEnd if cursor < len => Action::Move(len, None),
1683            // Avoid use of unused navigation keys (e.g. by ScrollComponent):
1684            Command::Home | Command::End | Command::DocHome | Command::DocEnd => Action::None,
1685            Command::PageUp | Command::PageDown if multi_line => {
1686                let mut v = self
1687                    .text
1688                    .text_glyph_pos(cursor)?
1689                    .next_back()
1690                    .map(|r| r.pos.into())
1691                    .unwrap_or(Vec2::ZERO);
1692                if let Some(x) = self.edit_x_coord {
1693                    v.0 = x;
1694                }
1695                const FACTOR: f32 = 2.0 / 3.0;
1696                let mut h_dist = f32::conv(self.text.rect().size.1) * FACTOR;
1697                if cmd == Command::PageUp {
1698                    h_dist *= -1.0;
1699                }
1700                v.1 += h_dist;
1701                Action::Move(self.text.text_index_nearest(v)?, Some(v.0))
1702            }
1703            Command::Delete | Command::DelBack if editable && have_sel => {
1704                Action::Delete(selection.clone())
1705            }
1706            Command::Delete if editable => GraphemeCursor::new(cursor, len, true)
1707                .next_boundary(self.text.text(), 0)
1708                .unwrap()
1709                .map(|next| Action::Delete(cursor..next))
1710                .unwrap_or(Action::None),
1711            Command::DelBack if editable => {
1712                // We always delete one code-point, not one grapheme cluster:
1713                let prev = self.text.text()[0..cursor]
1714                    .char_indices()
1715                    .next_back()
1716                    .map(|(i, _)| i)
1717                    .unwrap_or(0);
1718                Action::Delete(prev..cursor)
1719            }
1720            Command::DelWord if editable => {
1721                let next = self.text.text()[cursor..]
1722                    .split_word_bound_indices()
1723                    .nth(1)
1724                    .map(|(index, _)| cursor + index)
1725                    .unwrap_or(len);
1726                Action::Delete(cursor..next)
1727            }
1728            Command::DelWordBack if editable => {
1729                let prev = self.text.text()[0..cursor]
1730                    .split_word_bound_indices()
1731                    .next_back()
1732                    .map(|(index, _)| index)
1733                    .unwrap_or(0);
1734                Action::Delete(prev..cursor)
1735            }
1736            Command::SelectAll => {
1737                self.selection.set_sel_index(0);
1738                shift = true; // hack
1739                Action::Move(len, None)
1740            }
1741            Command::Cut if editable && have_sel => {
1742                cx.set_clipboard((self.text.text()[selection.clone()]).into());
1743                Action::Delete(selection.clone())
1744            }
1745            Command::Copy if have_sel => {
1746                cx.set_clipboard((self.text.text()[selection.clone()]).into());
1747                Action::None
1748            }
1749            Command::Paste if editable => {
1750                if let Some(content) = cx.get_clipboard() {
1751                    let range = self.trim_paste(&content);
1752                    string = content;
1753                    Action::Insert(&string[range], LastEdit::Paste)
1754                } else {
1755                    Action::None
1756                }
1757            }
1758            Command::Undo | Command::Redo if editable => {
1759                // TODO: maintain full edit history (externally?)
1760                if let Some((state, c2, sel)) = self.old_state.as_mut() {
1761                    self.text.swap_string(state);
1762                    self.selection.set_edit_index(*c2);
1763                    *c2 = cursor;
1764                    let index = *sel;
1765                    *sel = self.selection.sel_index();
1766                    self.selection.set_sel_index(index);
1767                    self.edit_x_coord = None;
1768                    self.last_edit = LastEdit::None;
1769                }
1770                Action::Edit
1771            }
1772            _ => return Ok(Unused),
1773        };
1774
1775        if !self.has_key_focus {
1776            // This can happen if we still had selection focus, then received
1777            // e.g. Command::Copy.
1778            let ime = Some(ImePurpose::Normal);
1779            cx.request_key_focus(self.id(), ime, FocusSource::Synthetic);
1780        }
1781
1782        if !matches!(action, Action::None) {
1783            self.current = CurrentAction::None;
1784        }
1785
1786        let result = match action {
1787            Action::None => EditAction::None,
1788            Action::Activate => EditAction::Activate,
1789            Action::Edit => EditAction::Edit,
1790            Action::Insert(s, edit) => {
1791                let mut index = cursor;
1792                if have_sel {
1793                    self.old_state =
1794                        Some((self.text.clone_string(), index, self.selection.sel_index()));
1795                    self.last_edit = edit;
1796
1797                    self.text.replace_range(selection.clone(), s);
1798                    index = selection.start;
1799                } else {
1800                    if self.last_edit != edit {
1801                        self.old_state =
1802                            Some((self.text.clone_string(), index, self.selection.sel_index()));
1803                        self.last_edit = edit;
1804                    }
1805
1806                    self.text.replace_range(index..index, s);
1807                }
1808                self.selection.set_all(index + s.len());
1809                self.edit_x_coord = None;
1810                EditAction::Edit
1811            }
1812            Action::Delete(sel) => {
1813                if self.last_edit != LastEdit::Delete {
1814                    self.old_state =
1815                        Some((self.text.clone_string(), cursor, self.selection.sel_index()));
1816                    self.last_edit = LastEdit::Delete;
1817                }
1818
1819                self.text.replace_range(sel.clone(), "");
1820                self.selection.set_all(sel.start);
1821                self.edit_x_coord = None;
1822                EditAction::Edit
1823            }
1824            Action::Move(index, x_coord) => {
1825                self.selection.set_edit_index(index);
1826                if !shift {
1827                    self.selection.set_empty();
1828                } else {
1829                    self.set_primary(cx);
1830                }
1831                self.edit_x_coord = x_coord;
1832                cx.redraw(&self);
1833                EditAction::None
1834            }
1835        };
1836
1837        self.prepare_text(cx);
1838
1839        Ok(match result {
1840            EditAction::None => Used,
1841            EditAction::Activate => {
1842                cx.depress_with_key(&self, code);
1843                G::activate(self, cx, data)
1844            }
1845            EditAction::Edit => {
1846                G::edit(self, cx, data);
1847                Used
1848            }
1849        })
1850    }
1851
1852    fn set_cursor_from_coord(&mut self, cx: &mut EventCx, coord: Coord) {
1853        let rel_pos = (coord - self.rect().pos).cast();
1854        if let Ok(index) = self.text.text_index_nearest(rel_pos) {
1855            if index != self.selection.edit_index() {
1856                self.selection.set_edit_index(index);
1857                self.set_view_offset_from_cursor(cx);
1858                self.edit_x_coord = None;
1859                cx.redraw(self);
1860            }
1861        }
1862    }
1863
1864    fn set_primary(&self, cx: &mut EventCx) {
1865        if self.has_key_focus && !self.selection.is_empty() && cx.has_primary() {
1866            let range = self.selection.range();
1867            cx.set_primary(String::from(&self.text.as_str()[range]));
1868        }
1869    }
1870
1871    /// Update view_offset after the cursor index changes
1872    ///
1873    /// A redraw is assumed since the cursor moved.
1874    fn set_view_offset_from_cursor(&mut self, cx: &mut EventCx) {
1875        let cursor = self.selection.edit_index();
1876        if let Some(marker) = self
1877            .text
1878            .text_glyph_pos(cursor)
1879            .ok()
1880            .and_then(|mut m| m.next_back())
1881        {
1882            let y0 = (marker.pos.1 - marker.ascent).cast_floor();
1883            let pos = self.rect().pos + Offset(marker.pos.0.cast_nearest(), y0);
1884            let size = Size(0, i32::conv_ceil(marker.pos.1 - marker.descent) - y0);
1885            cx.set_scroll(Scroll::Rect(Rect { pos, size }));
1886        }
1887    }
1888}