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