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