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