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 shell.publish(on_unfocus.clone());
886 }
887 }
888
889 let dnd_id = self.dnd_id();
890 let id = Widget::id(self);
891 update(
892 id,
893 event,
894 text_layout.children().next().unwrap(),
895 trailing_icon_layout,
896 cursor_position,
897 clipboard,
898 shell,
899 &mut self.value,
900 size,
901 font,
902 self.is_editable_variant,
903 self.is_secure,
904 self.on_focus.as_ref(),
905 self.on_unfocus.as_ref(),
906 self.on_input.as_deref(),
907 self.on_paste.as_deref(),
908 self.on_submit.as_deref(),
909 self.on_tab.as_ref(),
910 self.on_toggle_edit.as_deref(),
911 || tree.state.downcast_mut::<State>(),
912 self.on_create_dnd_source.as_deref(),
913 dnd_id,
914 line_height,
915 layout,
916 self.manage_value,
917 )
918 }
919
920 #[inline]
921 fn draw(
922 &self,
923 tree: &Tree,
924 renderer: &mut crate::Renderer,
925 theme: &crate::Theme,
926 style: &renderer::Style,
927 layout: Layout<'_>,
928 cursor_position: mouse::Cursor,
929 viewport: &Rectangle,
930 ) {
931 let text_layout = self.text_layout(layout);
932 draw(
933 renderer,
934 theme,
935 layout,
936 text_layout,
937 cursor_position,
938 tree,
939 &self.value,
940 &self.placeholder,
941 self.size,
942 self.font,
943 self.on_input.is_none() && !self.manage_value,
944 self.is_secure,
945 self.leading_icon.as_ref(),
946 self.trailing_icon.as_ref(),
947 &self.style,
948 self.dnd_icon,
949 self.line_height,
950 self.error.as_deref(),
951 self.label.as_deref(),
952 self.helper_text.as_deref(),
953 self.helper_size,
954 self.helper_line_height,
955 viewport,
956 style,
957 );
958 }
959
960 fn mouse_interaction(
961 &self,
962 state: &Tree,
963 layout: Layout<'_>,
964 cursor_position: mouse::Cursor,
965 viewport: &Rectangle,
966 renderer: &crate::Renderer,
967 ) -> mouse::Interaction {
968 let layout = self.text_layout(layout);
969 let mut index = 0;
970 if let (Some(leading_icon), Some(tree)) =
971 (self.leading_icon.as_ref(), state.children.get(index))
972 {
973 let leading_icon_layout = layout.children().nth(1).unwrap();
974
975 if cursor_position.is_over(leading_icon_layout.bounds()) {
976 return leading_icon.as_widget().mouse_interaction(
977 tree,
978 layout,
979 cursor_position,
980 viewport,
981 renderer,
982 );
983 }
984 index += 1;
985 }
986
987 if let (Some(trailing_icon), Some(tree)) =
988 (self.trailing_icon.as_ref(), state.children.get(index))
989 {
990 let mut children = layout.children();
991 children.next();
992 if self.leading_icon.is_some() {
994 children.next();
995 }
996 let trailing_icon_layout = children.next().unwrap();
997
998 if cursor_position.is_over(trailing_icon_layout.bounds()) {
999 return trailing_icon.as_widget().mouse_interaction(
1000 tree,
1001 layout,
1002 cursor_position,
1003 viewport,
1004 renderer,
1005 );
1006 }
1007 }
1008 let mut children = layout.children();
1009 let layout = children.next().unwrap();
1010 mouse_interaction(
1011 layout,
1012 cursor_position,
1013 self.on_input.is_none() && !self.manage_value,
1014 )
1015 }
1016
1017 #[inline]
1018 fn id(&self) -> Option<Id> {
1019 Some(self.id.clone())
1020 }
1021
1022 #[inline]
1023 fn set_id(&mut self, id: Id) {
1024 self.id = id;
1025 }
1026
1027 fn drag_destinations(
1028 &self,
1029 _state: &Tree,
1030 layout: Layout<'_>,
1031 _renderer: &crate::Renderer,
1032 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1033 ) {
1034 if let Some(input) = layout.children().last() {
1035 let Rectangle {
1036 x,
1037 y,
1038 width,
1039 height,
1040 } = input.bounds();
1041 dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
1042 id: self.dnd_id(),
1043 rectangle: iced::clipboard::dnd::Rectangle {
1044 x: x as f64,
1045 y: y as f64,
1046 width: width as f64,
1047 height: height as f64,
1048 },
1049 mime_types: SUPPORTED_TEXT_MIME_TYPES
1050 .iter()
1051 .map(|s| Cow::Borrowed(*s))
1052 .collect(),
1053 actions: DndAction::Move,
1054 preferred: DndAction::Move,
1055 });
1056 }
1057 }
1058}
1059
1060impl<'a, Message> From<TextInput<'a, Message>>
1061 for Element<'a, Message, crate::Theme, crate::Renderer>
1062where
1063 Message: 'static + Clone,
1064{
1065 fn from(
1066 text_input: TextInput<'a, Message>,
1067 ) -> Element<'a, Message, crate::Theme, crate::Renderer> {
1068 Element::new(text_input)
1069 }
1070}
1071
1072pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
1074 task::effect(Action::widget(operation::focusable::focus(id)))
1075}
1076
1077pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
1080 task::effect(Action::widget(operation::text_input::move_cursor_to_end(
1081 id,
1082 )))
1083}
1084
1085pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
1088 task::effect(Action::widget(operation::text_input::move_cursor_to_front(
1089 id,
1090 )))
1091}
1092
1093pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
1096 task::effect(Action::widget(operation::text_input::move_cursor_to(
1097 id, position,
1098 )))
1099}
1100
1101pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
1103 task::effect(Action::widget(operation::text_input::select_all(id)))
1104}
1105
1106#[allow(clippy::cast_precision_loss)]
1108#[allow(clippy::too_many_arguments)]
1109#[allow(clippy::too_many_lines)]
1110pub fn layout<Message>(
1111 renderer: &crate::Renderer,
1112 limits: &layout::Limits,
1113 width: Length,
1114 padding: Padding,
1115 size: Option<f32>,
1116 leading_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
1117 trailing_icon: Option<&Element<'_, Message, crate::Theme, crate::Renderer>>,
1118 line_height: text::LineHeight,
1119 label: Option<&str>,
1120 helper_text: Option<&str>,
1121 helper_text_size: f32,
1122 helper_text_line_height: text::LineHeight,
1123 font: iced_core::Font,
1124 tree: &mut Tree,
1125) -> layout::Node {
1126 let limits = limits.width(width);
1127 let spacing = THEME.lock().unwrap().cosmic().space_xxs();
1128 let mut nodes = Vec::with_capacity(3);
1129
1130 let text_pos = if let Some(label) = label {
1131 let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
1132 let state = tree.state.downcast_mut::<State>();
1133 let label_paragraph = &mut state.label;
1134 label_paragraph.update(Text {
1135 content: label,
1136 font,
1137 bounds: text_bounds,
1138 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
1139 horizontal_alignment: alignment::Horizontal::Left,
1140 vertical_alignment: alignment::Vertical::Center,
1141 line_height,
1142 shaping: text::Shaping::Advanced,
1143 wrapping: text::Wrapping::None,
1144 });
1145 let label_size = label_paragraph.min_bounds();
1146
1147 nodes.push(layout::Node::new(label_size));
1148 Vector::new(0.0, label_size.height + f32::from(spacing))
1149 } else {
1150 Vector::ZERO
1151 };
1152
1153 let text_size = size.unwrap_or_else(|| renderer.default_size().0);
1154 let mut text_input_height = line_height.to_absolute(text_size.into()).0;
1155 let padding = padding.fit(Size::ZERO, limits.max());
1156
1157 let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() {
1158 let children = &mut tree.children;
1159 let limits_copy = limits;
1161
1162 let limits = limits.shrink(padding);
1163 let icon_spacing = 8.0;
1164 let mut c_i = 0;
1165 let (leading_icon_width, mut leading_icon) =
1166 if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) {
1167 let size = icon.as_widget().size();
1168 let icon_node = icon.as_widget().layout(
1169 tree,
1170 renderer,
1171 &Limits::NONE.width(size.width).height(size.height),
1172 );
1173 text_input_height = text_input_height.max(icon_node.bounds().height);
1174 c_i += 1;
1175 (icon_node.bounds().width + icon_spacing, Some(icon_node))
1176 } else {
1177 (0.0, None)
1178 };
1179
1180 let (trailing_icon_width, mut trailing_icon) =
1181 if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) {
1182 let size = icon.as_widget().size();
1183 let icon_node = icon.as_widget().layout(
1184 tree,
1185 renderer,
1186 &Limits::NONE.width(size.width).height(size.height),
1187 );
1188 text_input_height = text_input_height.max(icon_node.bounds().height);
1189 (icon_node.bounds().width + icon_spacing, Some(icon_node))
1190 } else {
1191 (0.0, None)
1192 };
1193 let text_limits = limits
1194 .width(width)
1195 .height(line_height.to_absolute(text_size.into()));
1196 let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
1197 let text_node = layout::Node::new(
1198 text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
1199 )
1200 .move_to(Point::new(
1201 padding.left + leading_icon_width,
1202 padding.top
1203 + ((text_input_height - line_height.to_absolute(text_size.into()).0) / 2.0)
1204 .max(0.0),
1205 ));
1206 let mut node_list: Vec<_> = Vec::with_capacity(3);
1207
1208 let text_node_bounds = text_node.bounds();
1209 node_list.push(text_node);
1210
1211 if let Some(leading_icon) = leading_icon.take() {
1212 node_list.push(leading_icon.clone().move_to(Point::new(
1213 padding.left,
1214 padding.top + ((text_input_height - leading_icon.bounds().height) / 2.0).max(0.0),
1215 )));
1216 }
1217 if let Some(trailing_icon) = trailing_icon.take() {
1218 let trailing_icon = trailing_icon.clone().move_to(Point::new(
1219 text_node_bounds.x + text_node_bounds.width + f32::from(spacing),
1220 padding.top + ((text_input_height - trailing_icon.bounds().height) / 2.0).max(0.0),
1221 ));
1222 node_list.push(trailing_icon);
1223 }
1224
1225 let text_input_size = Size::new(
1226 text_node_bounds.x + text_node_bounds.width + trailing_icon_width,
1227 text_input_height,
1228 )
1229 .expand(padding);
1230
1231 let input_limits = limits_copy
1232 .width(width)
1233 .height(text_input_height.max(text_input_size.height))
1234 .min_width(text_input_size.width);
1235 let input_bounds = input_limits.resolve(
1236 width,
1237 text_input_height.max(text_input_size.height),
1238 text_input_size,
1239 );
1240 let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos);
1241 let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing);
1242 nodes.push(input_node);
1243
1244 Vector::new(0.0, y_pos)
1245 } else {
1246 let limits = limits
1247 .width(width)
1248 .height(text_input_height + padding.vertical())
1249 .shrink(padding);
1250 let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITY);
1251
1252 let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top));
1253
1254 let node = layout::Node::with_children(text_bounds.expand(padding), vec![text])
1255 .translate(text_pos);
1256 let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
1257
1258 nodes.push(node);
1259
1260 Vector::new(0.0, y_pos)
1261 };
1262
1263 if let Some(helper_text) = helper_text {
1264 let limits = limits
1265 .width(width)
1266 .shrink(padding)
1267 .height(helper_text_line_height.to_absolute(helper_text_size.into()));
1268 let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITY);
1269 let state = tree.state.downcast_mut::<State>();
1270 let helper_text_paragraph = &mut state.helper_text;
1271 helper_text_paragraph.update(Text {
1272 content: helper_text,
1273 font,
1274 bounds: text_bounds,
1275 size: iced::Pixels(helper_text_size),
1276 horizontal_alignment: alignment::Horizontal::Left,
1277 vertical_alignment: alignment::Vertical::Center,
1278 line_height: helper_text_line_height,
1279 shaping: text::Shaping::Advanced,
1280 wrapping: text::Wrapping::None,
1281 });
1282 let helper_text_size = helper_text_paragraph.min_bounds();
1283 let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
1284 nodes.push(helper_text_node);
1285 };
1286
1287 let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
1288 Size::new(
1289 size.width.max(node.bounds().width),
1290 size.height + node.bounds().height,
1291 )
1292 });
1293 size.height += (nodes.len() - 1) as f32 * f32::from(spacing);
1294
1295 let limits = limits
1296 .width(width)
1297 .height(size.height)
1298 .min_width(size.width);
1299
1300 layout::Node::with_children(limits.resolve(width, size.height, size), nodes)
1301}
1302
1303#[allow(clippy::too_many_arguments)]
1307#[allow(clippy::too_many_lines)]
1308#[allow(clippy::missing_panics_doc)]
1309#[allow(clippy::cast_lossless)]
1310#[allow(clippy::cast_possible_truncation)]
1311pub fn update<'a, Message: Clone + 'static>(
1312 id: Option<Id>,
1313 event: Event,
1314 text_layout: Layout<'_>,
1315 edit_button_layout: Option<Layout<'_>>,
1316 cursor: mouse::Cursor,
1317 clipboard: &mut dyn Clipboard,
1318 shell: &mut Shell<'_, Message>,
1319 value: &mut Value,
1320 size: f32,
1321 font: <crate::Renderer as iced_core::text::Renderer>::Font,
1322 is_editable_variant: bool,
1323 is_secure: bool,
1324 on_focus: Option<&Message>,
1325 on_unfocus: Option<&Message>,
1326 on_input: Option<&dyn Fn(String) -> Message>,
1327 on_paste: Option<&dyn Fn(String) -> Message>,
1328 on_submit: Option<&dyn Fn(String) -> Message>,
1329 on_tab: Option<&Message>,
1330 on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
1331 state: impl FnOnce() -> &'a mut State,
1332 #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
1333 #[allow(unused_variables)] dnd_id: u128,
1334 line_height: text::LineHeight,
1335 layout: Layout<'_>,
1336 manage_value: bool,
1337) -> event::Status {
1338 let update_cache = |state, value| {
1339 replace_paragraph(
1340 state,
1341 layout,
1342 value,
1343 font,
1344 iced::Pixels(size),
1345 line_height,
1346 &Limits::NONE.max_width(text_layout.bounds().width),
1347 );
1348 };
1349
1350 let mut secured_value = if is_secure {
1351 value.secure()
1352 } else {
1353 value.clone()
1354 };
1355 let unsecured_value = value;
1356 let value = &mut secured_value;
1357
1358 #[inline]
1362 #[cold]
1363 fn cold() {}
1364
1365 match event {
1366 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
1367 | Event::Touch(touch::Event::FingerPressed { .. }) => {
1368 cold();
1369 let state = state();
1370
1371 let click_position = if on_input.is_some() || manage_value {
1372 cursor.position_over(layout.bounds())
1373 } else {
1374 None
1375 };
1376
1377 if let Some(cursor_position) = click_position {
1378 if state.dragging_state == None
1380 && edit_button_layout.map_or(false, |l| cursor.is_over(l.bounds()))
1381 {
1382 if is_editable_variant {
1383 state.is_read_only = !state.is_read_only;
1384 state.move_cursor_to_end();
1385
1386 if let Some(on_toggle_edit) = on_toggle_edit {
1387 shell.publish(on_toggle_edit(!state.is_read_only));
1388 }
1389
1390 let now = Instant::now();
1391 LAST_FOCUS_UPDATE.with(|x| x.set(now));
1392 state.is_focused = Some(Focus {
1393 updated_at: now,
1394 now,
1395 });
1396 }
1397
1398 return event::Status::Captured;
1399 }
1400
1401 let target = cursor_position.x - text_layout.bounds().x;
1402
1403 let click =
1404 mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
1405
1406 match (
1407 &state.dragging_state,
1408 click.kind(),
1409 state.cursor().state(value),
1410 ) {
1411 #[cfg(feature = "wayland")]
1412 (None, click::Kind::Single, cursor::State::Selection { start, end }) => {
1413 if on_input.is_some() || manage_value {
1418 let left = start.min(end);
1419 let right = end.max(start);
1420
1421 let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
1422 state.value.raw(),
1423 text_layout.bounds(),
1424 left,
1425 );
1426
1427 let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
1428 state.value.raw(),
1429 text_layout.bounds(),
1430 right,
1431 );
1432
1433 let width = right_position - left_position;
1434 let selection_bounds = Rectangle {
1435 x: text_layout.bounds().x + left_position,
1436 y: text_layout.bounds().y,
1437 width,
1438 height: text_layout.bounds().height,
1439 };
1440
1441 if cursor.is_over(selection_bounds) {
1442 if is_secure {
1444 return event::Status::Ignored;
1445 }
1446 let input_text =
1447 state.selected_text(&value.to_string()).unwrap_or_default();
1448 state.dragging_state = Some(DraggingState::Dnd(
1449 DndAction::empty(),
1450 input_text.clone(),
1451 ));
1452 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1453 editor.delete();
1454
1455 let contents = editor.contents();
1456 let unsecured_value = Value::new(&contents);
1457 state.tracked_value = unsecured_value.clone();
1458 if let Some(on_input) = on_input {
1459 let message = (on_input)(contents);
1460 shell.publish(message);
1461 }
1462 if let Some(on_start_dnd) = on_start_dnd_source {
1463 shell.publish(on_start_dnd(state.clone()));
1464 }
1465 let state_clone = state.clone();
1466
1467 iced_core::clipboard::start_dnd(
1468 clipboard,
1469 false,
1470 id.map(iced_core::clipboard::DndSource::Widget),
1471 Some(iced_core::clipboard::IconSurface::new(
1472 Element::from(
1473 TextInput::<'static, ()>::new("", input_text.clone())
1474 .dnd_icon(true),
1475 ),
1476 iced_core::widget::tree::State::new(state_clone),
1477 Vector::ZERO,
1478 )),
1479 Box::new(TextInputString(input_text)),
1480 DndAction::Move,
1481 );
1482
1483 update_cache(state, &unsecured_value);
1484 } else {
1485 update_cache(state, value);
1486 state.setting_selection(value, text_layout.bounds(), target);
1487 }
1488 } else {
1489 state.setting_selection(value, text_layout.bounds(), target);
1490 }
1491 }
1492 (None, click::Kind::Single, _) => {
1493 state.setting_selection(value, text_layout.bounds(), target);
1494 }
1495 (None | Some(DraggingState::Selection), click::Kind::Double, _) => {
1496 update_cache(state, value);
1497
1498 if is_secure {
1499 state.cursor.select_all(value);
1500 } else {
1501 let position =
1502 find_cursor_position(text_layout.bounds(), value, state, target)
1503 .unwrap_or(0);
1504
1505 state.cursor.select_range(
1506 value.previous_start_of_word(position),
1507 value.next_end_of_word(position),
1508 );
1509 }
1510 state.dragging_state = Some(DraggingState::Selection);
1511 }
1512 (None | Some(DraggingState::Selection), click::Kind::Triple, _) => {
1513 update_cache(state, value);
1514 state.cursor.select_all(value);
1515 state.dragging_state = Some(DraggingState::Selection);
1516 }
1517 _ => {
1518 state.dragging_state = None;
1519 }
1520 }
1521
1522 if state.is_focused.is_none()
1524 && matches!(state.dragging_state, None | Some(DraggingState::Selection))
1525 {
1526 if let Some(on_focus) = on_focus {
1527 shell.publish(on_focus.clone());
1528 }
1529
1530 if state.is_read_only {
1531 state.is_read_only = false;
1532 if let Some(on_toggle_edit) = on_toggle_edit {
1533 let message = (on_toggle_edit)(true);
1534 shell.publish(message);
1535 }
1536 }
1537
1538 let now = Instant::now();
1539 LAST_FOCUS_UPDATE.with(|x| x.set(now));
1540
1541 state.is_focused = Some(Focus {
1542 updated_at: now,
1543 now,
1544 });
1545 }
1546
1547 state.last_click = Some(click);
1548
1549 return event::Status::Captured;
1550 } else {
1551 state.unfocus();
1552
1553 if let Some(on_unfocus) = on_unfocus {
1554 shell.publish(on_unfocus.clone());
1555 }
1556 }
1557 }
1558 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
1559 | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
1560 cold();
1561 let state = state();
1562 state.dragging_state = None;
1563
1564 return if cursor.is_over(layout.bounds()) {
1565 event::Status::Captured
1566 } else {
1567 event::Status::Ignored
1568 };
1569 }
1570 Event::Mouse(mouse::Event::CursorMoved { position })
1571 | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
1572 let state = state();
1573
1574 if matches!(state.dragging_state, Some(DraggingState::Selection)) {
1575 let target = position.x - text_layout.bounds().x;
1576
1577 update_cache(state, value);
1578 let position =
1579 find_cursor_position(text_layout.bounds(), value, state, target).unwrap_or(0);
1580
1581 state
1582 .cursor
1583 .select_range(state.cursor.start(value), position);
1584
1585 return event::Status::Captured;
1586 }
1587 }
1588 Event::Keyboard(keyboard::Event::KeyPressed {
1589 key,
1590 text,
1591 physical_key,
1592 ..
1593 }) => {
1594 let state = state();
1595
1596 if let Some(focus) = &mut state.is_focused {
1597 if state.is_read_only || (!manage_value && on_input.is_none()) {
1598 return event::Status::Ignored;
1599 };
1600
1601 let modifiers = state.keyboard_modifiers;
1602 focus.updated_at = Instant::now();
1603 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1604
1605 if state.keyboard_modifiers.command() {
1607 match key.as_ref() {
1608 keyboard::Key::Character("c") => {
1609 if !is_secure {
1610 if let Some((start, end)) = state.cursor.selection(value) {
1611 clipboard.write(
1612 iced_core::clipboard::Kind::Standard,
1613 value.select(start, end).to_string(),
1614 );
1615 }
1616 }
1617 }
1618 keyboard::Key::Character("x") => {
1621 if !is_secure {
1622 if let Some((start, end)) = state.cursor.selection(value) {
1623 clipboard.write(
1624 iced_core::clipboard::Kind::Standard,
1625 value.select(start, end).to_string(),
1626 );
1627 }
1628
1629 let mut editor = Editor::new(value, &mut state.cursor);
1630 editor.delete();
1631 let content = editor.contents();
1632 state.tracked_value = Value::new(&content);
1633 if let Some(on_input) = on_input {
1634 let message = (on_input)(content);
1635 shell.publish(message);
1636 }
1637 }
1638 }
1639 keyboard::Key::Character("v") => {
1640 let content = if let Some(content) = state.is_pasting.take() {
1641 content
1642 } else {
1643 let content: String = clipboard
1644 .read(iced_core::clipboard::Kind::Standard)
1645 .unwrap_or_default()
1646 .chars()
1647 .filter(|c| !c.is_control())
1648 .collect();
1649
1650 Value::new(&content)
1651 };
1652
1653 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1654
1655 editor.paste(content.clone());
1656
1657 let contents = editor.contents();
1658 let unsecured_value = Value::new(&contents);
1659 state.tracked_value = unsecured_value.clone();
1660
1661 if let Some(on_input) = on_input {
1662 let message = if let Some(paste) = &on_paste {
1663 (paste)(contents)
1664 } else {
1665 (on_input)(contents)
1666 };
1667
1668 shell.publish(message);
1669 }
1670
1671 state.is_pasting = Some(content);
1672
1673 let value = if is_secure {
1674 unsecured_value.secure()
1675 } else {
1676 unsecured_value
1677 };
1678
1679 update_cache(state, &value);
1680 return event::Status::Captured;
1681 }
1682
1683 keyboard::Key::Character("a") => {
1684 state.cursor.select_all(value);
1685 return event::Status::Captured;
1686 }
1687
1688 _ => {}
1689 }
1690 }
1691
1692 if let Some(c) = text.and_then(|t| t.chars().next().filter(|c| !c.is_control())) {
1694 if state.is_read_only || (!manage_value && on_input.is_none()) {
1695 return event::Status::Ignored;
1696 };
1697
1698 state.is_pasting = None;
1699
1700 if !state.keyboard_modifiers.command() && !modifiers.control() {
1701 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1702
1703 editor.insert(c);
1704
1705 let contents = editor.contents();
1706 let unsecured_value = Value::new(&contents);
1707 state.tracked_value = unsecured_value.clone();
1708
1709 if let Some(on_input) = on_input {
1710 let message = (on_input)(contents);
1711 shell.publish(message);
1712 }
1713
1714 focus.updated_at = Instant::now();
1715 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1716
1717 let value = if is_secure {
1718 unsecured_value.secure()
1719 } else {
1720 unsecured_value
1721 };
1722
1723 update_cache(state, &value);
1724
1725 return event::Status::Captured;
1726 }
1727 }
1728
1729 match key.as_ref() {
1730 keyboard::Key::Named(keyboard::key::Named::Enter) => {
1731 if let Some(on_submit) = on_submit {
1732 shell.publish((on_submit)(unsecured_value.to_string()));
1733 }
1734 }
1735 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
1736 if platform::is_jump_modifier_pressed(modifiers)
1737 && state.cursor.selection(value).is_none()
1738 {
1739 if is_secure {
1740 let cursor_pos = state.cursor.end(value);
1741 state.cursor.select_range(0, cursor_pos);
1742 } else {
1743 state.cursor.select_left_by_words(value);
1744 }
1745 }
1746
1747 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1748 editor.backspace();
1749
1750 let contents = editor.contents();
1751 let unsecured_value = Value::new(&contents);
1752 state.tracked_value = unsecured_value.clone();
1753 if let Some(on_input) = on_input {
1754 let message = (on_input)(editor.contents());
1755 shell.publish(message);
1756 }
1757 let value = if is_secure {
1758 unsecured_value.secure()
1759 } else {
1760 unsecured_value
1761 };
1762 update_cache(state, &value);
1763 }
1764 keyboard::Key::Named(keyboard::key::Named::Delete) => {
1765 if platform::is_jump_modifier_pressed(modifiers)
1766 && state.cursor.selection(value).is_none()
1767 {
1768 if is_secure {
1769 let cursor_pos = state.cursor.end(unsecured_value);
1770 state.cursor.select_range(cursor_pos, unsecured_value.len());
1771 } else {
1772 state.cursor.select_right_by_words(unsecured_value);
1773 }
1774 }
1775
1776 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1777 editor.delete();
1778 let contents = editor.contents();
1779 let unsecured_value = Value::new(&contents);
1780 if let Some(on_input) = on_input {
1781 let message = (on_input)(contents);
1782 state.tracked_value = unsecured_value.clone();
1783 shell.publish(message);
1784 }
1785
1786 let value = if is_secure {
1787 unsecured_value.secure()
1788 } else {
1789 unsecured_value
1790 };
1791
1792 update_cache(state, &value);
1793 }
1794 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
1795 if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
1796 if modifiers.shift() {
1797 state.cursor.select_left_by_words(value);
1798 } else {
1799 state.cursor.move_left_by_words(value);
1800 }
1801 } else if modifiers.shift() {
1802 state.cursor.select_left(value);
1803 } else {
1804 state.cursor.move_left(value);
1805 }
1806 }
1807 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
1808 if platform::is_jump_modifier_pressed(modifiers) && !is_secure {
1809 if modifiers.shift() {
1810 state.cursor.select_right_by_words(value);
1811 } else {
1812 state.cursor.move_right_by_words(value);
1813 }
1814 } else if modifiers.shift() {
1815 state.cursor.select_right(value);
1816 } else {
1817 state.cursor.move_right(value);
1818 }
1819 }
1820 keyboard::Key::Named(keyboard::key::Named::Home) => {
1821 if modifiers.shift() {
1822 state.cursor.select_range(state.cursor.start(value), 0);
1823 } else {
1824 state.cursor.move_to(0);
1825 }
1826 }
1827 keyboard::Key::Named(keyboard::key::Named::End) => {
1828 if modifiers.shift() {
1829 state
1830 .cursor
1831 .select_range(state.cursor.start(value), value.len());
1832 } else {
1833 state.cursor.move_to(value.len());
1834 }
1835 }
1836 keyboard::Key::Named(keyboard::key::Named::Escape) => {
1837 state.unfocus();
1838 state.is_read_only = true;
1839
1840 if let Some(on_unfocus) = on_unfocus {
1841 shell.publish(on_unfocus.clone());
1842 }
1843 }
1844
1845 keyboard::Key::Named(keyboard::key::Named::Tab) => {
1846 if let Some(on_tab) = on_tab {
1847 shell.publish(on_tab.clone());
1851 } else {
1852 state.is_read_only = true;
1853
1854 if let Some(on_unfocus) = on_unfocus {
1855 shell.publish(on_unfocus.clone());
1856 }
1857
1858 return event::Status::Ignored;
1859 };
1860 }
1861
1862 keyboard::Key::Named(
1863 keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
1864 ) => {
1865 return event::Status::Ignored;
1866 }
1867 _ => {}
1868 }
1869
1870 return event::Status::Captured;
1871 }
1872 }
1873 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
1874 let state = state();
1875
1876 if state.is_focused.is_some() {
1877 match key {
1878 keyboard::Key::Character(c) if "v" == c => {
1879 state.is_pasting = None;
1880 }
1881 keyboard::Key::Named(keyboard::key::Named::Tab)
1882 | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
1883 | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
1884 return event::Status::Ignored;
1885 }
1886 _ => {}
1887 }
1888
1889 return event::Status::Captured;
1890 }
1891 }
1892 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
1893 let state = state();
1894
1895 state.keyboard_modifiers = modifiers;
1896 }
1897 Event::Window(window::Event::RedrawRequested(now)) => {
1898 let state = state();
1899
1900 if let Some(focus) = &mut state.is_focused {
1901 focus.now = now;
1902
1903 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
1904 - (now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
1905
1906 shell.request_redraw(window::RedrawRequest::At(
1907 now + Duration::from_millis(u64::try_from(millis_until_redraw).unwrap()),
1908 ));
1909 }
1910 }
1911 #[cfg(feature = "wayland")]
1912 Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
1913 cold();
1914 let state = state();
1915 if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
1916 state.dragging_state = None;
1918 return event::Status::Captured;
1919 }
1920 }
1921 #[cfg(feature = "wayland")]
1922 Event::Dnd(DndEvent::Offer(
1923 rectangle,
1924 OfferEvent::Enter {
1925 x,
1926 y,
1927 mime_types,
1928 surface,
1929 },
1930 )) if rectangle == Some(dnd_id) => {
1931 cold();
1932 let state = state();
1933 let is_clicked = text_layout.bounds().contains(Point {
1934 x: x as f32,
1935 y: y as f32,
1936 });
1937
1938 let mut accepted = false;
1939 for m in &mime_types {
1940 if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
1941 let clone = m.clone();
1942 accepted = true;
1943 }
1944 }
1945 if accepted {
1946 let target = x as f32 - text_layout.bounds().x;
1947 state.dnd_offer =
1948 DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
1949 let position = if target > 0.0 {
1951 update_cache(state, value);
1952 find_cursor_position(text_layout.bounds(), value, state, target)
1953 } else {
1954 None
1955 };
1956
1957 state.cursor.move_to(position.unwrap_or(0));
1958 return event::Status::Captured;
1959 }
1960 }
1961 #[cfg(feature = "wayland")]
1962 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
1963 if rectangle == Some(dnd_id) =>
1964 {
1965 let state = state();
1966
1967 let target = x as f32 - text_layout.bounds().x;
1968 let position = if target > 0.0 {
1970 update_cache(state, value);
1971 find_cursor_position(text_layout.bounds(), value, state, target)
1972 } else {
1973 None
1974 };
1975
1976 state.cursor.move_to(position.unwrap_or(0));
1977 return event::Status::Captured;
1978 }
1979 #[cfg(feature = "wayland")]
1980 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
1981 cold();
1982 let state = state();
1983 if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
1984 let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
1985 .iter()
1986 .find(|m| mime_types.contains(&(**m).to_string()))
1987 else {
1988 state.dnd_offer = DndOfferState::None;
1989 return event::Status::Captured;
1990 };
1991 state.dnd_offer = DndOfferState::Dropped;
1992 }
1993
1994 return event::Status::Ignored;
1995 }
1996 #[cfg(feature = "wayland")]
1997 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != id => {}
1998 #[cfg(feature = "wayland")]
1999 Event::Dnd(DndEvent::Offer(
2000 rectangle,
2001 OfferEvent::Leave | OfferEvent::LeaveDestination,
2002 )) => {
2003 cold();
2004 let state = state();
2005 match state.dnd_offer {
2008 DndOfferState::Dropped => {}
2009 _ => {
2010 state.dnd_offer = DndOfferState::None;
2011 }
2012 };
2013 return event::Status::Captured;
2014 }
2015 #[cfg(feature = "wayland")]
2016 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2017 if rectangle == Some(dnd_id) =>
2018 {
2019 cold();
2020 let state = state();
2021 if let DndOfferState::Dropped = state.dnd_offer.clone() {
2022 state.dnd_offer = DndOfferState::None;
2023 if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2024 return event::Status::Captured;
2025 }
2026 let Ok(content) = String::from_utf8(data) else {
2027 return event::Status::Captured;
2028 };
2029
2030 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2031
2032 editor.paste(Value::new(content.as_str()));
2033 let contents = editor.contents();
2034 let unsecured_value = Value::new(&contents);
2035 state.tracked_value = unsecured_value.clone();
2036 if let Some(on_paste) = on_paste.as_ref() {
2037 let message = (on_paste)(contents);
2038 shell.publish(message);
2039 }
2040
2041 let value = if is_secure {
2042 unsecured_value.secure()
2043 } else {
2044 unsecured_value
2045 };
2046 update_cache(state, &value);
2047 return event::Status::Captured;
2048 }
2049 return event::Status::Ignored;
2050 }
2051 _ => {}
2052 }
2053
2054 event::Status::Ignored
2055}
2056
2057#[allow(clippy::too_many_arguments)]
2062#[allow(clippy::too_many_lines)]
2063#[allow(clippy::missing_panics_doc)]
2064pub fn draw<'a, Message>(
2065 renderer: &mut crate::Renderer,
2066 theme: &crate::Theme,
2067 layout: Layout<'_>,
2068 text_layout: Layout<'_>,
2069 cursor_position: mouse::Cursor,
2070 tree: &Tree,
2071 value: &Value,
2072 placeholder: &str,
2073 size: Option<f32>,
2074 font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2075 is_disabled: bool,
2076 is_secure: bool,
2077 icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2078 trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2079 style: &<crate::Theme as StyleSheet>::Style,
2080 dnd_icon: bool,
2081 line_height: text::LineHeight,
2082 error: Option<&str>,
2083 label: Option<&str>,
2084 helper_text: Option<&str>,
2085 helper_text_size: f32,
2086 helper_line_height: text::LineHeight,
2087 viewport: &Rectangle,
2088 renderer_style: &renderer::Style,
2089) {
2090 let children = &tree.children;
2092
2093 let state = tree.state.downcast_ref::<State>();
2094 let secure_value = is_secure.then(|| value.secure());
2095 let value = secure_value.as_ref().unwrap_or(value);
2096
2097 let mut children_layout = layout.children();
2098
2099 let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2100 let label_layout = children_layout.next();
2101 let layout = children_layout.next().unwrap();
2102 let helper_text_layout = children_layout.next();
2103 (label_layout, layout, helper_text_layout)
2104 } else if label.is_some() {
2105 let label_layout = children_layout.next();
2106 let layout = children_layout.next().unwrap();
2107 (label_layout, layout, None)
2108 } else if helper_text.is_some() {
2109 let layout = children_layout.next().unwrap();
2110 let helper_text_layout = children_layout.next();
2111 (None, layout, helper_text_layout)
2112 } else {
2113 let layout = children_layout.next().unwrap();
2114
2115 (None, layout, None)
2116 };
2117
2118 let mut children_layout = layout.children();
2119 let bounds = layout.bounds();
2120 let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2122
2123 let is_mouse_over = cursor_position.is_over(bounds);
2124
2125 let appearance = if is_disabled {
2126 theme.disabled(style)
2127 } else if error.is_some() {
2128 theme.error(style)
2129 } else if state.is_focused() {
2130 theme.focused(style)
2131 } else if is_mouse_over {
2132 theme.hovered(style)
2133 } else {
2134 theme.active(style)
2135 };
2136
2137 let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2138 let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2139
2140 if is_disabled {
2142 let background = theme.current_container().component.base.into();
2143 icon_color = icon_color.blend_alpha(background, 0.5);
2144 text_color = text_color.blend_alpha(background, 0.5);
2145 }
2146
2147 if let Some(border_offset) = appearance.border_offset {
2149 let offset_bounds = Rectangle {
2150 x: bounds.x - border_offset,
2151 y: bounds.y - border_offset,
2152 width: border_offset.mul_add(2.0, bounds.width),
2153 height: border_offset.mul_add(2.0, bounds.height),
2154 };
2155 renderer.fill_quad(
2156 renderer::Quad {
2157 bounds,
2158 border: Border {
2159 radius: appearance.border_radius,
2160 width: appearance.border_width,
2161 ..Default::default()
2162 },
2163 shadow: Shadow {
2164 offset: Vector::new(0.0, 1.0),
2165 color: Color::TRANSPARENT,
2166 blur_radius: 0.0,
2167 },
2168 },
2169 appearance.background,
2170 );
2171 renderer.fill_quad(
2172 renderer::Quad {
2173 bounds: offset_bounds,
2174 border: Border {
2175 width: appearance.border_width,
2176 color: appearance.border_color,
2177 radius: appearance.border_radius,
2178 },
2179 shadow: Shadow {
2180 offset: Vector::new(0.0, 1.0),
2181 color: Color::TRANSPARENT,
2182 blur_radius: 0.0,
2183 },
2184 },
2185 Background::Color(Color::TRANSPARENT),
2186 );
2187 } else {
2188 renderer.fill_quad(
2189 renderer::Quad {
2190 bounds,
2191 border: Border {
2192 width: appearance.border_width,
2193 color: appearance.border_color,
2194 radius: appearance.border_radius,
2195 },
2196 shadow: Shadow {
2197 offset: Vector::new(0.0, 1.0),
2198 color: Color::TRANSPARENT,
2199 blur_radius: 0.0,
2200 },
2201 },
2202 appearance.background,
2203 );
2204 }
2205
2206 if let (Some(label_layout), Some(label)) = (label_layout, label) {
2208 renderer.fill_text(
2209 Text {
2210 content: label.to_string(),
2211 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2212 font: font.unwrap_or_else(|| renderer.default_font()),
2213 bounds: label_layout.bounds().size(),
2214 horizontal_alignment: alignment::Horizontal::Left,
2215 vertical_alignment: alignment::Vertical::Top,
2216 line_height,
2217 shaping: text::Shaping::Advanced,
2218 wrapping: text::Wrapping::None,
2219 },
2220 label_layout.bounds().position(),
2221 appearance.label_color,
2222 *viewport,
2223 );
2224 }
2225 let mut child_index = 0;
2226 let leading_icon_tree = children.get(child_index);
2227 let has_start_icon = icon.is_some();
2229 if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2230 let mut children = text_layout.children();
2231 let _ = children.next().unwrap();
2232 let icon_layout = children.next().unwrap();
2233
2234 icon.as_widget().draw(
2235 tree,
2236 renderer,
2237 theme,
2238 &renderer::Style {
2239 icon_color,
2240 text_color,
2241 scale_factor: renderer_style.scale_factor,
2242 },
2243 icon_layout,
2244 cursor_position,
2245 viewport,
2246 );
2247 child_index += 1;
2248 }
2249
2250 let text = value.to_string();
2251 let font = font.unwrap_or_else(|| renderer.default_font());
2252 let size = size.unwrap_or_else(|| renderer.default_size().0);
2253 let text_width = state.value.min_width();
2254 let actual_width = text_width.max(text_bounds.width);
2255
2256 let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2257 #[cfg(feature = "wayland")]
2258 let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2259 #[cfg(not(feature = "wayland"))]
2260 let handling_dnd_offer = false;
2261 let (cursor, offset) = if let Some(focus) = &state.is_focused.or_else(|| {
2262 handling_dnd_offer.then(|| Focus {
2263 updated_at: Instant::now(),
2264 now: Instant::now(),
2265 })
2266 }) {
2267 match state.cursor.state(value) {
2268 cursor::State::Index(position) => {
2269 let (text_value_width, offset) =
2270 measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, position);
2271
2272 let is_cursor_visible = handling_dnd_offer
2273 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2274 % 2
2275 == 0;
2276 if is_cursor_visible {
2277 if dnd_icon {
2278 (None, 0.0)
2279 } else {
2280 (
2281 Some((
2282 renderer::Quad {
2283 bounds: Rectangle {
2284 x: text_bounds.x + text_value_width - offset
2285 + if text_value_width < 0. {
2286 actual_width
2287 } else {
2288 0.
2289 },
2290 y: text_bounds.y,
2291 width: 1.0,
2292 height: text_bounds.height,
2293 },
2294 border: Border {
2295 width: 0.0,
2296 color: Color::TRANSPARENT,
2297 radius: radius_0,
2298 },
2299 shadow: Shadow {
2300 offset: Vector::ZERO,
2301 color: Color::TRANSPARENT,
2302 blur_radius: 0.0,
2303 },
2304 },
2305 text_color,
2306 )),
2307 offset,
2308 )
2309 }
2310 } else {
2311 (None, offset)
2312 }
2313 }
2314 cursor::State::Selection { start, end } => {
2315 let left = start.min(end);
2316 let right = end.max(start);
2317
2318 let value_paragraph = &state.value;
2319 let (left_position, left_offset) =
2320 measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, left);
2321
2322 let (right_position, right_offset) =
2323 measure_cursor_and_scroll_offset(value_paragraph.raw(), text_bounds, right);
2324
2325 let width = right_position - left_position;
2326 if dnd_icon {
2327 (None, 0.0)
2328 } else {
2329 (
2330 Some((
2331 renderer::Quad {
2332 bounds: Rectangle {
2333 x: text_bounds.x
2334 + left_position
2335 + if left_position < 0. || right_position < 0. {
2336 actual_width
2337 } else {
2338 0.
2339 },
2340 y: text_bounds.y,
2341 width,
2342 height: text_bounds.height,
2343 },
2344 border: Border {
2345 width: 0.0,
2346 color: Color::TRANSPARENT,
2347 radius: radius_0,
2348 },
2349 shadow: Shadow {
2350 offset: Vector::ZERO,
2351 color: Color::TRANSPARENT,
2352 blur_radius: 0.0,
2353 },
2354 },
2355 appearance.selected_fill,
2356 )),
2357 if end == right {
2358 right_offset
2359 } else {
2360 left_offset
2361 },
2362 )
2363 }
2364 }
2365 }
2366 } else {
2367 (None, 0.0)
2368 };
2369
2370 let render = |renderer: &mut crate::Renderer| {
2371 if let Some((cursor, color)) = cursor {
2372 renderer.fill_quad(cursor, color);
2373 } else {
2374 renderer.with_translation(Vector::ZERO, |_| {});
2375 }
2376
2377 let bounds = Rectangle {
2378 x: text_bounds.x - offset,
2379 y: text_bounds.center_y(),
2380 width: actual_width,
2381 ..text_bounds
2382 };
2383 let color = if text.is_empty() {
2384 appearance.placeholder_color
2385 } else {
2386 text_color
2387 };
2388
2389 renderer.fill_text(
2390 Text {
2391 content: if text.is_empty() {
2392 placeholder.to_string()
2393 } else {
2394 text.clone()
2395 },
2396 font,
2397 bounds: bounds.size(),
2398 size: iced::Pixels(size),
2399 horizontal_alignment: alignment::Horizontal::Left,
2400 vertical_alignment: alignment::Vertical::Center,
2401 line_height: text::LineHeight::default(),
2402 shaping: text::Shaping::Advanced,
2403 wrapping: text::Wrapping::None,
2404 },
2405 bounds.position(),
2406 color,
2407 *viewport,
2408 );
2409 };
2410
2411 renderer.with_layer(text_bounds, render);
2412
2413 let trailing_icon_tree = children.get(child_index);
2414
2415 if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2417 let mut children = text_layout.children();
2418 let mut icon_layout = children.next().unwrap();
2419 if has_start_icon {
2420 icon_layout = children.next().unwrap();
2421 }
2422 icon_layout = children.next().unwrap();
2423
2424 icon.as_widget().draw(
2425 tree,
2426 renderer,
2427 theme,
2428 &renderer::Style {
2429 icon_color,
2430 text_color,
2431 scale_factor: renderer_style.scale_factor,
2432 },
2433 icon_layout,
2434 cursor_position,
2435 viewport,
2436 );
2437 }
2438
2439 if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
2441 renderer.fill_text(
2442 Text {
2443 content: helper_text.to_string(), size: iced::Pixels(helper_text_size),
2445 font,
2446 bounds: helper_text_layout.bounds().size(),
2447 horizontal_alignment: alignment::Horizontal::Left,
2448 vertical_alignment: alignment::Vertical::Top,
2449 line_height: helper_line_height,
2450 shaping: text::Shaping::Advanced,
2451 wrapping: text::Wrapping::None,
2452 },
2453 helper_text_layout.bounds().position(),
2454 text_color,
2455 *viewport,
2456 );
2457 }
2458}
2459
2460#[must_use]
2462pub fn mouse_interaction(
2463 layout: Layout<'_>,
2464 cursor_position: mouse::Cursor,
2465 is_disabled: bool,
2466) -> mouse::Interaction {
2467 if cursor_position.is_over(layout.bounds()) {
2468 if is_disabled {
2469 mouse::Interaction::NotAllowed
2470 } else {
2471 mouse::Interaction::Text
2472 }
2473 } else {
2474 mouse::Interaction::default()
2475 }
2476}
2477
2478#[derive(Debug, Clone)]
2480pub struct TextInputString(pub String);
2481
2482#[cfg(feature = "wayland")]
2483impl AsMimeTypes for TextInputString {
2484 fn available(&self) -> Cow<'static, [String]> {
2485 Cow::Owned(
2486 SUPPORTED_TEXT_MIME_TYPES
2487 .iter()
2488 .cloned()
2489 .map(String::from)
2490 .collect::<Vec<_>>(),
2491 )
2492 }
2493
2494 fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
2495 if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
2496 Some(Cow::Owned(self.0.clone().as_bytes().to_vec()))
2497 } else {
2498 None
2499 }
2500 }
2501}
2502
2503#[derive(Debug, Clone, PartialEq, Eq)]
2504pub(crate) enum DraggingState {
2505 Selection,
2506 #[cfg(feature = "wayland")]
2507 Dnd(DndAction, String),
2508}
2509
2510#[cfg(feature = "wayland")]
2511#[derive(Debug, Default, Clone)]
2512pub(crate) enum DndOfferState {
2513 #[default]
2514 None,
2515 HandlingOffer(Vec<String>, DndAction),
2516 Dropped,
2517}
2518#[derive(Debug, Default, Clone)]
2519#[cfg(not(feature = "wayland"))]
2520pub(crate) struct DndOfferState;
2521
2522#[derive(Debug, Default, Clone)]
2524#[must_use]
2525pub struct State {
2526 pub tracked_value: Value,
2527 pub value: crate::Plain,
2528 pub placeholder: crate::Plain,
2529 pub label: crate::Plain,
2530 pub helper_text: crate::Plain,
2531 pub dirty: bool,
2532 pub is_secure: bool,
2533 pub is_read_only: bool,
2534 pub emit_unfocus: bool,
2535 select_on_focus: bool,
2536 is_focused: Option<Focus>,
2537 dragging_state: Option<DraggingState>,
2538 dnd_offer: DndOfferState,
2539 is_pasting: Option<Value>,
2540 last_click: Option<mouse::Click>,
2541 cursor: Cursor,
2542 keyboard_modifiers: keyboard::Modifiers,
2543 }
2545
2546#[derive(Debug, Clone, Copy)]
2547struct Focus {
2548 updated_at: Instant,
2549 now: Instant,
2550}
2551
2552impl State {
2553 pub fn new(
2555 is_secure: bool,
2556 is_read_only: bool,
2557 always_active: bool,
2558 select_on_focus: bool,
2559 ) -> Self {
2560 Self {
2561 is_secure,
2562 is_read_only,
2563 is_focused: always_active.then(|| {
2564 let now = Instant::now();
2565 Focus {
2566 updated_at: now,
2567 now,
2568 }
2569 }),
2570 select_on_focus,
2571 ..Self::default()
2572 }
2573 }
2574
2575 #[must_use]
2577 pub fn selected_text(&self, text: &str) -> Option<String> {
2578 let value = Value::new(text);
2579 match self.cursor.state(&value) {
2580 cursor::State::Index(_) => None,
2581 cursor::State::Selection { start, end } => {
2582 let left = start.min(end);
2583 let right = end.max(start);
2584 Some(text[left..right].to_string())
2585 }
2586 }
2587 }
2588
2589 #[cfg(feature = "wayland")]
2590 #[must_use]
2592 pub fn dragged_text(&self) -> Option<String> {
2593 match self.dragging_state.as_ref() {
2594 Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
2595 _ => None,
2596 }
2597 }
2598
2599 pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
2601 Self {
2602 tracked_value: Value::default(),
2603 is_secure,
2604 value: crate::Plain::default(),
2605 placeholder: crate::Plain::default(),
2606 label: crate::Plain::default(),
2607 helper_text: crate::Plain::default(),
2608 is_read_only,
2609 emit_unfocus: false,
2610 is_focused: None,
2611 select_on_focus: false,
2612 dragging_state: None,
2613 dnd_offer: DndOfferState::default(),
2614 is_pasting: None,
2615 last_click: None,
2616 cursor: Cursor::default(),
2617 keyboard_modifiers: keyboard::Modifiers::default(),
2618 dirty: false,
2619 }
2620 }
2621
2622 #[inline]
2624 #[must_use]
2625 pub fn is_focused(&self) -> bool {
2626 self.is_focused.is_some()
2627 }
2628
2629 #[inline]
2631 #[must_use]
2632 pub fn cursor(&self) -> Cursor {
2633 self.cursor
2634 }
2635
2636 #[cold]
2638 pub fn focus(&mut self) {
2639 let now = Instant::now();
2640 LAST_FOCUS_UPDATE.with(|x| x.set(now));
2641 self.is_read_only = false;
2642 self.is_focused = Some(Focus {
2643 updated_at: now,
2644 now,
2645 });
2646
2647 if self.select_on_focus {
2648 self.select_all()
2649 } else {
2650 self.move_cursor_to_end();
2651 }
2652 }
2653
2654 #[cold]
2656 pub(super) fn unfocus(&mut self) {
2657 self.move_cursor_to_front();
2658 self.last_click = None;
2659 self.is_focused = None;
2660 self.dragging_state = None;
2661 self.is_pasting = None;
2662 self.keyboard_modifiers = keyboard::Modifiers::default();
2663 }
2664
2665 #[inline]
2667 pub fn move_cursor_to_front(&mut self) {
2668 self.cursor.move_to(0);
2669 }
2670
2671 #[inline]
2673 pub fn move_cursor_to_end(&mut self) {
2674 self.cursor.move_to(usize::MAX);
2675 }
2676
2677 #[inline]
2679 pub fn move_cursor_to(&mut self, position: usize) {
2680 self.cursor.move_to(position);
2681 }
2682
2683 #[inline]
2685 pub fn select_all(&mut self) {
2686 self.cursor.select_range(0, usize::MAX);
2687 }
2688
2689 pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
2690 let position = if target > 0.0 {
2691 find_cursor_position(bounds, value, self, target)
2692 } else {
2693 None
2694 };
2695
2696 self.cursor.move_to(position.unwrap_or(0));
2697 self.dragging_state = Some(DraggingState::Selection);
2698 }
2699}
2700
2701impl operation::Focusable for State {
2702 #[inline]
2703 fn is_focused(&self) -> bool {
2704 Self::is_focused(self)
2705 }
2706
2707 #[inline]
2708 fn focus(&mut self) {
2709 Self::focus(self);
2710 }
2711
2712 #[inline]
2713 fn unfocus(&mut self) {
2714 Self::unfocus(self);
2715 }
2716}
2717
2718impl operation::TextInput for State {
2719 #[inline]
2720 fn move_cursor_to_front(&mut self) {
2721 Self::move_cursor_to_front(self);
2722 }
2723
2724 #[inline]
2725 fn move_cursor_to_end(&mut self) {
2726 Self::move_cursor_to_end(self);
2727 }
2728
2729 #[inline]
2730 fn move_cursor_to(&mut self, position: usize) {
2731 Self::move_cursor_to(self, position);
2732 }
2733
2734 #[inline]
2735 fn select_all(&mut self) {
2736 Self::select_all(self);
2737 }
2738}
2739
2740#[inline(never)]
2741fn measure_cursor_and_scroll_offset(
2742 paragraph: &impl text::Paragraph,
2743 text_bounds: Rectangle,
2744 cursor_index: usize,
2745) -> (f32, f32) {
2746 let grapheme_position = paragraph
2747 .grapheme_position(0, cursor_index)
2748 .unwrap_or(Point::ORIGIN);
2749
2750 let offset = ((grapheme_position.x + 5.0) - text_bounds.width).max(0.0);
2751
2752 (grapheme_position.x, offset)
2753}
2754
2755#[inline(never)]
2758fn find_cursor_position(
2759 text_bounds: Rectangle,
2760 value: &Value,
2761 state: &State,
2762 x: f32,
2763) -> Option<usize> {
2764 let offset = offset(text_bounds, value, state);
2765 let value = value.to_string();
2766
2767 let char_offset = state
2768 .value
2769 .raw()
2770 .hit_test(Point::new(x + offset, text_bounds.height / 2.0))
2771 .map(text::Hit::cursor)?;
2772
2773 Some(
2774 unicode_segmentation::UnicodeSegmentation::graphemes(
2775 &value[..char_offset.min(value.len())],
2776 true,
2777 )
2778 .count(),
2779 )
2780}
2781
2782#[inline(never)]
2783fn replace_paragraph(
2784 state: &mut State,
2785 layout: Layout<'_>,
2786 value: &Value,
2787 font: <crate::Renderer as iced_core::text::Renderer>::Font,
2788 text_size: Pixels,
2789 line_height: text::LineHeight,
2790 limits: &layout::Limits,
2791) {
2792 let mut children_layout = layout.children();
2793 let text_bounds = children_layout.next().unwrap();
2794 let bounds = limits.resolve(
2795 Length::Shrink,
2796 Length::Fill,
2797 Size::new(0., text_bounds.bounds().height),
2798 );
2799
2800 state.value = crate::Plain::new(Text {
2801 font,
2802 line_height,
2803 content: &value.to_string(),
2804 bounds,
2805 size: text_size,
2806 horizontal_alignment: alignment::Horizontal::Left,
2807 vertical_alignment: alignment::Vertical::Top,
2808 shaping: text::Shaping::Advanced,
2809 wrapping: text::Wrapping::None,
2810 });
2811}
2812
2813const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
2814
2815mod platform {
2816 use iced_core::keyboard;
2817
2818 #[inline]
2819 pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
2820 if cfg!(target_os = "macos") {
2821 modifiers.alt()
2822 } else {
2823 modifiers.control()
2824 }
2825 }
2826}
2827
2828#[inline(never)]
2829fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
2830 if state.is_focused() {
2831 let cursor = state.cursor();
2832
2833 let focus_position = match cursor.state(value) {
2834 cursor::State::Index(i) => i,
2835 cursor::State::Selection { end, .. } => end,
2836 };
2837
2838 let (_, offset) =
2839 measure_cursor_and_scroll_offset(state.value.raw(), text_bounds, focus_position);
2840
2841 offset
2842 } else {
2843 0.0
2844 }
2845}