cosmic/widget/text_input/
input.rs

1// Copyright 2019 H�ctor Ram�n, Iced contributors
2// Copyright 2023 System76 <info@system76.com>
3// SPDX-License-Identifier: MIT
4
5//! Display fields that can be filled with text.
6//!
7//! A [`TextInput`] has some local [`State`].
8use std::borrow::Cow;
9use std::cell::{Cell, LazyCell};
10
11use crate::ext::ColorExt;
12use crate::theme::THEME;
13
14use super::cursor;
15pub use super::cursor::Cursor;
16use super::editor::Editor;
17use super::style::StyleSheet;
18pub use super::value::Value;
19
20use apply::Apply;
21use iced::Limits;
22use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
23use iced::clipboard::mime::AsMimeTypes;
24use iced_core::event::{self, Event};
25use iced_core::mouse::{self, click};
26use iced_core::overlay::Group;
27use iced_core::renderer::{self, Renderer as CoreRenderer};
28use iced_core::text::{self, Paragraph, Renderer, Text};
29use iced_core::time::{Duration, Instant};
30use iced_core::touch;
31use iced_core::widget::Id;
32use iced_core::widget::operation::{self, Operation};
33use iced_core::widget::tree::{self, Tree};
34use iced_core::window;
35use iced_core::{Background, alignment};
36use iced_core::{Border, Shadow, keyboard};
37use iced_core::{
38    Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
39    Vector, Widget,
40};
41use iced_core::{layout, overlay};
42use iced_runtime::{Action, Task, task};
43
44thread_local! {
45    // Prevents two inputs from being focused at the same time.
46    static LAST_FOCUS_UPDATE: LazyCell<Cell<Instant>> = LazyCell::new(|| Cell::new(Instant::now()));
47}
48
49/// Creates a new [`TextInput`].
50///
51/// [`TextInput`]: widget::TextInput
52pub fn text_input<'a, Message>(
53    placeholder: impl Into<Cow<'a, str>>,
54    value: impl Into<Cow<'a, str>>,
55) -> TextInput<'a, Message>
56where
57    Message: Clone + 'static,
58{
59    TextInput::new(placeholder, value)
60}
61
62/// A text label which can transform into a text input on activation.
63pub fn editable_input<'a, Message: Clone + 'static>(
64    placeholder: impl Into<Cow<'a, str>>,
65    text: impl Into<Cow<'a, str>>,
66    editing: bool,
67    on_toggle_edit: impl Fn(bool) -> Message + 'a,
68) -> TextInput<'a, Message> {
69    let icon = crate::widget::icon::from_name(if editing {
70        "edit-clear-symbolic"
71    } else {
72        "edit-symbolic"
73    });
74
75    TextInput::new(placeholder, text)
76        .style(crate::theme::TextInput::EditableText)
77        .editable()
78        .editing(editing)
79        .on_toggle_edit(on_toggle_edit)
80        .trailing_icon(icon.size(16).into())
81}
82
83/// Creates a new search [`TextInput`].
84///
85/// [`TextInput`]: widget::TextInput
86pub fn search_input<'a, Message>(
87    placeholder: impl Into<Cow<'a, str>>,
88    value: impl Into<Cow<'a, str>>,
89) -> TextInput<'a, Message>
90where
91    Message: Clone + 'static,
92{
93    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
94
95    TextInput::new(placeholder, value)
96        .padding([0, spacing])
97        .style(crate::theme::TextInput::Search)
98        .leading_icon(
99            crate::widget::icon::from_name("system-search-symbolic")
100                .size(16)
101                .apply(crate::widget::container)
102                .padding(8)
103                .into(),
104        )
105}
106/// Creates a new secure [`TextInput`].
107///
108/// [`TextInput`]: widget::TextInput
109pub fn secure_input<'a, Message>(
110    placeholder: impl Into<Cow<'a, str>>,
111    value: impl Into<Cow<'a, str>>,
112    on_visible_toggle: Option<Message>,
113    hidden: bool,
114) -> TextInput<'a, Message>
115where
116    Message: Clone + 'static,
117{
118    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
119    let mut input = TextInput::new(placeholder, value)
120        .padding([0, spacing])
121        .style(crate::theme::TextInput::Default)
122        .leading_icon(
123            crate::widget::icon::from_name("system-lock-screen-symbolic")
124                .size(16)
125                .apply(crate::widget::container)
126                .padding(8)
127                .into(),
128        );
129    if hidden {
130        input = input.password();
131    }
132    if let Some(msg) = on_visible_toggle {
133        input.trailing_icon(
134            crate::widget::icon::from_name(if hidden {
135                "document-properties-symbolic"
136            } else {
137                "image-red-eye-symbolic"
138            })
139            .size(16)
140            .apply(crate::widget::button::custom)
141            .class(crate::theme::Button::Icon)
142            .on_press(msg)
143            .padding(8)
144            .into(),
145        )
146    } else {
147        input
148    }
149}
150
151/// Creates a new inline [`TextInput`].
152///
153/// [`TextInput`]: widget::TextInput
154pub fn inline_input<'a, Message>(
155    placeholder: impl Into<Cow<'a, str>>,
156    value: impl Into<Cow<'a, str>>,
157) -> TextInput<'a, Message>
158where
159    Message: Clone + 'static,
160{
161    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
162
163    TextInput::new(placeholder, value)
164        .style(crate::theme::TextInput::Inline)
165        .padding(spacing)
166}
167
168pub(crate) const SUPPORTED_TEXT_MIME_TYPES: &[&str; 6] = &[
169    "text/plain;charset=utf-8",
170    "text/plain;charset=UTF-8",
171    "UTF8_STRING",
172    "STRING",
173    "text/plain",
174    "TEXT",
175];
176
177/// A field that can be filled with text.
178#[allow(missing_debug_implementations)]
179#[must_use]
180pub struct TextInput<'a, Message> {
181    id: Id,
182    placeholder: Cow<'a, str>,
183    value: Value,
184    is_secure: bool,
185    is_editable_variant: bool,
186    is_read_only: bool,
187    select_on_focus: bool,
188    font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
189    width: Length,
190    padding: Padding,
191    size: Option<f32>,
192    helper_size: f32,
193    label: Option<Cow<'a, str>>,
194    helper_text: Option<Cow<'a, str>>,
195    error: Option<Cow<'a, str>>,
196    on_focus: Option<Message>,
197    on_unfocus: Option<Message>,
198    on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
199    on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
200    on_tab: Option<Message>,
201    on_submit: Option<Box<dyn Fn(String) -> Message + 'a>>,
202    on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
203    leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
204    trailing_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
205    style: <crate::Theme as StyleSheet>::Style,
206    on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
207    surface_ids: Option<(window::Id, window::Id)>,
208    dnd_icon: bool,
209    line_height: text::LineHeight,
210    helper_line_height: text::LineHeight,
211    always_active: bool,
212    /// The text input tracks and manages the input value in its state.
213    manage_value: bool,
214}
215
216impl<'a, Message> TextInput<'a, Message>
217where
218    Message: Clone + 'static,
219{
220    /// Creates a new [`TextInput`].
221    ///
222    /// It expects:
223    /// - a placeholder,
224    /// - the current value
225    pub fn new(placeholder: impl Into<Cow<'a, str>>, value: impl Into<Cow<'a, str>>) -> Self {
226        let spacing = THEME.lock().unwrap().cosmic().space_xxs();
227
228        let v: Cow<'a, str> = value.into();
229        TextInput {
230            id: Id::unique(),
231            placeholder: placeholder.into(),
232            value: Value::new(v.as_ref()),
233            is_secure: false,
234            is_editable_variant: false,
235            is_read_only: false,
236            select_on_focus: false,
237            font: None,
238            width: Length::Fill,
239            padding: spacing.into(),
240            size: None,
241            helper_size: 10.0,
242            helper_line_height: text::LineHeight::Absolute(14.0.into()),
243            on_focus: None,
244            on_unfocus: None,
245            on_input: None,
246            on_paste: None,
247            on_submit: None,
248            on_tab: None,
249            on_toggle_edit: None,
250            leading_icon: None,
251            trailing_icon: None,
252            error: None,
253            style: crate::theme::TextInput::default(),
254            on_create_dnd_source: None,
255            surface_ids: None,
256            dnd_icon: false,
257            line_height: text::LineHeight::default(),
258            label: None,
259            helper_text: None,
260            always_active: false,
261            manage_value: false,
262        }
263    }
264
265    #[inline]
266    fn dnd_id(&self) -> u128 {
267        match &self.id.0 {
268            iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
269                *id as u128
270            }
271            _ => unreachable!(),
272        }
273    }
274
275    /// Sets the input to be always active.
276    /// This makes it behave as if it was always focused.
277    #[inline]
278    pub const fn always_active(mut self) -> Self {
279        self.always_active = true;
280        self
281    }
282
283    /// Sets the text of the [`TextInput`].
284    pub fn label(mut self, label: impl Into<Cow<'a, str>>) -> Self {
285        self.label = Some(label.into());
286        self
287    }
288
289    /// Sets the helper text of the [`TextInput`].
290    pub fn helper_text(mut self, helper_text: impl Into<Cow<'a, str>>) -> Self {
291        self.helper_text = Some(helper_text.into());
292        self
293    }
294
295    /// Sets the [`Id`] of the [`TextInput`].
296    #[inline]
297    pub fn id(mut self, id: Id) -> Self {
298        self.id = id;
299        self
300    }
301
302    /// Sets the error message of the [`TextInput`].
303    pub fn error(mut self, error: impl Into<Cow<'a, str>>) -> Self {
304        self.error = Some(error.into());
305        self
306    }
307
308    /// Sets the [`LineHeight`] of the [`TextInput`].
309    pub fn line_height(mut self, line_height: impl Into<text::LineHeight>) -> Self {
310        self.line_height = line_height.into();
311        self
312    }
313
314    /// Converts the [`TextInput`] into a secure password input.
315    #[inline]
316    pub const fn password(mut self) -> Self {
317        self.is_secure = true;
318        self
319    }
320
321    /// Applies behaviors unique to the `editable_input` variable.
322    #[inline]
323    pub(crate) const fn editable(mut self) -> Self {
324        self.is_editable_variant = true;
325        self
326    }
327
328    #[inline]
329    pub const fn editing(mut self, enable: bool) -> Self {
330        self.is_read_only = !enable;
331        self
332    }
333
334    /// Selects all text when the text input is focused
335    #[inline]
336    pub const fn select_on_focus(mut self, select_on_focus: bool) -> Self {
337        self.select_on_focus = select_on_focus;
338        self
339    }
340
341    /// Emits a message when an unfocused text input has been focused by click.
342    ///
343    /// This will not trigger if the input was focused externally by the application.
344    #[inline]
345    pub fn on_focus(mut self, on_focus: Message) -> Self {
346        self.on_focus = Some(on_focus);
347        self
348    }
349
350    /// Emits a message when a focused text input has been unfocused via the Tab or Esc key.
351    ///
352    /// This will not trigger if the input was unfocused externally by the application.
353    #[inline]
354    pub fn on_unfocus(mut self, on_unfocus: Message) -> Self {
355        self.on_unfocus = Some(on_unfocus);
356        self
357    }
358
359    /// Sets the message that should be produced when some text is typed into
360    /// the [`TextInput`].
361    ///
362    /// If this method is not called, the [`TextInput`] will be disabled.
363    pub fn on_input(mut self, callback: impl Fn(String) -> Message + 'a) -> Self {
364        self.on_input = Some(Box::new(callback));
365        self
366    }
367
368    /// Emits a message when a focused text input receives the Enter/Return key.
369    pub fn on_submit(mut self, callback: impl Fn(String) -> Message + 'a) -> Self {
370        self.on_submit = Some(Box::new(callback));
371        self
372    }
373
374    /// Optionally emits a message when a focused text input receives the Enter/Return key.
375    pub fn on_submit_maybe(self, callback: Option<impl Fn(String) -> Message + 'a>) -> Self {
376        if let Some(callback) = callback {
377            self.on_submit(callback)
378        } else {
379            self
380        }
381    }
382
383    /// Emits a message when the Tab key has been captured, which prevents focus from changing.
384    ///
385    /// If you do no want to capture the Tab key, use [`TextInput::on_unfocus`] instead.
386    #[inline]
387    pub fn on_tab(mut self, on_tab: Message) -> Self {
388        self.on_tab = Some(on_tab);
389        self
390    }
391
392    /// Emits a message when the editable state of the input changes.
393    pub fn on_toggle_edit(mut self, callback: impl Fn(bool) -> Message + 'a) -> Self {
394        self.on_toggle_edit = Some(Box::new(callback));
395        self
396    }
397
398    /// Sets the message that should be produced when some text is pasted into
399    /// the [`TextInput`].
400    pub fn on_paste(mut self, on_paste: impl Fn(String) -> Message + 'a) -> Self {
401        self.on_paste = Some(Box::new(on_paste));
402        self
403    }
404
405    /// Sets the [`Font`] of the [`TextInput`].
406    ///
407    /// [`Font`]: text::Renderer::Font
408    #[inline]
409    pub const fn font(
410        mut self,
411        font: <crate::Renderer as iced_core::text::Renderer>::Font,
412    ) -> Self {
413        self.font = Some(font);
414        self
415    }
416
417    /// Sets the start [`Icon`] of the [`TextInput`].
418    #[inline]
419    pub fn leading_icon(
420        mut self,
421        icon: Element<'a, Message, crate::Theme, crate::Renderer>,
422    ) -> Self {
423        self.leading_icon = Some(icon);
424        self
425    }
426
427    /// Sets the end [`Icon`] of the [`TextInput`].
428    #[inline]
429    pub fn trailing_icon(
430        mut self,
431        icon: Element<'a, Message, crate::Theme, crate::Renderer>,
432    ) -> Self {
433        self.trailing_icon = Some(icon);
434        self
435    }
436
437    /// Sets the width of the [`TextInput`].
438    pub fn width(mut self, width: impl Into<Length>) -> Self {
439        self.width = width.into();
440        self
441    }
442
443    /// Sets the [`Padding`] of the [`TextInput`].
444    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
445        self.padding = padding.into();
446        self
447    }
448
449    /// Sets the text size of the [`TextInput`].
450    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
451        self.size = Some(size.into().0);
452        self
453    }
454
455    /// Sets the style of the [`TextInput`].
456    pub fn style(mut self, style: impl Into<<crate::Theme as StyleSheet>::Style>) -> Self {
457        self.style = style.into();
458        self
459    }
460
461    /// Sets the text input to manage its input value or not
462    #[inline]
463    pub const fn manage_value(mut self, manage_value: bool) -> Self {
464        self.manage_value = manage_value;
465        self
466    }
467
468    /// Draws the [`TextInput`] with the given [`Renderer`], overriding its
469    /// [`Value`] if provided.
470    ///
471    /// [`Renderer`]: text::Renderer
472    #[allow(clippy::too_many_arguments)]
473    #[inline]
474    pub fn draw(
475        &self,
476        tree: &Tree,
477        renderer: &mut crate::Renderer,
478        theme: &crate::Theme,
479        layout: Layout<'_>,
480        cursor_position: mouse::Cursor,
481        value: Option<&Value>,
482        style: &renderer::Style,
483    ) {
484        let text_layout = self.text_layout(layout);
485        draw(
486            renderer,
487            theme,
488            layout,
489            text_layout,
490            cursor_position,
491            tree,
492            value.unwrap_or(&self.value),
493            &self.placeholder,
494            self.size,
495            self.font,
496            self.on_input.is_none(),
497            self.is_secure,
498            self.leading_icon.as_ref(),
499            self.trailing_icon.as_ref(),
500            &self.style,
501            self.dnd_icon,
502            self.line_height,
503            self.error.as_deref(),
504            self.label.as_deref(),
505            self.helper_text.as_deref(),
506            self.helper_size,
507            self.helper_line_height,
508            &layout.bounds(),
509            style,
510        );
511    }
512
513    /// Sets the start dnd handler of the [`TextInput`].
514    #[cfg(feature = "wayland")]
515    pub fn on_start_dnd(mut self, on_start_dnd: impl Fn(State) -> Message + 'a) -> Self {
516        self.on_create_dnd_source = Some(Box::new(on_start_dnd));
517        self
518    }
519
520    /// Sets the window id of the [`TextInput`] and the window id of the drag icon.
521    /// Both ids are required to be unique.
522    /// This is required for the dnd to work.
523    #[inline]
524    pub const fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self {
525        self.surface_ids = Some(window_id);
526        self
527    }
528
529    /// Sets the mode of this [`TextInput`] to be a drag and drop icon.
530    #[inline]
531    pub const fn dnd_icon(mut self, dnd_icon: bool) -> Self {
532        self.dnd_icon = dnd_icon;
533        self
534    }
535
536    pub fn on_clear(self, on_clear: Message) -> Self {
537        self.trailing_icon(
538            crate::widget::icon::from_name("edit-clear-symbolic")
539                .size(16)
540                .apply(crate::widget::button::custom)
541                .class(crate::theme::Button::Icon)
542                .on_press(on_clear)
543                .padding(8)
544                .into(),
545        )
546    }
547
548    /// Get the layout node of the actual text input
549
550    fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> {
551        if self.dnd_icon {
552            layout
553        } else if self.label.is_some() {
554            let mut nodes = layout.children();
555            nodes.next();
556            nodes.next().unwrap()
557        } else {
558            layout.children().next().unwrap()
559        }
560    }
561}
562
563impl<Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'_, Message>
564where
565    Message: Clone + 'static,
566{
567    #[inline]
568    fn tag(&self) -> tree::Tag {
569        tree::Tag::of::<State>()
570    }
571
572    #[inline]
573    fn state(&self) -> tree::State {
574        tree::State::new(State::new(
575            self.is_secure,
576            self.is_read_only,
577            self.always_active,
578            self.select_on_focus,
579        ))
580    }
581
582    fn diff(&mut self, tree: &mut Tree) {
583        let state = tree.state.downcast_mut::<State>();
584
585        if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value {
586            state.tracked_value = self.value.clone();
587        } else if self.value.is_empty() {
588            self.value = state.tracked_value.clone();
589            // std::mem::swap(&mut state.tracked_value, &mut self.value);
590        }
591        // Unfocus text input if it becomes disabled
592        if self.on_input.is_none() && !self.manage_value {
593            state.last_click = None;
594            state.is_focused = None;
595            state.is_pasting = None;
596            state.dragging_state = None;
597        }
598        let old_value = state
599            .value
600            .raw()
601            .buffer()
602            .lines
603            .iter()
604            .map(|l| l.text())
605            .collect::<String>();
606        if state.is_secure != self.is_secure
607            || old_value != self.value.to_string()
608            || state
609                .label
610                .raw()
611                .buffer()
612                .lines
613                .iter()
614                .map(|l| l.text())
615                .collect::<String>()
616                != self.label.as_deref().unwrap_or_default()
617            || state
618                .helper_text
619                .raw()
620                .buffer()
621                .lines
622                .iter()
623                .map(|l| l.text())
624                .collect::<String>()
625                != self.helper_text.as_deref().unwrap_or_default()
626        {
627            state.is_secure = self.is_secure;
628            state.dirty = true;
629        }
630
631        if self.always_active && state.is_focused.is_none() {
632            let now = Instant::now();
633            LAST_FOCUS_UPDATE.with(|x| x.set(now));
634            state.is_focused = Some(Focus {
635                updated_at: now,
636                now,
637            });
638        }
639
640        // if the previous state was at the end of the text, keep it there
641        let old_value = Value::new(&old_value);
642        if state.is_focused.is_some() {
643            if let cursor::State::Index(index) = state.cursor.state(&old_value) {
644                if index == old_value.len() {
645                    state.cursor.move_to(self.value.len());
646                }
647            };
648        }
649
650        if let Some(f) = state.is_focused.as_ref() {
651            if f.updated_at != LAST_FOCUS_UPDATE.with(|f| f.get()) {
652                state.unfocus();
653                state.emit_unfocus = true;
654            }
655        }
656
657        self.is_read_only = state.is_read_only;
658
659        // Stop pasting if input becomes disabled
660        if !self.manage_value && self.on_input.is_none() {
661            state.is_pasting = None;
662        }
663
664        let mut children: Vec<_> = self
665            .leading_icon
666            .iter_mut()
667            .chain(self.trailing_icon.iter_mut())
668            .map(iced_core::Element::as_widget_mut)
669            .collect();
670        tree.diff_children(children.as_mut_slice());
671    }
672
673    fn children(&self) -> Vec<Tree> {
674        self.leading_icon
675            .iter()
676            .chain(self.trailing_icon.iter())
677            .map(|icon| Tree::new(icon))
678            .collect()
679    }
680
681    #[inline]
682    fn size(&self) -> Size<Length> {
683        Size {
684            width: self.width,
685            height: Length::Shrink,
686        }
687    }
688
689    fn layout(
690        &self,
691        tree: &mut Tree,
692        renderer: &crate::Renderer,
693        limits: &layout::Limits,
694    ) -> layout::Node {
695        let font = self.font.unwrap_or_else(|| renderer.default_font());
696        if self.dnd_icon {
697            let state = tree.state.downcast_mut::<State>();
698            let limits = limits.width(Length::Shrink).height(Length::Shrink);
699
700            let size = self.size.unwrap_or_else(|| renderer.default_size().0);
701
702            let bounds = limits.resolve(Length::Shrink, Length::Fill, Size::INFINITY);
703            let value_paragraph = &mut state.value;
704            let v = self.value.to_string();
705            value_paragraph.update(Text {
706                content: if self.value.is_empty() {
707                    self.placeholder.as_ref()
708                } else {
709                    &v
710                },
711                font,
712                bounds,
713                size: iced::Pixels(size),
714                horizontal_alignment: alignment::Horizontal::Left,
715                vertical_alignment: alignment::Vertical::Center,
716                line_height: text::LineHeight::default(),
717                shaping: text::Shaping::Advanced,
718                wrapping: text::Wrapping::None,
719            });
720
721            let Size { width, height } =
722                limits.resolve(Length::Shrink, Length::Shrink, value_paragraph.min_bounds());
723
724            let size = limits.resolve(width, height, Size::new(width, height));
725            layout::Node::with_children(size, vec![layout::Node::new(size)])
726        } else {
727            let res = layout(
728                renderer,
729                limits,
730                self.width,
731                self.padding,
732                self.size,
733                self.leading_icon.as_ref(),
734                self.trailing_icon.as_ref(),
735                self.line_height,
736                self.label.as_deref(),
737                self.helper_text.as_deref(),
738                self.helper_size,
739                self.helper_line_height,
740                font,
741                tree,
742            );
743
744            // XXX not ideal, but we need to update the cache when is_secure changes
745            let size = self.size.unwrap_or_else(|| renderer.default_size().0);
746            let line_height = self.line_height;
747            let state = tree.state.downcast_mut::<State>();
748            if state.dirty {
749                state.dirty = false;
750                let value = if self.is_secure {
751                    self.value.secure()
752                } else {
753                    self.value.clone()
754                };
755                replace_paragraph(
756                    state,
757                    Layout::new(&res),
758                    &value,
759                    font,
760                    iced::Pixels(size),
761                    line_height,
762                    limits,
763                );
764            }
765            res
766        }
767    }
768
769    fn operate(
770        &self,
771        tree: &mut Tree,
772        _layout: Layout<'_>,
773        _renderer: &crate::Renderer,
774        operation: &mut dyn Operation<()>,
775    ) {
776        let state = tree.state.downcast_mut::<State>();
777
778        operation.custom(state, Some(&self.id));
779        operation.focusable(state, Some(&self.id));
780        operation.text_input(state, Some(&self.id));
781    }
782
783    fn overlay<'b>(
784        &'b mut self,
785        tree: &'b mut Tree,
786        layout: Layout<'_>,
787        renderer: &crate::Renderer,
788        translation: Vector,
789    ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
790        let mut layout_ = Vec::with_capacity(2);
791        if self.leading_icon.is_some() {
792            let mut children = self.text_layout(layout).children();
793            children.next();
794            layout_.push(children.next().unwrap());
795        }
796        if self.trailing_icon.is_some() {
797            let mut children = self.text_layout(layout).children();
798            children.next();
799            if self.leading_icon.is_some() {
800                children.next();
801            }
802            layout_.push(children.next().unwrap());
803        };
804        let children = self
805            .leading_icon
806            .iter_mut()
807            .chain(self.trailing_icon.iter_mut())
808            .zip(&mut tree.children)
809            .zip(layout_)
810            .filter_map(|((child, state), layout)| {
811                child
812                    .as_widget_mut()
813                    .overlay(state, layout, renderer, translation)
814            })
815            .collect::<Vec<_>>();
816
817        (!children.is_empty()).then(|| Group::with_children(children).overlay())
818    }
819
820    fn on_event(
821        &mut self,
822        tree: &mut Tree,
823        event: Event,
824        layout: Layout<'_>,
825        cursor_position: mouse::Cursor,
826        renderer: &crate::Renderer,
827        clipboard: &mut dyn Clipboard,
828        shell: &mut Shell<'_, Message>,
829        viewport: &Rectangle,
830    ) -> event::Status {
831        let text_layout = self.text_layout(layout);
832        let mut trailing_icon_layout = None;
833        let font = self.font.unwrap_or_else(|| renderer.default_font());
834        let size = self.size.unwrap_or_else(|| renderer.default_size().0);
835        let line_height = self.line_height;
836
837        // Disables editing of the editable variant when clicking outside of it.
838        if self.is_editable_variant {
839            if let Some(ref on_edit) = self.on_toggle_edit {
840                let state = tree.state.downcast_mut::<State>();
841                if !state.is_read_only && state.is_focused.is_none() {
842                    state.is_read_only = true;
843                    shell.publish((on_edit)(false));
844                }
845            }
846        }
847
848        // Calculates the layout of the trailing icon button element.
849        if !tree.children.is_empty() {
850            let index = tree.children.len() - 1;
851            if let (Some(trailing_icon), Some(tree)) =
852                (self.trailing_icon.as_mut(), tree.children.get_mut(index))
853            {
854                trailing_icon_layout = Some(text_layout.children().last().unwrap());
855
856                // Enable custom buttons defined on the trailing icon position to be handled.
857                if !self.is_editable_variant {
858                    if let Some(trailing_layout) = trailing_icon_layout {
859                        if cursor_position.is_over(trailing_layout.bounds()) {
860                            let res = trailing_icon.as_widget_mut().on_event(
861                                tree,
862                                event.clone(),
863                                trailing_layout,
864                                cursor_position,
865                                renderer,
866                                clipboard,
867                                shell,
868                                viewport,
869                            );
870
871                            if res == event::Status::Captured {
872                                return res;
873                            }
874                        }
875                    }
876                }
877            }
878        }
879
880        let state = tree.state.downcast_mut::<State>();
881
882        if let Some(on_unfocus) = self.on_unfocus.as_ref() {
883            if state.emit_unfocus {
884                state.emit_unfocus = false;
885                shell.publish(on_unfocus.clone());
886            }
887        }
888
889        let dnd_id = self.dnd_id();
890        let id = Widget::id(self);
891        update(
892            id,
893            event,
894            text_layout.children().next().unwrap(),
895            trailing_icon_layout,
896            cursor_position,
897            clipboard,
898            shell,
899            &mut self.value,
900            size,
901            font,
902            self.is_editable_variant,
903            self.is_secure,
904            self.on_focus.as_ref(),
905            self.on_unfocus.as_ref(),
906            self.on_input.as_deref(),
907            self.on_paste.as_deref(),
908            self.on_submit.as_deref(),
909            self.on_tab.as_ref(),
910            self.on_toggle_edit.as_deref(),
911            || tree.state.downcast_mut::<State>(),
912            self.on_create_dnd_source.as_deref(),
913            dnd_id,
914            line_height,
915            layout,
916            self.manage_value,
917        )
918    }
919
920    #[inline]
921    fn draw(
922        &self,
923        tree: &Tree,
924        renderer: &mut crate::Renderer,
925        theme: &crate::Theme,
926        style: &renderer::Style,
927        layout: Layout<'_>,
928        cursor_position: mouse::Cursor,
929        viewport: &Rectangle,
930    ) {
931        let text_layout = self.text_layout(layout);
932        draw(
933            renderer,
934            theme,
935            layout,
936            text_layout,
937            cursor_position,
938            tree,
939            &self.value,
940            &self.placeholder,
941            self.size,
942            self.font,
943            self.on_input.is_none() && !self.manage_value,
944            self.is_secure,
945            self.leading_icon.as_ref(),
946            self.trailing_icon.as_ref(),
947            &self.style,
948            self.dnd_icon,
949            self.line_height,
950            self.error.as_deref(),
951            self.label.as_deref(),
952            self.helper_text.as_deref(),
953            self.helper_size,
954            self.helper_line_height,
955            viewport,
956            style,
957        );
958    }
959
960    fn mouse_interaction(
961        &self,
962        state: &Tree,
963        layout: Layout<'_>,
964        cursor_position: mouse::Cursor,
965        viewport: &Rectangle,
966        renderer: &crate::Renderer,
967    ) -> mouse::Interaction {
968        let layout = self.text_layout(layout);
969        let mut index = 0;
970        if let (Some(leading_icon), Some(tree)) =
971            (self.leading_icon.as_ref(), state.children.get(index))
972        {
973            let leading_icon_layout = layout.children().nth(1).unwrap();
974
975            if cursor_position.is_over(leading_icon_layout.bounds()) {
976                return leading_icon.as_widget().mouse_interaction(
977                    tree,
978                    layout,
979                    cursor_position,
980                    viewport,
981                    renderer,
982                );
983            }
984            index += 1;
985        }
986
987        if let (Some(trailing_icon), Some(tree)) =
988            (self.trailing_icon.as_ref(), state.children.get(index))
989        {
990            let mut children = layout.children();
991            children.next();
992            // skip if there is no leading icon
993            if self.leading_icon.is_some() {
994                children.next();
995            }
996            let trailing_icon_layout = children.next().unwrap();
997
998            if cursor_position.is_over(trailing_icon_layout.bounds()) {
999                return trailing_icon.as_widget().mouse_interaction(
1000                    tree,
1001                    layout,
1002                    cursor_position,
1003                    viewport,
1004                    renderer,
1005                );
1006            }
1007        }
1008        let mut children = layout.children();
1009        let layout = children.next().unwrap();
1010        mouse_interaction(
1011            layout,
1012            cursor_position,
1013            self.on_input.is_none() && !self.manage_value,
1014        )
1015    }
1016
1017    #[inline]
1018    fn id(&self) -> Option<Id> {
1019        Some(self.id.clone())
1020    }
1021
1022    #[inline]
1023    fn set_id(&mut self, id: Id) {
1024        self.id = id;
1025    }
1026
1027    fn drag_destinations(
1028        &self,
1029        _state: &Tree,
1030        layout: Layout<'_>,
1031        _renderer: &crate::Renderer,
1032        dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1033    ) {
1034        if let Some(input) = layout.children().last() {
1035            let Rectangle {
1036                x,
1037                y,
1038                width,
1039                height,
1040            } = input.bounds();
1041            dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
1042                id: self.dnd_id(),
1043                rectangle: iced::clipboard::dnd::Rectangle {
1044                    x: x as f64,
1045                    y: y as f64,
1046                    width: width as f64,
1047                    height: height as f64,
1048                },
1049                mime_types: SUPPORTED_TEXT_MIME_TYPES
1050                    .iter()
1051                    .map(|s| Cow::Borrowed(*s))
1052                    .collect(),
1053                actions: DndAction::Move,
1054                preferred: DndAction::Move,
1055            });
1056        }
1057    }
1058}
1059
1060impl<'a, Message> From<TextInput<'a, Message>>
1061    for Element<'a, Message, crate::Theme, crate::Renderer>
1062where
1063    Message: 'static + Clone,
1064{
1065    fn from(
1066        text_input: TextInput<'a, Message>,
1067    ) -> Element<'a, Message, crate::Theme, crate::Renderer> {
1068        Element::new(text_input)
1069    }
1070}
1071
1072/// Produces a [`Task`] that focuses the [`TextInput`] with the given [`Id`].
1073pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
1074    task::effect(Action::widget(operation::focusable::focus(id)))
1075}
1076
1077/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
1078/// end.
1079pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
1080    task::effect(Action::widget(operation::text_input::move_cursor_to_end(
1081        id,
1082    )))
1083}
1084
1085/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
1086/// front.
1087pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
1088    task::effect(Action::widget(operation::text_input::move_cursor_to_front(
1089        id,
1090    )))
1091}
1092
1093/// Produces a [`Task`] that moves the cursor of the [`TextInput`] with the given [`Id`] to the
1094/// provided position.
1095pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
1096    task::effect(Action::widget(operation::text_input::move_cursor_to(
1097        id, position,
1098    )))
1099}
1100
1101/// Produces a [`Task`] that selects all the content of the [`TextInput`] with the given [`Id`].
1102pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
1103    task::effect(Action::widget(operation::text_input::select_all(id)))
1104}
1105
1106/// Computes the layout of a [`TextInput`].
1107#[allow(clippy::cast_precision_loss)]
1108#[allow(clippy::too_many_arguments)]
1109#[allow(clippy::too_many_lines)]
1110pub fn layout<Message>(
1111    renderer: &crate::Renderer,
1112    limits: &layout::Limits,
1113    width: Length,
1114    padding: Padding,
1115    size: Option<f32>,
1116    leading_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
1117    trailing_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
1118    line_height: text::LineHeight,
1119    label: Option<&str>,
1120    helper_text: Option<&str>,
1121    helper_text_size: f32,
1122    helper_text_line_height: text::LineHeight,
1123    font: iced_core::Font,
1124    tree: &mut Tree,
1125) -> layout::Node {
1126    let limits = limits.width(width);
1127    let spacing = THEME.lock().unwrap().cosmic().space_xxs();
1128    let mut nodes = Vec::with_capacity(3);
1129
1130    let text_pos = if let Some(label) = label {
1131        let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
1132        let state = tree.state.downcast_mut::<State>();
1133        let label_paragraph = &mut state.label;
1134        label_paragraph.update(Text {
1135            content: label,
1136            font,
1137            bounds: text_bounds,
1138            size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
1139            horizontal_alignment: alignment::Horizontal::Left,
1140            vertical_alignment: alignment::Vertical::Center,
1141            line_height,
1142            shaping: text::Shaping::Advanced,
1143            wrapping: text::Wrapping::None,
1144        });
1145        let label_size = label_paragraph.min_bounds();
1146
1147        nodes.push(layout::Node::new(label_size));
1148        Vector::new(0.0, label_size.height + f32::from(spacing))
1149    } else {
1150        Vector::ZERO
1151    };
1152
1153    let text_size = size.unwrap_or_else(|| renderer.default_size().0);
1154    let mut text_input_height = line_height.to_absolute(text_size.into()).0;
1155    let padding = padding.fit(Size::ZERO, limits.max());
1156
1157    let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() {
1158        let children = &mut tree.children;
1159        // TODO configurable icon spacing, maybe via appearance
1160        let limits_copy = limits;
1161
1162        let limits = limits.shrink(padding);
1163        let icon_spacing = 8.0;
1164        let mut c_i = 0;
1165        let (leading_icon_width, mut leading_icon) =
1166            if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) {
1167                let size = icon.as_widget().size();
1168                let icon_node = icon.as_widget().layout(
1169                    tree,
1170                    renderer,
1171                    &Limits::NONE.width(size.width).height(size.height),
1172                );
1173                text_input_height = text_input_height.max(icon_node.bounds().height);
1174                c_i += 1;
1175                (icon_node.bounds().width + icon_spacing, Some(icon_node))
1176            } else {
1177                (0.0, None)
1178            };
1179
1180        let (trailing_icon_width, mut trailing_icon) =
1181            if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) {
1182                let size = icon.as_widget().size();
1183                let icon_node = icon.as_widget().layout(
1184                    tree,
1185                    renderer,
1186                    &Limits::NONE.width(size.width).height(size.height),
1187                );
1188                text_input_height = text_input_height.max(icon_node.bounds().height);
1189                (icon_node.bounds().width + icon_spacing, Some(icon_node))
1190            } else {
1191                (0.0, None)
1192            };
1193        let text_limits = limits
1194            .width(width)
1195            .height(line_height.to_absolute(text_size.into()));
1196        let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
1197        let text_node = layout::Node::new(
1198            text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
1199        )
1200        .move_to(Point::new(
1201            padding.left + leading_icon_width,
1202            padding.top
1203                + ((text_input_height - line_height.to_absolute(text_size.into()).0) / 2.0)
1204                    .max(0.0),
1205        ));
1206        let mut node_list: Vec<_> = Vec::with_capacity(3);
1207
1208        let text_node_bounds = text_node.bounds();
1209        node_list.push(text_node);
1210
1211        if let Some(leading_icon) = leading_icon.take() {
1212            node_list.push(leading_icon.clone().move_to(Point::new(
1213                padding.left,
1214                padding.top + ((text_input_height - leading_icon.bounds().height) / 2.0).max(0.0),
1215            )));
1216        }
1217        if let Some(trailing_icon) = trailing_icon.take() {
1218            let trailing_icon = trailing_icon.clone().move_to(Point::new(
1219                text_node_bounds.x + text_node_bounds.width + f32::from(spacing),
1220                padding.top + ((text_input_height - trailing_icon.bounds().height) / 2.0).max(0.0),
1221            ));
1222            node_list.push(trailing_icon);
1223        }
1224
1225        let text_input_size = Size::new(
1226            text_node_bounds.x + text_node_bounds.width + trailing_icon_width,
1227            text_input_height,
1228        )
1229        .expand(padding);
1230
1231        let input_limits = limits_copy
1232            .width(width)
1233            .height(text_input_height.max(text_input_size.height))
1234            .min_width(text_input_size.width);
1235        let input_bounds = input_limits.resolve(
1236            width,
1237            text_input_height.max(text_input_size.height),
1238            text_input_size,
1239        );
1240        let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos);
1241        let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing);
1242        nodes.push(input_node);
1243
1244        Vector::new(0.0, y_pos)
1245    } else {
1246        let limits = limits
1247            .width(width)
1248            .height(text_input_height + padding.vertical())
1249            .shrink(padding);
1250        let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
1251
1252        let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top));
1253
1254        let node = layout::Node::with_children(text_bounds.expand(padding), vec![text])
1255            .translate(text_pos);
1256        let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
1257
1258        nodes.push(node);
1259
1260        Vector::new(0.0, y_pos)
1261    };
1262
1263    if let Some(helper_text) = helper_text {
1264        let limits = limits
1265            .width(width)
1266            .shrink(padding)
1267            .height(helper_text_line_height.to_absolute(helper_text_size.into()));
1268        let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
1269        let state = tree.state.downcast_mut::<State>();
1270        let helper_text_paragraph = &mut state.helper_text;
1271        helper_text_paragraph.update(Text {
1272            content: helper_text,
1273            font,
1274            bounds: text_bounds,
1275            size: iced::Pixels(helper_text_size),
1276            horizontal_alignment: alignment::Horizontal::Left,
1277            vertical_alignment: alignment::Vertical::Center,
1278            line_height: helper_text_line_height,
1279            shaping: text::Shaping::Advanced,
1280            wrapping: text::Wrapping::None,
1281        });
1282        let helper_text_size = helper_text_paragraph.min_bounds();
1283        let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
1284        nodes.push(helper_text_node);
1285    };
1286
1287    let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
1288        Size::new(
1289            size.width.max(node.bounds().width),
1290            size.height + node.bounds().height,
1291        )
1292    });
1293    size.height += (nodes.len() - 1) as f32 * f32::from(spacing);
1294
1295    let limits = limits
1296        .width(width)
1297        .height(size.height)
1298        .min_width(size.width);
1299
1300    layout::Node::with_children(limits.resolve(width, size.height, size), nodes)
1301}
1302
1303// TODO: Merge into widget method since iced has done the same.
1304/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
1305/// accordingly.
1306#[allow(clippy::too_many_arguments)]
1307#[allow(clippy::too_many_lines)]
1308#[allow(clippy::missing_panics_doc)]
1309#[allow(clippy::cast_lossless)]
1310#[allow(clippy::cast_possible_truncation)]
1311pub fn update<'a, Message: Clone + 'static>(
1312    id: Option<Id>,
1313    event: Event,
1314    text_layout: Layout<'_>,
1315    edit_button_layout: Option<Layout<'_>>,
1316    cursor: mouse::Cursor,
1317    clipboard: &mut dyn Clipboard,
1318    shell: &mut Shell<'_, Message>,
1319    value: &mut Value,
1320    size: f32,
1321    font: <crate::Renderer as iced_core::text::Renderer>::Font,
1322    is_editable_variant: bool,
1323    is_secure: bool,
1324    on_focus: Option<&Message>,
1325    on_unfocus: Option<&Message>,
1326    on_input: Option<&dyn Fn(String) -> Message>,
1327    on_paste: Option<&dyn Fn(String) -> Message>,
1328    on_submit: Option<&dyn Fn(String) -> Message>,
1329    on_tab: Option<&Message>,
1330    on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
1331    state: impl FnOnce() -> &'a mut State,
1332    #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
1333    #[allow(unused_variables)] dnd_id: u128,
1334    line_height: text::LineHeight,
1335    layout: Layout<'_>,
1336    manage_value: bool,
1337) -> event::Status {
1338    let update_cache = |state, value| {
1339        replace_paragraph(
1340            state,
1341            layout,
1342            value,
1343            font,
1344            iced::Pixels(size),
1345            line_height,
1346            &Limits::NONE.max_width(text_layout.bounds().width),
1347        );
1348    };
1349
1350    let mut secured_value = if is_secure {
1351        value.secure()
1352    } else {
1353        value.clone()
1354    };
1355    let unsecured_value = value;
1356    let value = &mut secured_value;
1357
1358    // NOTE: Clicks must be captured to prevent mouse areas behind them handling the same clicks.
1359
1360    /// Mark a branch as cold
1361    #[inline]
1362    #[cold]
1363    fn cold() {}
1364
1365    match event {
1366        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
1367        | Event::Touch(touch::Event::FingerPressed { .. }) => {
1368            cold();
1369            let state = state();
1370
1371            let click_position = if on_input.is_some() || manage_value {
1372                cursor.position_over(layout.bounds())
1373            } else {
1374                None
1375            };
1376
1377            if let Some(cursor_position) = click_position {
1378                // Check if the edit button was clicked.
1379                if state.dragging_state == None
1380                    && edit_button_layout.map_or(false, |l| cursor.is_over(l.bounds()))
1381                {
1382                    if is_editable_variant {
1383                        state.is_read_only = !state.is_read_only;
1384                        state.move_cursor_to_end();
1385
1386                        if let Some(on_toggle_edit) = on_toggle_edit {
1387                            shell.publish(on_toggle_edit(!state.is_read_only));
1388                        }
1389
1390                        let now = Instant::now();
1391                        LAST_FOCUS_UPDATE.with(|x| x.set(now));
1392                        state.is_focused = Some(Focus {
1393                            updated_at: now,
1394                            now,
1395                        });
1396                    }
1397
1398                    return event::Status::Captured;
1399                }
1400
1401                let target = cursor_position.x - text_layout.bounds().x;
1402
1403                let click =
1404                    mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
1405
1406                match (
1407                    &state.dragging_state,
1408                    click.kind(),
1409                    state.cursor().state(value),
1410                ) {
1411                    #[cfg(feature = "wayland")]
1412                    (None, click::Kind::Single, cursor::State::Selection { start, end }) => {
1413                        // if something is already selected, we can start a drag and drop for a
1414                        // single click that is on top of the selected text
1415                        // is the click on selected text?
1416
1417                        if on_input.is_some() || manage_value {
1418                            let left = start.min(end);
1419                            let right = end.max(start);
1420
1421                            let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
1422                                state.value.raw(),
1423                                text_layout.bounds(),
1424                                left,
1425                            );
1426
1427                            let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
1428                                state.value.raw(),
1429                                text_layout.bounds(),
1430                                right,
1431                            );
1432
1433                            let width = right_position - left_position;
1434                            let selection_bounds = Rectangle {
1435                                x: text_layout.bounds().x + left_position,
1436                                y: text_layout.bounds().y,
1437                                width,
1438                                height: text_layout.bounds().height,
1439                            };
1440
1441                            if cursor.is_over(selection_bounds) {
1442                                // XXX never start a dnd if the input is secure
1443                                if is_secure {
1444                                    return event::Status::Ignored;
1445                                }
1446                                let input_text =
1447                                    state.selected_text(&value.to_string()).unwrap_or_default();
1448                                state.dragging_state = Some(DraggingState::Dnd(
1449                                    DndAction::empty(),
1450                                    input_text.clone(),
1451                                ));
1452                                let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1453                                editor.delete();
1454
1455                                let contents = editor.contents();
1456                                let unsecured_value = Value::new(&contents);
1457                                state.tracked_value = unsecured_value.clone();
1458                                if let Some(on_input) = on_input {
1459                                    let message = (on_input)(contents);
1460                                    shell.publish(message);
1461                                }
1462                                if let Some(on_start_dnd) = on_start_dnd_source {
1463                                    shell.publish(on_start_dnd(state.clone()));
1464                                }
1465                                let state_clone = state.clone();
1466
1467                                iced_core::clipboard::start_dnd(
1468                                    clipboard,
1469                                    false,
1470                                    id.map(iced_core::clipboard::DndSource::Widget),
1471                                    Some(iced_core::clipboard::IconSurface::new(
1472                                        Element::from(
1473                                            TextInput::<'static, ()>::new("", input_text.clone())
1474                                                .dnd_icon(true),
1475                                        ),
1476                                        iced_core::widget::tree::State::new(state_clone),
1477                                        Vector::ZERO,
1478                                    )),
1479                                    Box::new(TextInputString(input_text)),
1480                                    DndAction::Move,
1481                                );
1482
1483                                update_cache(state, &unsecured_value);
1484                            } else {
1485                                update_cache(state, value);
1486                                state.setting_selection(value, text_layout.bounds(), target);
1487                            }
1488                        } else {
1489                            state.setting_selection(value, text_layout.bounds(), target);
1490                        }
1491                    }
1492                    (None, click::Kind::Single, _) => {
1493                        state.setting_selection(value, text_layout.bounds(), target);
1494                    }
1495                    (None | Some(DraggingState::Selection), click::Kind::Double, _) => {
1496                        update_cache(state, value);
1497
1498                        if is_secure {
1499                            state.cursor.select_all(value);
1500                        } else {
1501                            let position =
1502                                find_cursor_position(text_layout.bounds(), value, state, target)
1503                                    .unwrap_or(0);
1504
1505                            state.cursor.select_range(
1506                                value.previous_start_of_word(position),
1507                                value.next_end_of_word(position),
1508                            );
1509                        }
1510                        state.dragging_state = Some(DraggingState::Selection);
1511                    }
1512                    (None | Some(DraggingState::Selection), click::Kind::Triple, _) => {
1513                        update_cache(state, value);
1514                        state.cursor.select_all(value);
1515                        state.dragging_state = Some(DraggingState::Selection);
1516                    }
1517                    _ => {
1518                        state.dragging_state = None;
1519                    }
1520                }
1521
1522                // Focus on click of the text input, and ensure that the input is writable.
1523                if state.is_focused.is_none()
1524                    && matches!(state.dragging_state, None | Some(DraggingState::Selection))
1525                {
1526                    if let Some(on_focus) = on_focus {
1527                        shell.publish(on_focus.clone());
1528                    }
1529
1530                    if state.is_read_only {
1531                        state.is_read_only = false;
1532                        if let Some(on_toggle_edit) = on_toggle_edit {
1533                            let message = (on_toggle_edit)(true);
1534                            shell.publish(message);
1535                        }
1536                    }
1537
1538                    let now = Instant::now();
1539                    LAST_FOCUS_UPDATE.with(|x| x.set(now));
1540
1541                    state.is_focused = Some(Focus {
1542                        updated_at: now,
1543                        now,
1544                    });
1545                }
1546
1547                state.last_click = Some(click);
1548
1549                return event::Status::Captured;
1550            } else {
1551                state.unfocus();
1552
1553                if let Some(on_unfocus) = on_unfocus {
1554                    shell.publish(on_unfocus.clone());
1555                }
1556            }
1557        }
1558        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
1559        | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
1560            cold();
1561            let state = state();
1562            state.dragging_state = None;
1563
1564            return if cursor.is_over(layout.bounds()) {
1565                event::Status::Captured
1566            } else {
1567                event::Status::Ignored
1568            };
1569        }
1570        Event::Mouse(mouse::Event::CursorMoved { position })
1571        | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
1572            let state = state();
1573
1574            if matches!(state.dragging_state, Some(DraggingState::Selection)) {
1575                let target = position.x - text_layout.bounds().x;
1576
1577                update_cache(state, value);
1578                let position =
1579                    find_cursor_position(text_layout.bounds(), value, state, target).unwrap_or(0);
1580
1581                state
1582                    .cursor
1583                    .select_range(state.cursor.start(value), position);
1584
1585                return event::Status::Captured;
1586            }
1587        }
1588        Event::Keyboard(keyboard::Event::KeyPressed {
1589            key,
1590            text,
1591            physical_key,
1592            ..
1593        }) => {
1594            let state = state();
1595
1596            if let Some(focus) = &mut state.is_focused {
1597                if state.is_read_only || (!manage_value && on_input.is_none()) {
1598                    return event::Status::Ignored;
1599                };
1600
1601                let modifiers = state.keyboard_modifiers;
1602                focus.updated_at = Instant::now();
1603                LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1604
1605                // Check if Ctrl+A/C/V/X was pressed.
1606                if state.keyboard_modifiers.command() {
1607                    match key.as_ref() {
1608                        keyboard::Key::Character("c") => {
1609                            if !is_secure {
1610                                if let Some((start, end)) = state.cursor.selection(value) {
1611                                    clipboard.write(
1612                                        iced_core::clipboard::Kind::Standard,
1613                                        value.select(start, end).to_string(),
1614                                    );
1615                                }
1616                            }
1617                        }
1618                        // XXX if we want to allow cutting of secure text, we need to
1619                        // update the cache and decide which value to cut
1620                        keyboard::Key::Character("x") => {
1621                            if !is_secure {
1622                                if let Some((start, end)) = state.cursor.selection(value) {
1623                                    clipboard.write(
1624                                        iced_core::clipboard::Kind::Standard,
1625                                        value.select(start, end).to_string(),
1626                                    );
1627                                }
1628
1629                                let mut editor = Editor::new(value, &mut state.cursor);
1630                                editor.delete();
1631                                let content = editor.contents();
1632                                state.tracked_value = Value::new(&content);
1633                                if let Some(on_input) = on_input {
1634                                    let message = (on_input)(content);
1635                                    shell.publish(message);
1636                                }
1637                            }
1638                        }
1639                        keyboard::Key::Character("v") => {
1640                            let content = if let Some(content) = state.is_pasting.take() {
1641                                content
1642                            } else {
1643                                let content: String = clipboard
1644                                    .read(iced_core::clipboard::Kind::Standard)
1645                                    .unwrap_or_default()
1646                                    .chars()
1647                                    .filter(|c| !c.is_control())
1648                                    .collect();
1649
1650                                Value::new(&content)
1651                            };
1652
1653                            let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1654
1655                            editor.paste(content.clone());
1656
1657                            let contents = editor.contents();
1658                            let unsecured_value = Value::new(&contents);
1659                            state.tracked_value = unsecured_value.clone();
1660
1661                            if let Some(on_input) = on_input {
1662                                let message = if let Some(paste) = &on_paste {
1663                                    (paste)(contents)
1664                                } else {
1665                                    (on_input)(contents)
1666                                };
1667
1668                                shell.publish(message);
1669                            }
1670
1671                            state.is_pasting = Some(content);
1672
1673                            let value = if is_secure {
1674                                unsecured_value.secure()
1675                            } else {
1676                                unsecured_value
1677                            };
1678
1679                            update_cache(state, &value);
1680                            return event::Status::Captured;
1681                        }
1682
1683                        keyboard::Key::Character("a") => {
1684                            state.cursor.select_all(value);
1685                            return event::Status::Captured;
1686                        }
1687
1688                        _ => {}
1689                    }
1690                }
1691
1692                // Capture keyboard inputs that should be submitted.
1693                if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) {
1694                    if state.is_read_only || (!manage_value && on_input.is_none()) {
1695                        return event::Status::Ignored;
1696                    };
1697
1698                    state.is_pasting = None;
1699
1700                    if !state.keyboard_modifiers.command() && !modifiers.control() {
1701                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1702
1703                        editor.insert(c);
1704
1705                        let contents = editor.contents();
1706                        let unsecured_value = Value::new(&contents);
1707                        state.tracked_value = unsecured_value.clone();
1708
1709                        if let Some(on_input) = on_input {
1710                            let message = (on_input)(contents);
1711                            shell.publish(message);
1712                        }
1713
1714                        focus.updated_at = Instant::now();
1715                        LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1716
1717                        let value = if is_secure {
1718                            unsecured_value.secure()
1719                        } else {
1720                            unsecured_value
1721                        };
1722
1723                        update_cache(state, &value);
1724
1725                        return event::Status::Captured;
1726                    }
1727                }
1728
1729                match key.as_ref() {
1730                    keyboard::Key::Named(keyboard::key::Named::Enter) => {
1731                        if let Some(on_submit) = on_submit {
1732                            shell.publish((on_submit)(unsecured_value.to_string()));
1733                        }
1734                    }
1735                    keyboard::Key::Named(keyboard::key::Named::Backspace) => {
1736                        if platform::is_jump_modifier_pressed(modifiers)
1737                            && state.cursor.selection(value).is_none()
1738                        {
1739                            if is_secure {
1740                                let cursor_pos = state.cursor.end(value);
1741                                state.cursor.select_range(0, cursor_pos);
1742                            } else {
1743                                state.cursor.select_left_by_words(value);
1744                            }
1745                        }
1746
1747                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1748                        editor.backspace();
1749
1750                        let contents = editor.contents();
1751                        let unsecured_value = Value::new(&contents);
1752                        state.tracked_value = unsecured_value.clone();
1753                        if let Some(on_input) = on_input {
1754                            let message = (on_input)(editor.contents());
1755                            shell.publish(message);
1756                        }
1757                        let value = if is_secure {
1758                            unsecured_value.secure()
1759                        } else {
1760                            unsecured_value
1761                        };
1762                        update_cache(state, &value);
1763                    }
1764                    keyboard::Key::Named(keyboard::key::Named::Delete) => {
1765                        if platform::is_jump_modifier_pressed(modifiers)
1766                            && state.cursor.selection(value).is_none()
1767                        {
1768                            if is_secure {
1769                                let cursor_pos = state.cursor.end(unsecured_value);
1770                                state.cursor.select_range(cursor_pos, unsecured_value.len());
1771                            } else {
1772                                state.cursor.select_right_by_words(unsecured_value);
1773                            }
1774                        }
1775
1776                        let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1777                        editor.delete();
1778                        let contents = editor.contents();
1779                        let unsecured_value = Value::new(&contents);
1780                        if let Some(on_input) = on_input {
1781                            let message = (on_input)(contents);
1782                            state.tracked_value = unsecured_value.clone();
1783                            shell.publish(message);
1784                        }
1785
1786                        let value = if is_secure {
1787                            unsecured_value.secure()
1788                        } else {
1789                            unsecured_value
1790                        };
1791
1792                        update_cache(state, &value);
1793                    }
1794                    keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
1795                        if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
1796                            if modifiers.shift() {
1797                                state.cursor.select_left_by_words(value);
1798                            } else {
1799                                state.cursor.move_left_by_words(value);
1800                            }
1801                        } else if modifiers.shift() {
1802                            state.cursor.select_left(value);
1803                        } else {
1804                            state.cursor.move_left(value);
1805                        }
1806                    }
1807                    keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
1808                        if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
1809                            if modifiers.shift() {
1810                                state.cursor.select_right_by_words(value);
1811                            } else {
1812                                state.cursor.move_right_by_words(value);
1813                            }
1814                        } else if modifiers.shift() {
1815                            state.cursor.select_right(value);
1816                        } else {
1817                            state.cursor.move_right(value);
1818                        }
1819                    }
1820                    keyboard::Key::Named(keyboard::key::Named::Home) => {
1821                        if modifiers.shift() {
1822                            state.cursor.select_range(state.cursor.start(value), 0);
1823                        } else {
1824                            state.cursor.move_to(0);
1825                        }
1826                    }
1827                    keyboard::Key::Named(keyboard::key::Named::End) => {
1828                        if modifiers.shift() {
1829                            state
1830                                .cursor
1831                                .select_range(state.cursor.start(value), value.len());
1832                        } else {
1833                            state.cursor.move_to(value.len());
1834                        }
1835                    }
1836                    keyboard::Key::Named(keyboard::key::Named::Escape) => {
1837                        state.unfocus();
1838                        state.is_read_only = true;
1839
1840                        if let Some(on_unfocus) = on_unfocus {
1841                            shell.publish(on_unfocus.clone());
1842                        }
1843                    }
1844
1845                    keyboard::Key::Named(keyboard::key::Named::Tab) => {
1846                        if let Some(on_tab) = on_tab {
1847                            // Allow the application to decide how the event is handled.
1848                            // This could be to connect the text input to another text input.
1849                            // Or to connect the text input to a button.
1850                            shell.publish(on_tab.clone());
1851                        } else {
1852                            state.is_read_only = true;
1853
1854                            if let Some(on_unfocus) = on_unfocus {
1855                                shell.publish(on_unfocus.clone());
1856                            }
1857
1858                            return event::Status::Ignored;
1859                        };
1860                    }
1861
1862                    keyboard::Key::Named(
1863                        keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
1864                    ) => {
1865                        return event::Status::Ignored;
1866                    }
1867                    _ => {}
1868                }
1869
1870                return event::Status::Captured;
1871            }
1872        }
1873        Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
1874            let state = state();
1875
1876            if state.is_focused.is_some() {
1877                match key {
1878                    keyboard::Key::Character(c) if "v" == c => {
1879                        state.is_pasting = None;
1880                    }
1881                    keyboard::Key::Named(keyboard::key::Named::Tab)
1882                    | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
1883                    | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
1884                        return event::Status::Ignored;
1885                    }
1886                    _ => {}
1887                }
1888
1889                return event::Status::Captured;
1890            }
1891        }
1892        Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1893            let state = state();
1894
1895            state.keyboard_modifiers = modifiers;
1896        }
1897        Event::Window(window::Event::RedrawRequested(now)) => {
1898            let state = state();
1899
1900            if let Some(focus) = &mut state.is_focused {
1901                focus.now = now;
1902
1903                let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
1904                    - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
1905
1906                shell.request_redraw(window::RedrawRequest::At(
1907                    now + Duration::from_millis(u64::try_from(millis_until_redraw).unwrap()),
1908                ));
1909            }
1910        }
1911        #[cfg(feature = "wayland")]
1912        Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
1913            cold();
1914            let state = state();
1915            if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
1916                // TODO: restore value in text input
1917                state.dragging_state = None;
1918                return event::Status::Captured;
1919            }
1920        }
1921        #[cfg(feature = "wayland")]
1922        Event::Dnd(DndEvent::Offer(
1923            rectangle,
1924            OfferEvent::Enter {
1925                x,
1926                y,
1927                mime_types,
1928                surface,
1929            },
1930        )) if rectangle == Some(dnd_id) => {
1931            cold();
1932            let state = state();
1933            let is_clicked = text_layout.bounds().contains(Point {
1934                x: x as f32,
1935                y: y as f32,
1936            });
1937
1938            let mut accepted = false;
1939            for m in &mime_types {
1940                if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
1941                    let clone = m.clone();
1942                    accepted = true;
1943                }
1944            }
1945            if accepted {
1946                let target = x as f32 - text_layout.bounds().x;
1947                state.dnd_offer =
1948                    DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
1949                // existing logic for setting the selection
1950                let position = if target > 0.0 {
1951                    update_cache(state, value);
1952                    find_cursor_position(text_layout.bounds(), value, state, target)
1953                } else {
1954                    None
1955                };
1956
1957                state.cursor.move_to(position.unwrap_or(0));
1958                return event::Status::Captured;
1959            }
1960        }
1961        #[cfg(feature = "wayland")]
1962        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
1963            if rectangle == Some(dnd_id) =>
1964        {
1965            let state = state();
1966
1967            let target = x as f32 - text_layout.bounds().x;
1968            // existing logic for setting the selection
1969            let position = if target > 0.0 {
1970                update_cache(state, value);
1971                find_cursor_position(text_layout.bounds(), value, state, target)
1972            } else {
1973                None
1974            };
1975
1976            state.cursor.move_to(position.unwrap_or(0));
1977            return event::Status::Captured;
1978        }
1979        #[cfg(feature = "wayland")]
1980        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
1981            cold();
1982            let state = state();
1983            if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
1984                let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
1985                    .iter()
1986                    .find(|m| mime_types.contains(&(**m).to_string()))
1987                else {
1988                    state.dnd_offer = DndOfferState::None;
1989                    return event::Status::Captured;
1990                };
1991                state.dnd_offer = DndOfferState::Dropped;
1992            }
1993
1994            return event::Status::Ignored;
1995        }
1996        #[cfg(feature = "wayland")]
1997        Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != id => {}
1998        #[cfg(feature = "wayland")]
1999        Event::Dnd(DndEvent::Offer(
2000            rectangle,
2001            OfferEvent::Leave | OfferEvent::LeaveDestination,
2002        )) => {
2003            cold();
2004            let state = state();
2005            // ASHLEY TODO we should be able to reset but for now we don't if we are handling a
2006            // drop
2007            match state.dnd_offer {
2008                DndOfferState::Dropped => {}
2009                _ => {
2010                    state.dnd_offer = DndOfferState::None;
2011                }
2012            };
2013            return event::Status::Captured;
2014        }
2015        #[cfg(feature = "wayland")]
2016        Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2017            if rectangle == Some(dnd_id) =>
2018        {
2019            cold();
2020            let state = state();
2021            if let DndOfferState::Dropped = state.dnd_offer.clone() {
2022                state.dnd_offer = DndOfferState::None;
2023                if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2024                    return event::Status::Captured;
2025                }
2026                let Ok(content) = String::from_utf8(data) else {
2027                    return event::Status::Captured;
2028                };
2029
2030                let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2031
2032                editor.paste(Value::new(content.as_str()));
2033                let contents = editor.contents();
2034                let unsecured_value = Value::new(&contents);
2035                state.tracked_value = unsecured_value.clone();
2036                if let Some(on_paste) = on_paste.as_ref() {
2037                    let message = (on_paste)(contents);
2038                    shell.publish(message);
2039                }
2040
2041                let value = if is_secure {
2042                    unsecured_value.secure()
2043                } else {
2044                    unsecured_value
2045                };
2046                update_cache(state, &value);
2047                return event::Status::Captured;
2048            }
2049            return event::Status::Ignored;
2050        }
2051        _ => {}
2052    }
2053
2054    event::Status::Ignored
2055}
2056
2057/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
2058/// [`Value`] if provided.
2059///
2060/// [`Renderer`]: text::Renderer
2061#[allow(clippy::too_many_arguments)]
2062#[allow(clippy::too_many_lines)]
2063#[allow(clippy::missing_panics_doc)]
2064pub fn draw<'a, Message>(
2065    renderer: &mut crate::Renderer,
2066    theme: &crate::Theme,
2067    layout: Layout<'_>,
2068    text_layout: Layout<'_>,
2069    cursor_position: mouse::Cursor,
2070    tree: &Tree,
2071    value: &Value,
2072    placeholder: &str,
2073    size: Option<f32>,
2074    font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2075    is_disabled: bool,
2076    is_secure: bool,
2077    icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2078    trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2079    style: &<crate::Theme as StyleSheet>::Style,
2080    dnd_icon: bool,
2081    line_height: text::LineHeight,
2082    error: Option<&str>,
2083    label: Option<&str>,
2084    helper_text: Option<&str>,
2085    helper_text_size: f32,
2086    helper_line_height: text::LineHeight,
2087    viewport: &Rectangle,
2088    renderer_style: &renderer::Style,
2089) {
2090    // all children should be icon images
2091    let children = &tree.children;
2092
2093    let state = tree.state.downcast_ref::<State>();
2094    let secure_value = is_secure.then(|| value.secure());
2095    let value = secure_value.as_ref().unwrap_or(value);
2096
2097    let mut children_layout = layout.children();
2098
2099    let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2100        let label_layout = children_layout.next();
2101        let layout = children_layout.next().unwrap();
2102        let helper_text_layout = children_layout.next();
2103        (label_layout, layout, helper_text_layout)
2104    } else if label.is_some() {
2105        let label_layout = children_layout.next();
2106        let layout = children_layout.next().unwrap();
2107        (label_layout, layout, None)
2108    } else if helper_text.is_some() {
2109        let layout = children_layout.next().unwrap();
2110        let helper_text_layout = children_layout.next();
2111        (None, layout, helper_text_layout)
2112    } else {
2113        let layout = children_layout.next().unwrap();
2114
2115        (None, layout, None)
2116    };
2117
2118    let mut children_layout = layout.children();
2119    let bounds = layout.bounds();
2120    // XXX Dnd widget may not have a layout with children, so we just use the text_layout
2121    let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2122
2123    let is_mouse_over = cursor_position.is_over(bounds);
2124
2125    let appearance = if is_disabled {
2126        theme.disabled(style)
2127    } else if error.is_some() {
2128        theme.error(style)
2129    } else if state.is_focused() {
2130        theme.focused(style)
2131    } else if is_mouse_over {
2132        theme.hovered(style)
2133    } else {
2134        theme.active(style)
2135    };
2136
2137    let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2138    let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2139
2140    // TODO: iced will not render alpha itself on text or icon colors.
2141    if is_disabled {
2142        let background = theme.current_container().component.base.into();
2143        icon_color = icon_color.blend_alpha(background, 0.5);
2144        text_color = text_color.blend_alpha(background, 0.5);
2145    }
2146
2147    // draw background and its border
2148    if let Some(border_offset) = appearance.border_offset {
2149        let offset_bounds = Rectangle {
2150            x: bounds.x - border_offset,
2151            y: bounds.y - border_offset,
2152            width: border_offset.mul_add(2.0, bounds.width),
2153            height: border_offset.mul_add(2.0, bounds.height),
2154        };
2155        renderer.fill_quad(
2156            renderer::Quad {
2157                bounds,
2158                border: Border {
2159                    radius: appearance.border_radius,
2160                    width: appearance.border_width,
2161                    ..Default::default()
2162                },
2163                shadow: Shadow {
2164                    offset: Vector::new(0.0, 1.0),
2165                    color: Color::TRANSPARENT,
2166                    blur_radius: 0.0,
2167                },
2168            },
2169            appearance.background,
2170        );
2171        renderer.fill_quad(
2172            renderer::Quad {
2173                bounds: offset_bounds,
2174                border: Border {
2175                    width: appearance.border_width,
2176                    color: appearance.border_color,
2177                    radius: appearance.border_radius,
2178                },
2179                shadow: Shadow {
2180                    offset: Vector::new(0.0, 1.0),
2181                    color: Color::TRANSPARENT,
2182                    blur_radius: 0.0,
2183                },
2184            },
2185            Background::Color(Color::TRANSPARENT),
2186        );
2187    } else {
2188        renderer.fill_quad(
2189            renderer::Quad {
2190                bounds,
2191                border: Border {
2192                    width: appearance.border_width,
2193                    color: appearance.border_color,
2194                    radius: appearance.border_radius,
2195                },
2196                shadow: Shadow {
2197                    offset: Vector::new(0.0, 1.0),
2198                    color: Color::TRANSPARENT,
2199                    blur_radius: 0.0,
2200                },
2201            },
2202            appearance.background,
2203        );
2204    }
2205
2206    // draw the label if it exists
2207    if let (Some(label_layout), Some(label)) = (label_layout, label) {
2208        renderer.fill_text(
2209            Text {
2210                content: label.to_string(),
2211                size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2212                font: font.unwrap_or_else(|| renderer.default_font()),
2213                bounds: label_layout.bounds().size(),
2214                horizontal_alignment: alignment::Horizontal::Left,
2215                vertical_alignment: alignment::Vertical::Top,
2216                line_height,
2217                shaping: text::Shaping::Advanced,
2218                wrapping: text::Wrapping::None,
2219            },
2220            label_layout.bounds().position(),
2221            appearance.label_color,
2222            *viewport,
2223        );
2224    }
2225    let mut child_index = 0;
2226    let leading_icon_tree = children.get(child_index);
2227    // draw the start icon in the text input
2228    let has_start_icon = icon.is_some();
2229    if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2230        let mut children = text_layout.children();
2231        let _ = children.next().unwrap();
2232        let icon_layout = children.next().unwrap();
2233
2234        icon.as_widget().draw(
2235            tree,
2236            renderer,
2237            theme,
2238            &renderer::Style {
2239                icon_color,
2240                text_color,
2241                scale_factor: renderer_style.scale_factor,
2242            },
2243            icon_layout,
2244            cursor_position,
2245            viewport,
2246        );
2247        child_index += 1;
2248    }
2249
2250    let text = value.to_string();
2251    let font = font.unwrap_or_else(|| renderer.default_font());
2252    let size = size.unwrap_or_else(|| renderer.default_size().0);
2253    let text_width = state.value.min_width();
2254    let actual_width = text_width.max(text_bounds.width);
2255
2256    let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2257    #[cfg(feature = "wayland")]
2258    let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2259    #[cfg(not(feature = "wayland"))]
2260    let handling_dnd_offer = false;
2261    let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| {
2262        handling_dnd_offer.then(|| Focus {
2263            updated_at: Instant::now(),
2264            now: Instant::now(),
2265        })
2266    }) {
2267        match state.cursor.state(value) {
2268            cursor::State::Index(position) => {
2269                let (text_value_width, offset) =
2270                    measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);
2271
2272                let is_cursor_visible = handling_dnd_offer
2273                    || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2274                        % 2
2275                        == 0;
2276                if is_cursor_visible {
2277                    if dnd_icon {
2278                        (None, 0.0)
2279                    } else {
2280                        (
2281                            Some((
2282                                renderer::Quad {
2283                                    bounds: Rectangle {
2284                                        x: text_bounds.x + text_value_width - offset
2285                                            + if text_value_width < 0. {
2286                                                actual_width
2287                                            } else {
2288                                                0.
2289                                            },
2290                                        y: text_bounds.y,
2291                                        width: 1.0,
2292                                        height: text_bounds.height,
2293                                    },
2294                                    border: Border {
2295                                        width: 0.0,
2296                                        color: Color::TRANSPARENT,
2297                                        radius: radius_0,
2298                                    },
2299                                    shadow: Shadow {
2300                                        offset: Vector::ZERO,
2301                                        color: Color::TRANSPARENT,
2302                                        blur_radius: 0.0,
2303                                    },
2304                                },
2305                                text_color,
2306                            )),
2307                            offset,
2308                        )
2309                    }
2310                } else {
2311                    (None, offset)
2312                }
2313            }
2314            cursor::State::Selection { start, end } => {
2315                let left = start.min(end);
2316                let right = end.max(start);
2317
2318                let value_paragraph = &state.value;
2319                let (left_position, left_offset) =
2320                    measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left);
2321
2322                let (right_position, right_offset) =
2323                    measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right);
2324
2325                let width = right_position - left_position;
2326                if dnd_icon {
2327                    (None, 0.0)
2328                } else {
2329                    (
2330                        Some((
2331                            renderer::Quad {
2332                                bounds: Rectangle {
2333                                    x: text_bounds.x
2334                                        + left_position
2335                                        + if left_position < 0. || right_position < 0. {
2336                                            actual_width
2337                                        } else {
2338                                            0.
2339                                        },
2340                                    y: text_bounds.y,
2341                                    width,
2342                                    height: text_bounds.height,
2343                                },
2344                                border: Border {
2345                                    width: 0.0,
2346                                    color: Color::TRANSPARENT,
2347                                    radius: radius_0,
2348                                },
2349                                shadow: Shadow {
2350                                    offset: Vector::ZERO,
2351                                    color: Color::TRANSPARENT,
2352                                    blur_radius: 0.0,
2353                                },
2354                            },
2355                            appearance.selected_fill,
2356                        )),
2357                        if end == right {
2358                            right_offset
2359                        } else {
2360                            left_offset
2361                        },
2362                    )
2363                }
2364            }
2365        }
2366    } else {
2367        (None, 0.0)
2368    };
2369
2370    let render = |renderer: &mut crate::Renderer| {
2371        if let Some((cursor, color)) = cursor {
2372            renderer.fill_quad(cursor, color);
2373        } else {
2374            renderer.with_translation(Vector::ZERO, |_| {});
2375        }
2376
2377        let bounds = Rectangle {
2378            x: text_bounds.x - offset,
2379            y: text_bounds.center_y(),
2380            width: actual_width,
2381            ..text_bounds
2382        };
2383        let color = if text.is_empty() {
2384            appearance.placeholder_color
2385        } else {
2386            text_color
2387        };
2388
2389        renderer.fill_text(
2390            Text {
2391                content: if text.is_empty() {
2392                    placeholder.to_string()
2393                } else {
2394                    text.clone()
2395                },
2396                font,
2397                bounds: bounds.size(),
2398                size: iced::Pixels(size),
2399                horizontal_alignment: alignment::Horizontal::Left,
2400                vertical_alignment: alignment::Vertical::Center,
2401                line_height: text::LineHeight::default(),
2402                shaping: text::Shaping::Advanced,
2403                wrapping: text::Wrapping::None,
2404            },
2405            bounds.position(),
2406            color,
2407            *viewport,
2408        );
2409    };
2410
2411    renderer.with_layer(text_bounds, render);
2412
2413    let trailing_icon_tree = children.get(child_index);
2414
2415    // draw the end icon in the text input
2416    if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2417        let mut children = text_layout.children();
2418        let mut icon_layout = children.next().unwrap();
2419        if has_start_icon {
2420            icon_layout = children.next().unwrap();
2421        }
2422        icon_layout = children.next().unwrap();
2423
2424        icon.as_widget().draw(
2425            tree,
2426            renderer,
2427            theme,
2428            &renderer::Style {
2429                icon_color,
2430                text_color,
2431                scale_factor: renderer_style.scale_factor,
2432            },
2433            icon_layout,
2434            cursor_position,
2435            viewport,
2436        );
2437    }
2438
2439    // draw the helper text if it exists
2440    if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
2441        renderer.fill_text(
2442            Text {
2443                content: helper_text.to_string(), // TODO remove to_string?
2444                size: iced::Pixels(helper_text_size),
2445                font,
2446                bounds: helper_text_layout.bounds().size(),
2447                horizontal_alignment: alignment::Horizontal::Left,
2448                vertical_alignment: alignment::Vertical::Top,
2449                line_height: helper_line_height,
2450                shaping: text::Shaping::Advanced,
2451                wrapping: text::Wrapping::None,
2452            },
2453            helper_text_layout.bounds().position(),
2454            text_color,
2455            *viewport,
2456        );
2457    }
2458}
2459
2460/// Computes the current [`mouse::Interaction`] of the [`TextInput`].
2461#[must_use]
2462pub fn mouse_interaction(
2463    layout: Layout<'_>,
2464    cursor_position: mouse::Cursor,
2465    is_disabled: bool,
2466) -> mouse::Interaction {
2467    if cursor_position.is_over(layout.bounds()) {
2468        if is_disabled {
2469            mouse::Interaction::NotAllowed
2470        } else {
2471            mouse::Interaction::Text
2472        }
2473    } else {
2474        mouse::Interaction::default()
2475    }
2476}
2477
2478/// A string which can be sent to the clipboard or drag-and-dropped.
2479#[derive(Debug, Clone)]
2480pub struct TextInputString(pub String);
2481
2482#[cfg(feature = "wayland")]
2483impl AsMimeTypes for TextInputString {
2484    fn available(&self) -> Cow<'static, [String]> {
2485        Cow::Owned(
2486            SUPPORTED_TEXT_MIME_TYPES
2487                .iter()
2488                .cloned()
2489                .map(String::from)
2490                .collect::<Vec<_>>(),
2491        )
2492    }
2493
2494    fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
2495        if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
2496            Some(Cow::Owned(self.0.clone().as_bytes().to_vec()))
2497        } else {
2498            None
2499        }
2500    }
2501}
2502
2503#[derive(Debug, Clone, PartialEq, Eq)]
2504pub(crate) enum DraggingState {
2505    Selection,
2506    #[cfg(feature = "wayland")]
2507    Dnd(DndAction, String),
2508}
2509
2510#[cfg(feature = "wayland")]
2511#[derive(Debug, Default, Clone)]
2512pub(crate) enum DndOfferState {
2513    #[default]
2514    None,
2515    HandlingOffer(Vec<String>, DndAction),
2516    Dropped,
2517}
2518#[derive(Debug, Default, Clone)]
2519#[cfg(not(feature = "wayland"))]
2520pub(crate) struct DndOfferState;
2521
2522/// The state of a [`TextInput`].
2523#[derive(Debug, Default, Clone)]
2524#[must_use]
2525pub struct State {
2526    pub tracked_value: Value,
2527    pub value: crate::Plain,
2528    pub placeholder: crate::Plain,
2529    pub label: crate::Plain,
2530    pub helper_text: crate::Plain,
2531    pub dirty: bool,
2532    pub is_secure: bool,
2533    pub is_read_only: bool,
2534    pub emit_unfocus: bool,
2535    select_on_focus: bool,
2536    is_focused: Option<Focus>,
2537    dragging_state: Option<DraggingState>,
2538    dnd_offer: DndOfferState,
2539    is_pasting: Option<Value>,
2540    last_click: Option<mouse::Click>,
2541    cursor: Cursor,
2542    keyboard_modifiers: keyboard::Modifiers,
2543    // TODO: Add stateful horizontal scrolling offset
2544}
2545
2546#[derive(Debug, Clone, Copy)]
2547struct Focus {
2548    updated_at: Instant,
2549    now: Instant,
2550}
2551
2552impl State {
2553    /// Creates a new [`State`], representing an unfocused [`TextInput`].
2554    pub fn new(
2555        is_secure: bool,
2556        is_read_only: bool,
2557        always_active: bool,
2558        select_on_focus: bool,
2559    ) -> Self {
2560        Self {
2561            is_secure,
2562            is_read_only,
2563            is_focused: always_active.then(|| {
2564                let now = Instant::now();
2565                Focus {
2566                    updated_at: now,
2567                    now,
2568                }
2569            }),
2570            select_on_focus,
2571            ..Self::default()
2572        }
2573    }
2574
2575    /// Returns the current value of the selected text in the [`TextInput`].
2576    #[must_use]
2577    pub fn selected_text(&self, text: &str) -> Option<String> {
2578        let value = Value::new(text);
2579        match self.cursor.state(&value) {
2580            cursor::State::Index(_) => None,
2581            cursor::State::Selection { start, end } => {
2582                let left = start.min(end);
2583                let right = end.max(start);
2584                Some(text[left..right].to_string())
2585            }
2586        }
2587    }
2588
2589    #[cfg(feature = "wayland")]
2590    /// Returns the current value of the dragged text in the [`TextInput`].
2591    #[must_use]
2592    pub fn dragged_text(&self) -> Option<String> {
2593        match self.dragging_state.as_ref() {
2594            Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
2595            _ => None,
2596        }
2597    }
2598
2599    /// Creates a new [`State`], representing a focused [`TextInput`].
2600    pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
2601        Self {
2602            tracked_value: Value::default(),
2603            is_secure,
2604            value: crate::Plain::default(),
2605            placeholder: crate::Plain::default(),
2606            label: crate::Plain::default(),
2607            helper_text: crate::Plain::default(),
2608            is_read_only,
2609            emit_unfocus: false,
2610            is_focused: None,
2611            select_on_focus: false,
2612            dragging_state: None,
2613            dnd_offer: DndOfferState::default(),
2614            is_pasting: None,
2615            last_click: None,
2616            cursor: Cursor::default(),
2617            keyboard_modifiers: keyboard::Modifiers::default(),
2618            dirty: false,
2619        }
2620    }
2621
2622    /// Returns whether the [`TextInput`] is currently focused or not.
2623    #[inline]
2624    #[must_use]
2625    pub fn is_focused(&self) -> bool {
2626        self.is_focused.is_some()
2627    }
2628
2629    /// Returns the [`Cursor`] of the [`TextInput`].
2630    #[inline]
2631    #[must_use]
2632    pub fn cursor(&self) -> Cursor {
2633        self.cursor
2634    }
2635
2636    /// Focuses the [`TextInput`].
2637    #[cold]
2638    pub fn focus(&mut self) {
2639        let now = Instant::now();
2640        LAST_FOCUS_UPDATE.with(|x| x.set(now));
2641        self.is_read_only = false;
2642        self.is_focused = Some(Focus {
2643            updated_at: now,
2644            now,
2645        });
2646
2647        if self.select_on_focus {
2648            self.select_all()
2649        } else {
2650            self.move_cursor_to_end();
2651        }
2652    }
2653
2654    /// Unfocuses the [`TextInput`].
2655    #[cold]
2656    pub(super) fn unfocus(&mut self) {
2657        self.move_cursor_to_front();
2658        self.last_click = None;
2659        self.is_focused = None;
2660        self.dragging_state = None;
2661        self.is_pasting = None;
2662        self.keyboard_modifiers = keyboard::Modifiers::default();
2663    }
2664
2665    /// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
2666    #[inline]
2667    pub fn move_cursor_to_front(&mut self) {
2668        self.cursor.move_to(0);
2669    }
2670
2671    /// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
2672    #[inline]
2673    pub fn move_cursor_to_end(&mut self) {
2674        self.cursor.move_to(usize::MAX);
2675    }
2676
2677    /// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
2678    #[inline]
2679    pub fn move_cursor_to(&mut self, position: usize) {
2680        self.cursor.move_to(position);
2681    }
2682
2683    /// Selects all the content of the [`TextInput`].
2684    #[inline]
2685    pub fn select_all(&mut self) {
2686        self.cursor.select_range(0, usize::MAX);
2687    }
2688
2689    pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
2690        let position = if target > 0.0 {
2691            find_cursor_position(bounds, value, self, target)
2692        } else {
2693            None
2694        };
2695
2696        self.cursor.move_to(position.unwrap_or(0));
2697        self.dragging_state = Some(DraggingState::Selection);
2698    }
2699}
2700
2701impl operation::Focusable for State {
2702    #[inline]
2703    fn is_focused(&self) -> bool {
2704        Self::is_focused(self)
2705    }
2706
2707    #[inline]
2708    fn focus(&mut self) {
2709        Self::focus(self);
2710    }
2711
2712    #[inline]
2713    fn unfocus(&mut self) {
2714        Self::unfocus(self);
2715    }
2716}
2717
2718impl operation::TextInput for State {
2719    #[inline]
2720    fn move_cursor_to_front(&mut self) {
2721        Self::move_cursor_to_front(self);
2722    }
2723
2724    #[inline]
2725    fn move_cursor_to_end(&mut self) {
2726        Self::move_cursor_to_end(self);
2727    }
2728
2729    #[inline]
2730    fn move_cursor_to(&mut self, position: usize) {
2731        Self::move_cursor_to(self, position);
2732    }
2733
2734    #[inline]
2735    fn select_all(&mut self) {
2736        Self::select_all(self);
2737    }
2738}
2739
2740#[inline(never)]
2741fn measure_cursor_and_scroll_offset(
2742    paragraph: &impl text::Paragraph,
2743    text_bounds: Rectangle,
2744    cursor_index: usize,
2745) -> (f32, f32) {
2746    let grapheme_position = paragraph
2747        .grapheme_position(0, cursor_index)
2748        .unwrap_or(Point::ORIGIN);
2749
2750    let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
2751
2752    (grapheme_position.x, offset)
2753}
2754
2755/// Computes the position of the text cursor at the given X coordinate of
2756/// a [`TextInput`].
2757#[inline(never)]
2758fn find_cursor_position(
2759    text_bounds: Rectangle,
2760    value: &Value,
2761    state: &State,
2762    x: f32,
2763) -> Option<usize> {
2764    let offset = offset(text_bounds, value, state);
2765    let value = value.to_string();
2766
2767    let char_offset = state
2768        .value
2769        .raw()
2770        .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
2771        .map(text::Hit::cursor)?;
2772
2773    Some(
2774        unicode_segmentation::UnicodeSegmentation::graphemes(
2775            &value[..char_offset.min(value.len())],
2776            true,
2777        )
2778        .count(),
2779    )
2780}
2781
2782#[inline(never)]
2783fn replace_paragraph(
2784    state: &mut State,
2785    layout: Layout<'_>,
2786    value: &Value,
2787    font: <crate::Renderer as iced_core::text::Renderer>::Font,
2788    text_size: Pixels,
2789    line_height: text::LineHeight,
2790    limits: &layout::Limits,
2791) {
2792    let mut children_layout = layout.children();
2793    let text_bounds = children_layout.next().unwrap();
2794    let bounds = limits.resolve(
2795        Length::Shrink,
2796        Length::Fill,
2797        Size::new(0., text_bounds.bounds().height),
2798    );
2799
2800    state.value = crate::Plain::new(Text {
2801        font,
2802        line_height,
2803        content: &value.to_string(),
2804        bounds,
2805        size: text_size,
2806        horizontal_alignment: alignment::Horizontal::Left,
2807        vertical_alignment: alignment::Vertical::Top,
2808        shaping: text::Shaping::Advanced,
2809        wrapping: text::Wrapping::None,
2810    });
2811}
2812
2813const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
2814
2815mod platform {
2816    use iced_core::keyboard;
2817
2818    #[inline]
2819    pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
2820        if cfg!(target_os = "macos") {
2821            modifiers.alt()
2822        } else {
2823            modifiers.control()
2824        }
2825    }
2826}
2827
2828#[inline(never)]
2829fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
2830    if state.is_focused() {
2831        let cursor = state.cursor();
2832
2833        let focus_position = match cursor.state(value) {
2834            cursor::State::Index(i) => i,
2835            cursor::State::Selection { end, .. } => end,
2836        };
2837
2838        let (_, offset) =
2839            measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
2840
2841        offset
2842    } else {
2843        0.0
2844    }
2845}