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