Skip to main content

freya_components/
input.rs

1use std::{
2    borrow::Cow,
3    cell::{
4        Ref,
5        RefCell,
6    },
7    rc::Rc,
8};
9
10use freya_core::{
11    elements::paragraph::{
12        ParagraphCursorExt,
13        ParagraphHolderInner,
14    },
15    prelude::*,
16};
17use freya_edit::*;
18use torin::{
19    gaps::Gaps,
20    prelude::{
21        Alignment,
22        Area,
23        Content,
24        Direction,
25        Point2D,
26        Vector2D,
27    },
28    size::Size,
29};
30use tracing::warn;
31
32use crate::{
33    cursor_blink::use_cursor_blink,
34    define_theme,
35    get_theme,
36    scrollviews::{
37        ScrollConfig,
38        ScrollView,
39        use_scroll_controller,
40    },
41};
42
43define_theme! {
44    for = Input;
45    theme_field = theme_layout;
46
47    %[component]
48    pub InputLayout {
49        %[fields]
50        corner_radius: CornerRadius,
51        padding: Gaps,
52    }
53}
54
55define_theme! {
56    for = Input;
57    theme_field = theme_colors;
58
59    %[component]
60    pub InputColors {
61        %[fields]
62        background: Color,
63        focus_background: Color,
64        border_fill: Color,
65        focus_border_fill: Color,
66        color: Color,
67        placeholder_color: Color,
68    }
69}
70
71#[derive(Clone, PartialEq)]
72pub enum InputStyleVariant {
73    Normal,
74    Filled,
75    Flat,
76}
77
78#[derive(Clone, PartialEq)]
79pub enum InputLayoutVariant {
80    Normal,
81    Compact,
82    Expanded,
83}
84
85#[derive(Default, Clone, Copy, PartialEq)]
86pub enum InputMode {
87    #[default]
88    Shown,
89    Hidden(char),
90}
91
92impl InputMode {
93    pub fn new_password() -> Self {
94        Self::Hidden('*')
95    }
96}
97
98#[derive(Debug, Default, PartialEq, Clone, Copy)]
99pub enum InputStatus {
100    /// Default state.
101    #[default]
102    Idle,
103    /// Pointer is hovering the input.
104    Hovering,
105}
106
107#[derive(Clone)]
108pub struct InputValidator {
109    valid: Rc<RefCell<bool>>,
110    text: Rc<RefCell<String>>,
111}
112
113impl InputValidator {
114    pub fn new(text: String) -> Self {
115        Self {
116            valid: Rc::new(RefCell::new(true)),
117            text: Rc::new(RefCell::new(text)),
118        }
119    }
120    pub fn text(&'_ self) -> Ref<'_, String> {
121        self.text.borrow()
122    }
123    pub fn set_valid(&self, is_valid: bool) {
124        *self.valid.borrow_mut() = is_valid;
125    }
126    pub fn is_valid(&self) -> bool {
127        *self.valid.borrow()
128    }
129}
130
131/// Small box to write some text.
132///
133/// ## **Normal**
134///
135/// ```rust
136/// # use freya::prelude::*;
137/// fn app() -> impl IntoElement {
138///     let value = use_state(String::new);
139///     Input::new(value).placeholder("Type here")
140/// }
141/// ```
142/// ## **Filled**
143///
144/// ```rust
145/// # use freya::prelude::*;
146/// fn app() -> impl IntoElement {
147///     let value = use_state(String::new);
148///     Input::new(value).placeholder("Type here").filled()
149/// }
150/// ```
151/// ## **Flat**
152///
153/// ```rust
154/// # use freya::prelude::*;
155/// fn app() -> impl IntoElement {
156///     let value = use_state(String::new);
157///     Input::new(value).placeholder("Type here").flat()
158/// }
159/// ```
160///
161/// See the [interactive components demo](https://freyaui.dev/demo).
162#[derive(Clone, PartialEq)]
163pub struct Input {
164    pub(crate) theme_colors: Option<InputColorsThemePartial>,
165    pub(crate) theme_layout: Option<InputLayoutThemePartial>,
166    value: WritableSetter<String>,
167    placeholder: Option<Cow<'static, str>>,
168    on_validate: Option<EventHandler<InputValidator>>,
169    on_submit: Option<EventHandler<String>>,
170    mode: InputMode,
171    auto_focus: bool,
172    width: Size,
173    height: Size,
174    multiline: bool,
175    enabled: bool,
176    key: DiffKey,
177    style_variant: InputStyleVariant,
178    layout_variant: InputLayoutVariant,
179    text_align: TextAlign,
180    a11y_id: Option<AccessibilityId>,
181    leading: Option<Element>,
182    trailing: Option<Element>,
183    on_pre_key_down: Option<Callback<Event<KeyboardEventData>, bool>>,
184}
185
186impl KeyExt for Input {
187    fn key_mut(&mut self) -> &mut DiffKey {
188        &mut self.key
189    }
190}
191
192impl Input {
193    pub fn new(value: impl Into<WritableSetter<String>>) -> Self {
194        Input {
195            theme_colors: None,
196            theme_layout: None,
197            value: value.into(),
198            placeholder: None,
199            on_validate: None,
200            on_submit: None,
201            mode: InputMode::default(),
202            auto_focus: false,
203            width: Size::px(150.),
204            height: Size::default(),
205            multiline: false,
206            enabled: true,
207            key: DiffKey::default(),
208            style_variant: InputStyleVariant::Normal,
209            layout_variant: InputLayoutVariant::Normal,
210            text_align: TextAlign::default(),
211            a11y_id: None,
212            leading: None,
213            trailing: None,
214            on_pre_key_down: None,
215        }
216    }
217
218    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
219        self.enabled = enabled.into();
220        self
221    }
222
223    pub fn placeholder(mut self, placeholder: impl Into<Cow<'static, str>>) -> Self {
224        self.placeholder = Some(placeholder.into());
225        self
226    }
227
228    pub fn on_validate(mut self, on_validate: impl Into<EventHandler<InputValidator>>) -> Self {
229        self.on_validate = Some(on_validate.into());
230        self
231    }
232
233    pub fn on_submit(mut self, on_submit: impl Into<EventHandler<String>>) -> Self {
234        self.on_submit = Some(on_submit.into());
235        self
236    }
237
238    pub fn mode(mut self, mode: InputMode) -> Self {
239        self.mode = mode;
240        self
241    }
242
243    pub fn auto_focus(mut self, auto_focus: impl Into<bool>) -> Self {
244        self.auto_focus = auto_focus.into();
245        self
246    }
247
248    pub fn width(mut self, width: impl Into<Size>) -> Self {
249        self.width = width.into();
250        self
251    }
252
253    pub fn height(mut self, height: impl Into<Size>) -> Self {
254        self.height = height.into();
255        self
256    }
257
258    /// Allow writing multiple lines, `Enter` inserts a line break instead of submitting.
259    /// Combine it with [Self::height] to get a scrollable input of a bounded size.
260    pub fn multiline(mut self, multiline: impl Into<bool>) -> Self {
261        self.multiline = multiline.into();
262        self
263    }
264
265    pub fn theme_colors(mut self, theme: InputColorsThemePartial) -> Self {
266        self.theme_colors = Some(theme);
267        self
268    }
269
270    pub fn theme_layout(mut self, theme: InputLayoutThemePartial) -> Self {
271        self.theme_layout = Some(theme);
272        self
273    }
274
275    pub fn text_align(mut self, text_align: impl Into<TextAlign>) -> Self {
276        self.text_align = text_align.into();
277        self
278    }
279
280    pub fn style_variant(mut self, style_variant: impl Into<InputStyleVariant>) -> Self {
281        self.style_variant = style_variant.into();
282        self
283    }
284
285    pub fn layout_variant(mut self, layout_variant: impl Into<InputLayoutVariant>) -> Self {
286        self.layout_variant = layout_variant.into();
287        self
288    }
289
290    /// Shortcut for [Self::style_variant] with [InputStyleVariant::Filled].
291    pub fn filled(self) -> Self {
292        self.style_variant(InputStyleVariant::Filled)
293    }
294
295    /// Shortcut for [Self::style_variant] with [InputStyleVariant::Flat].
296    pub fn flat(self) -> Self {
297        self.style_variant(InputStyleVariant::Flat)
298    }
299
300    /// Shortcut for [Self::layout_variant] with [InputLayoutVariant::Compact].
301    pub fn compact(self) -> Self {
302        self.layout_variant(InputLayoutVariant::Compact)
303    }
304
305    /// Shortcut for [Self::layout_variant] with [InputLayoutVariant::Expanded].
306    pub fn expanded(self) -> Self {
307        self.layout_variant(InputLayoutVariant::Expanded)
308    }
309
310    pub fn a11y_id(mut self, a11y_id: impl Into<AccessibilityId>) -> Self {
311        self.a11y_id = Some(a11y_id.into());
312        self
313    }
314
315    /// Optional element rendered before the text input.
316    pub fn leading(mut self, leading: impl Into<Element>) -> Self {
317        self.leading = Some(leading.into());
318        self
319    }
320
321    /// Optional element rendered after the text input.
322    pub fn trailing(mut self, trailing: impl Into<Element>) -> Self {
323        self.trailing = Some(trailing.into());
324        self
325    }
326
327    /// Sets a pre-handler called for each key event. Return `true` to let the input process it,
328    /// `false` to skip. The callback may call `stop_propagation()` / `prevent_default()` directly.
329    pub fn on_pre_key_down(
330        mut self,
331        on_pre_key_down: impl Into<Callback<Event<KeyboardEventData>, bool>>,
332    ) -> Self {
333        self.on_pre_key_down = Some(on_pre_key_down.into());
334        self
335    }
336}
337
338impl CornerRadiusExt for Input {
339    fn with_corner_radius(self, corner_radius: f32) -> Self {
340        self.corner_radius(corner_radius)
341    }
342}
343
344impl Component for Input {
345    fn render(&self) -> impl IntoElement {
346        let a11y_id = use_hook(|| self.a11y_id.unwrap_or_else(AccessibilityId::new_unique));
347        let focus = use_focus(a11y_id);
348        let holder = use_state(ParagraphHolder::default);
349        let mut area = use_state(Area::default);
350        let mut viewport_area = use_state(Area::default);
351        let mut scroll_controller = use_scroll_controller(ScrollConfig::default);
352        let mut status = use_state(InputStatus::default);
353        let is_masked = matches!(self.mode, InputMode::Hidden(_));
354        let mut editable = use_editable(
355            || self.value.read().to_string(),
356            move || {
357                EditableConfig::new()
358                    .with_allow_write_clipboard(!is_masked)
359                    .with_select_all_on_double_click(is_masked)
360            },
361        );
362        let mut is_dragging = use_state(|| false);
363        let mut value = self.value.clone();
364
365        let theme_colors = match self.style_variant {
366            InputStyleVariant::Normal => {
367                get_theme!(&self.theme_colors, InputColorsThemePreference, "input")
368            }
369            InputStyleVariant::Filled => get_theme!(
370                &self.theme_colors,
371                InputColorsThemePreference,
372                "filled_input"
373            ),
374            InputStyleVariant::Flat => {
375                get_theme!(&self.theme_colors, InputColorsThemePreference, "flat_input")
376            }
377        };
378        let theme_layout = match self.layout_variant {
379            InputLayoutVariant::Normal => get_theme!(
380                &self.theme_layout,
381                InputLayoutThemePreference,
382                "input_layout"
383            ),
384            InputLayoutVariant::Compact => get_theme!(
385                &self.theme_layout,
386                InputLayoutThemePreference,
387                "compact_input_layout"
388            ),
389            InputLayoutVariant::Expanded => get_theme!(
390                &self.theme_layout,
391                InputLayoutThemePreference,
392                "expanded_input_layout"
393            ),
394        };
395
396        let (mut movement_timeout, cursor_color) =
397            use_cursor_blink(focus() != Focus::Not, theme_colors.color);
398
399        let enabled = use_reactive(&self.enabled);
400
401        let display_placeholder = value.read().is_empty()
402            && self.placeholder.is_some()
403            && !editable.editor().read().has_preedit();
404        let on_validate = self.on_validate.clone();
405        let on_submit = self.on_submit.clone();
406
407        if *value.read() != editable.editor().read().committed_text() {
408            let mut editor = editable.editor_mut().write();
409            editor.clear_preedit();
410            editor.set(&value.read());
411            editor.editor_history_mut().clear();
412            editor.clear_selection();
413        }
414
415        let mode = self.mode;
416        let text_align = self.text_align;
417        let padding = theme_layout.padding;
418        let multiline = self.multiline;
419        let mut follow_cursor = move || {
420            if !a11y_id.is_focused() || display_placeholder {
421                return;
422            }
423
424            let holder = holder.peek();
425            let holder = holder.0.borrow();
426            let Some(ParagraphHolderInner {
427                paragraph,
428                scale_factor,
429                ..
430            }) = holder.as_ref()
431            else {
432                warn!("Paragraph should be build by now.");
433                return;
434            };
435
436            let viewport = viewport_area();
437            if viewport.width() == 0. {
438                return;
439            }
440
441            // Text as currently displayed
442            let editor = editable.editor().peek();
443            let text = match mode {
444                InputMode::Hidden(character) => {
445                    character.to_string().repeat(editor.rope().len_chars())
446                }
447                InputMode::Shown => editor.rope().to_string(),
448            };
449
450            let cursor_rect = paragraph.cursor_rect(&text, editor.cursor_pos(), text_align);
451            let cursor_location =
452                Point2D::new(cursor_rect.left, cursor_rect.top) / (*scale_factor as f32);
453            let visible_cursor_location =
454                cursor_location + Vector2D::new(padding.left(), padding.top());
455            // Visible window start
456            let visible_start_x = viewport.min_x() - area.peek().min_x();
457
458            // Minimally reveal the cursor
459            if visible_cursor_location.x < visible_start_x {
460                scroll_controller.scroll_to_x(-cursor_location.x as i32);
461            } else if visible_cursor_location.x > visible_start_x + viewport.width() {
462                scroll_controller.scroll_to_x(
463                    -(visible_cursor_location.x + padding.right() - viewport.width()) as i32,
464                );
465            }
466
467            if multiline {
468                let cursor_bottom = cursor_rect.bottom / (*scale_factor as f32) + padding.bottom();
469                let visible_start_y = viewport.min_y() - area.peek().min_y();
470
471                if visible_cursor_location.y < visible_start_y {
472                    scroll_controller.scroll_to_y(-cursor_location.y as i32);
473                } else if cursor_bottom > visible_start_y + viewport.height() {
474                    scroll_controller.scroll_to_y(-(cursor_bottom - viewport.height()) as i32);
475                }
476            }
477        };
478
479        let on_ime_preedit = move |e: Event<ImePreeditEventData>| {
480            let mut editor = editable.editor_mut().write();
481            if e.data().text.is_empty() {
482                editor.clear_preedit();
483            } else {
484                editor.set_preedit(&e.data().text);
485            }
486        };
487
488        let on_pre_key_down = self.on_pre_key_down.clone().unwrap_or_else(|| {
489            Callback::new(move |e: Event<KeyboardEventData>| match &e.key {
490                Key::Named(NamedKey::Enter) if !multiline => true,
491                Key::Named(NamedKey::Escape) | Key::Named(NamedKey::Shift) => true,
492                Key::Named(NamedKey::Tab) => false,
493                _ => {
494                    e.stop_propagation();
495                    e.prevent_default();
496                    true
497                }
498            })
499        });
500        let on_key_down = move |e: Event<KeyboardEventData>| {
501            let key = e.key.clone();
502            let modifiers = e.modifiers;
503
504            if !on_pre_key_down.call(e) {
505                return;
506            }
507
508            match &key {
509                // On submit
510                Key::Named(NamedKey::Enter) if !multiline => {
511                    if let Some(on_submit) = &on_submit {
512                        let text = editable.editor().peek().committed_text();
513                        on_submit.call(text);
514                    }
515                }
516                // On unfocus
517                Key::Named(NamedKey::Escape) => {
518                    a11y_id.request_unfocus();
519                }
520                // On change
521                _ => {
522                    movement_timeout.reset();
523                    let previous_history_version =
524                        editable.editor().peek().editor_history().version;
525                    editable.process_event(EditableEvent::KeyDown {
526                        key: &key,
527                        modifiers,
528                        editor_line: Some(EditorLine::SingleParagraph),
529                        holder: Some(&holder.read()),
530                    });
531                    let text = editable.editor().read().committed_text();
532
533                    let apply_change = match &on_validate {
534                        Some(on_validate) => {
535                            let mut editor = editable.editor_mut().write();
536                            let validator = InputValidator::new(text.clone());
537                            on_validate.call(validator.clone());
538                            if !validator.is_valid() {
539                                if let Some(selection) = editor.undo() {
540                                    *editor.selection_mut() = selection;
541                                }
542                                editor.editor_history_mut().clear_redos();
543                            }
544                            validator.is_valid()
545                        }
546                        None => true,
547                    };
548
549                    if apply_change {
550                        value.set(text);
551                    }
552                    if editable.editor().peek().editor_history().version == previous_history_version
553                    {
554                        follow_cursor();
555                    }
556                }
557            }
558        };
559
560        let on_key_up = move |e: Event<KeyboardEventData>| {
561            e.stop_propagation();
562            editable.process_event(EditableEvent::KeyUp { key: &e.key });
563        };
564
565        let on_input_focus_press = move |e: Event<FocusPressEventData>| {
566            e.stop_propagation();
567            e.prevent_default();
568            if cfg!(target_os = "android") {
569                if a11y_id.is_focused() {
570                    // Require a second press to enabling dragging on Android
571                    is_dragging.set_if_modified(true);
572                }
573            } else {
574                is_dragging.set_if_modified(true);
575            }
576            movement_timeout.reset();
577            if !display_placeholder {
578                let text_area = area.read().to_f64();
579                let global_location = e.global_location().clamp(text_area.min(), text_area.max());
580                let location = (global_location - text_area.min()).to_point();
581                editable.process_event(EditableEvent::Down {
582                    location,
583                    editor_line: EditorLine::SingleParagraph,
584                    holder: &holder.read(),
585                });
586            }
587            a11y_id.request_focus();
588        };
589
590        let on_focus_press = move |e: Event<FocusPressEventData>| {
591            e.stop_propagation();
592            e.prevent_default();
593            if cfg!(target_os = "android") {
594                if a11y_id.is_focused() {
595                    // Require a second press to enabling dragging on Android
596                    is_dragging.set_if_modified(true);
597                }
598            } else {
599                is_dragging.set_if_modified(true);
600            }
601            movement_timeout.reset();
602            if !display_placeholder {
603                editable.process_event(EditableEvent::Down {
604                    location: e.element_location(),
605                    editor_line: EditorLine::SingleParagraph,
606                    holder: &holder.read(),
607                });
608            }
609            a11y_id.request_focus();
610        };
611
612        let on_global_pointer_move = move |e: Event<PointerEventData>| {
613            if a11y_id.is_focused() && *is_dragging.read() {
614                let text_area = area.read().to_f64();
615                let location = (e.global_location() - text_area.min()).to_point();
616                editable.process_event(EditableEvent::Move {
617                    location,
618                    editor_line: EditorLine::SingleParagraph,
619                    holder: &holder.read(),
620                });
621                follow_cursor();
622            }
623        };
624
625        let on_pointer_enter = move |_| {
626            *status.write() = InputStatus::Hovering;
627        };
628
629        let on_pointer_leave = move |_| {
630            if status() == InputStatus::Hovering {
631                *status.write() = InputStatus::default();
632            }
633        };
634
635        let on_global_pointer_up = move |_: Event<PointerEventData>| {
636            match *status.read() {
637                InputStatus::Idle if a11y_id.is_focused() => {
638                    editable.process_event(EditableEvent::Release);
639                }
640                InputStatus::Hovering => {
641                    editable.process_event(EditableEvent::Release);
642                }
643                _ => {}
644            };
645
646            if a11y_id.is_focused() {
647                if *is_dragging.read() {
648                    // The input is focused and dragging, but it just clicked so we assume the dragging can stop
649                    is_dragging.set(false);
650                } else {
651                    // The input is focused but not dragging, so the click means it was clicked outside, therefore we can unfocus this input
652                    a11y_id.request_unfocus();
653                }
654            }
655        };
656
657        let on_pointer_press = move |e: Event<PointerEventData>| {
658            e.stop_propagation();
659            e.prevent_default();
660            match *status.read() {
661                InputStatus::Idle if a11y_id.is_focused() => {
662                    editable.process_event(EditableEvent::Release);
663                }
664                InputStatus::Hovering => {
665                    editable.process_event(EditableEvent::Release);
666                }
667                _ => {}
668            };
669
670            if a11y_id.is_focused() {
671                is_dragging.set_if_modified(false);
672            }
673        };
674
675        let on_paragraph_sized = move |e: Event<SizedEventData>| {
676            let viewport = viewport_area();
677            if viewport.width() > 0. {
678                scroll_controller.apply_layout(e.area.size, viewport.size);
679            }
680
681            let text_size_changed = area.peek().size != e.area.size;
682            area.set_if_modified(e.area);
683            if text_size_changed {
684                follow_cursor();
685            }
686        };
687
688        let (background, cursor_index, text_selection) = if enabled() && focus() != Focus::Not {
689            (
690                theme_colors.focus_background,
691                Some(editable.editor().read().cursor_pos()),
692                editable
693                    .editor()
694                    .read()
695                    .get_visible_selection(EditorLine::SingleParagraph),
696            )
697        } else {
698            (theme_colors.background, None, None)
699        };
700
701        let border = if focus().is_focused() {
702            Border::new()
703                .fill(theme_colors.focus_border_fill)
704                .width(2.)
705                .alignment(BorderAlignment::Inner)
706        } else {
707            Border::new()
708                .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.85))
709                .width(1.)
710                .alignment(BorderAlignment::Inner)
711        };
712
713        let color = if display_placeholder {
714            theme_colors.placeholder_color
715        } else {
716            theme_colors.color
717        };
718
719        let value = self.value.read();
720        let a11y_text: Cow<str> = match (self.mode, &self.placeholder) {
721            (_, Some(ph)) if display_placeholder => Cow::Borrowed(ph.as_ref()),
722            (InputMode::Hidden(ch), _) => Cow::Owned(ch.to_string().repeat(value.len())),
723            (InputMode::Shown, _) => Cow::Borrowed(value.as_ref()),
724        };
725
726        let a11_role = match self.mode {
727            InputMode::Hidden(_) => AccessibilityRole::PasswordInput,
728            _ if self.multiline => AccessibilityRole::MultilineTextInput,
729            _ => AccessibilityRole::TextInput,
730        };
731
732        rect()
733            .a11y_id(a11y_id)
734            .a11y_enabled(self.enabled)
735            .a11y_focusable(self.enabled)
736            .a11y_auto_focus(self.auto_focus)
737            .a11y_alt(a11y_text)
738            .a11y_role(a11_role)
739            .maybe(self.enabled, |el| {
740                el.on_key_up(on_key_up)
741                    .on_key_down(on_key_down)
742                    .on_focus_press(on_input_focus_press)
743                    .on_ime_preedit(on_ime_preedit)
744                    .on_pointer_press(on_pointer_press)
745                    .on_global_pointer_up(on_global_pointer_up)
746                    .on_global_pointer_move(on_global_pointer_move)
747            })
748            .on_pointer_enter(on_pointer_enter)
749            .on_pointer_leave(on_pointer_leave)
750            .cursor(if self.enabled {
751                CursorIcon::Text
752            } else {
753                CursorIcon::NotAllowed
754            })
755            .width(self.width.clone())
756            .height(self.height.clone())
757            .background(background.mul_if(!self.enabled, 0.85))
758            .border(border)
759            .corner_radius(theme_layout.corner_radius)
760            .content(Content::Flex)
761            .direction(Direction::Horizontal)
762            .cross_align(Alignment::center())
763            .maybe_child(
764                self.leading
765                    .clone()
766                    .map(|leading| rect().padding(Gaps::new(0., 0., 0., 8.)).child(leading)),
767            )
768            .child(
769                ScrollView::new_controlled(scroll_controller)
770                    .width(Size::flex(1.))
771                    .height(if self.multiline && !matches!(self.height, Size::Inner) {
772                        Size::fill()
773                    } else {
774                        Size::Inner
775                    })
776                    .direction(if self.multiline {
777                        Direction::Vertical
778                    } else {
779                        Direction::Horizontal
780                    })
781                    .show_scrollbar(self.multiline)
782                    .on_sized(move |e: Event<SizedEventData>| viewport_area.set_if_modified(e.area))
783                    .child(
784                        paragraph()
785                            .holder(holder.read().clone())
786                            .on_sized(on_paragraph_sized)
787                            .min_width(Size::percent(100.))
788                            .maybe(self.multiline, |el| el.max_width(Size::percent(100.)))
789                            .maybe(self.enabled, |el| el.on_focus_press(on_focus_press))
790                            .padding(theme_layout.padding)
791                            .cursor_index(cursor_index)
792                            .cursor_color(cursor_color)
793                            .color(color)
794                            .text_align(self.text_align)
795                            .maybe(!self.multiline, |el| el.max_lines(1))
796                            .highlights(text_selection.map(|h| vec![h]))
797                            .maybe(display_placeholder, |el| {
798                                el.span(self.placeholder.as_ref().unwrap().to_string())
799                            })
800                            .maybe(!display_placeholder, |el| {
801                                let editor = editable.editor().read();
802                                if editor.has_preedit() {
803                                    let (b, p, a) = editor.preedit_text_segments();
804                                    let (b, p, a) = match self.mode {
805                                        InputMode::Hidden(ch) => {
806                                            let ch = ch.to_string();
807                                            (
808                                                ch.repeat(b.chars().count()),
809                                                ch.repeat(p.chars().count()),
810                                                ch.repeat(a.chars().count()),
811                                            )
812                                        }
813                                        InputMode::Shown => (b, p, a),
814                                    };
815                                    el.span(b)
816                                        .span(
817                                            Span::new(p).text_decoration(TextDecoration::Underline),
818                                        )
819                                        .span(a)
820                                } else {
821                                    let text = match self.mode {
822                                        InputMode::Hidden(ch) => {
823                                            ch.to_string().repeat(editor.rope().len_chars())
824                                        }
825                                        InputMode::Shown => editor.rope().to_string(),
826                                    };
827                                    el.span(text)
828                                }
829                            }),
830                    ),
831            )
832            .maybe_child(
833                self.trailing
834                    .clone()
835                    .map(|trailing| rect().padding(Gaps::new(0., 8., 0., 0.)).child(trailing)),
836            )
837    }
838
839    fn render_key(&self) -> DiffKey {
840        self.key.clone().or(self.default_key())
841    }
842}