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