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