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