cosmic/widget/text_input/
input.rs

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