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