Skip to main content

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