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(all(feature = "wayland", target_os = "linux"))]
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(all(feature = "wayland", target_os = "linux"))]
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(all(feature = "wayland", target_os = "linux"))]
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(all(feature = "wayland", target_os = "linux"))]
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::Tab)
2115 | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
2116 | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
2117 return;
2118 }
2119 _ => {}
2120 }
2121
2122 shell.capture_event();
2123 return;
2124 }
2125 }
2126 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
2127 let state = state();
2128
2129 state.keyboard_modifiers = *modifiers;
2130 }
2131 Event::InputMethod(event) => {
2132 let state = state();
2133
2134 match event {
2135 input_method::Event::Opened | input_method::Event::Closed => {
2136 state.preedit = matches!(event, input_method::Event::Opened)
2137 .then(input_method::Preedit::new);
2138 shell.capture_event();
2139 return;
2140 }
2141 input_method::Event::Preedit(content, selection) => {
2142 if state.is_focused() {
2143 state.preedit = Some(input_method::Preedit {
2144 content: content.to_owned(),
2145 selection: selection.clone(),
2146 text_size: Some(size.into()),
2147 });
2148 shell.capture_event();
2149 return;
2150 }
2151 }
2152 input_method::Event::Commit(text) => {
2153 let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
2154 return;
2155 };
2156 let Some(on_input) = on_input else {
2157 return;
2158 };
2159 if state.is_read_only {
2160 return;
2161 }
2162
2163 focus.updated_at = Instant::now();
2164 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
2165
2166 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2167 editor.paste(Value::new(&text));
2168
2169 let contents = editor.contents();
2170 let unsecured_value = Value::new(&contents);
2171 let message = if let Some(paste) = &on_paste {
2172 (paste)(contents)
2173 } else {
2174 (on_input)(contents)
2175 };
2176 shell.publish(message);
2177
2178 state.is_pasting = None;
2179 let value = if is_secure {
2180 unsecured_value.secure()
2181 } else {
2182 unsecured_value
2183 };
2184
2185 update_cache(state, &value);
2186 shell.capture_event();
2187 return;
2188 }
2189 }
2190 }
2191 Event::Window(window::Event::RedrawRequested(now)) => {
2192 let state = state();
2193
2194 if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
2195 focus.now = *now;
2196
2197 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
2198 - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
2199 shell.request_redraw_at(window::RedrawRequest::At(
2200 now.checked_add(Duration::from_millis(millis_until_redraw as u64))
2201 .unwrap_or(*now),
2202 ));
2203
2204 shell.request_input_method(&input_method(state, text_layout, unsecured_value));
2205 } else if always_active {
2206 shell.request_redraw();
2207 }
2208 }
2209 #[cfg(all(feature = "wayland", target_os = "linux"))]
2210 Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
2211 cold();
2212 let state = state();
2213 if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
2214 state.dragging_state = None;
2216 shell.capture_event();
2217 return;
2218 }
2219 }
2220 #[cfg(all(feature = "wayland", target_os = "linux"))]
2221 Event::Dnd(DndEvent::Offer(
2222 rectangle,
2223 OfferEvent::Enter {
2224 x,
2225 y,
2226 mime_types,
2227 surface,
2228 },
2229 )) if *rectangle == Some(dnd_id) => {
2230 cold();
2231 let state = state();
2232 let is_clicked = text_layout.bounds().contains(Point {
2233 x: *x as f32,
2234 y: *y as f32,
2235 });
2236
2237 let mut accepted = false;
2238 for m in mime_types {
2239 if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
2240 let clone = m.clone();
2241 accepted = true;
2242 }
2243 }
2244 if accepted {
2245 let target = {
2246 let text_bounds = text_layout.bounds();
2247
2248 let alignment_offset = alignment_offset(
2249 text_bounds.width,
2250 state.value.raw().min_width(),
2251 effective_alignment(state.value.raw()),
2252 );
2253
2254 *x as f32 - text_bounds.x - alignment_offset
2255 };
2256 state.dnd_offer =
2257 DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
2258 update_cache(state, value);
2260 let (position, affinity) =
2261 find_cursor_position(text_layout.bounds(), value, state, target)
2262 .unwrap_or((0, text::Affinity::Before));
2263
2264 state.cursor.set_affinity(affinity);
2265 state.cursor.move_to(position);
2266 shell.capture_event();
2267 return;
2268 }
2269 }
2270 #[cfg(all(feature = "wayland", target_os = "linux"))]
2271 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
2272 if *rectangle == Some(dnd_id) =>
2273 {
2274 let state = state();
2275
2276 let target = {
2277 let text_bounds = text_layout.bounds();
2278
2279 let alignment_offset = alignment_offset(
2280 text_bounds.width,
2281 state.value.raw().min_width(),
2282 effective_alignment(state.value.raw()),
2283 );
2284
2285 *x as f32 - text_bounds.x - alignment_offset
2286 };
2287 update_cache(state, value);
2289 let (position, affinity) =
2290 find_cursor_position(text_layout.bounds(), value, state, target)
2291 .unwrap_or((0, text::Affinity::Before));
2292
2293 state.cursor.set_affinity(affinity);
2294 state.cursor.move_to(position);
2295 shell.capture_event();
2296 return;
2297 }
2298 #[cfg(all(feature = "wayland", target_os = "linux"))]
2299 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
2300 cold();
2301 let state = state();
2302 if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
2303 let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
2304 .iter()
2305 .find(|&&m| mime_types.iter().any(|t| t == m))
2306 else {
2307 state.dnd_offer = DndOfferState::None;
2308 shell.capture_event();
2309 return;
2310 };
2311 state.dnd_offer = DndOfferState::Dropped;
2312 }
2313
2314 return;
2315 }
2316 #[cfg(all(feature = "wayland", target_os = "linux"))]
2317 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {}
2318 #[cfg(all(feature = "wayland", target_os = "linux"))]
2319 Event::Dnd(DndEvent::Offer(
2320 rectangle,
2321 OfferEvent::Leave | OfferEvent::LeaveDestination,
2322 )) => {
2323 cold();
2324 let state = state();
2325 match state.dnd_offer {
2328 DndOfferState::Dropped => {}
2329 _ => {
2330 state.dnd_offer = DndOfferState::None;
2331 }
2332 };
2333 shell.capture_event();
2334 return;
2335 }
2336 #[cfg(all(feature = "wayland", target_os = "linux"))]
2337 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2338 if *rectangle == Some(dnd_id) =>
2339 {
2340 cold();
2341 let state = state();
2342 if matches!(&state.dnd_offer, DndOfferState::Dropped) {
2343 state.dnd_offer = DndOfferState::None;
2344 if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2345 shell.capture_event();
2346 return;
2347 }
2348 let Ok(content) = String::from_utf8(data.clone()) else {
2349 shell.capture_event();
2350 return;
2351 };
2352
2353 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2354
2355 editor.paste(Value::new(content.as_str()));
2356 let contents = editor.contents();
2357 let unsecured_value = Value::new(&contents);
2358 state.tracked_value = unsecured_value.clone();
2359 if let Some(on_paste) = on_paste.as_ref() {
2360 let message = (on_paste)(contents);
2361 shell.publish(message);
2362 }
2363
2364 let value = if is_secure {
2365 unsecured_value.secure()
2366 } else {
2367 unsecured_value
2368 };
2369 update_cache(state, &value);
2370 shell.capture_event();
2371 return;
2372 }
2373 return;
2374 }
2375 _ => {}
2376 }
2377}
2378
2379fn input_method<'b>(
2380 state: &'b State,
2381 text_layout: Layout<'_>,
2382 value: &Value,
2383) -> InputMethod<&'b str> {
2384 if !state.is_focused() {
2385 return InputMethod::Disabled;
2386 };
2387
2388 let text_bounds = text_layout.bounds();
2389 let cursor_index = match state.cursor.state(value) {
2390 cursor::State::Index(position) => position,
2391 cursor::State::Selection { start, end } => start.min(end),
2392 };
2393 let (cursor, offset) = measure_cursor_and_scroll_offset(
2394 state.value.raw(),
2395 text_bounds,
2396 cursor_index,
2397 value,
2398 state.cursor.affinity(),
2399 state.scroll_offset,
2400 );
2401 InputMethod::Enabled {
2402 cursor: Rectangle::new(
2403 Point::new(text_bounds.x + cursor - offset, text_bounds.y),
2404 Size::new(1.0, text_bounds.height),
2405 ),
2406 purpose: if state.is_secure {
2407 input_method::Purpose::Secure
2408 } else {
2409 input_method::Purpose::Normal
2410 },
2411 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
2412 }
2413}
2414
2415#[allow(clippy::too_many_arguments)]
2420#[allow(clippy::too_many_lines)]
2421#[allow(clippy::missing_panics_doc)]
2422pub fn draw<'a, Message>(
2423 renderer: &mut crate::Renderer,
2424 theme: &crate::Theme,
2425 layout: Layout<'_>,
2426 text_layout: Layout<'_>,
2427 cursor_position: mouse::Cursor,
2428 tree: &Tree,
2429 value: &Value,
2430 placeholder: &str,
2431 size: Option<f32>,
2432 font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2433 is_disabled: bool,
2434 is_secure: bool,
2435 icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2436 trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2437 style: &<crate::Theme as StyleSheet>::Style,
2438 dnd_icon: bool,
2439 line_height: text::LineHeight,
2440 error: Option<&str>,
2441 label: Option<&str>,
2442 helper_text: Option<&str>,
2443 helper_text_size: f32,
2444 helper_line_height: text::LineHeight,
2445 viewport: &Rectangle,
2446 renderer_style: &renderer::Style,
2447) {
2448 let children = &tree.children;
2450
2451 let state = tree.state.downcast_ref::<State>();
2452 let secure_value = is_secure.then(|| value.secure());
2453 let value = secure_value.as_ref().unwrap_or(value);
2454
2455 let mut children_layout = layout.children();
2456
2457 let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2458 let label_layout = children_layout.next();
2459 let layout = children_layout.next().unwrap();
2460 let helper_text_layout = children_layout.next();
2461 (label_layout, layout, helper_text_layout)
2462 } else if label.is_some() {
2463 let label_layout = children_layout.next();
2464 let layout = children_layout.next().unwrap();
2465 (label_layout, layout, None)
2466 } else if helper_text.is_some() {
2467 let layout = children_layout.next().unwrap();
2468 let helper_text_layout = children_layout.next();
2469 (None, layout, helper_text_layout)
2470 } else {
2471 let layout = children_layout.next().unwrap();
2472
2473 (None, layout, None)
2474 };
2475
2476 let mut children_layout = layout.children();
2477 let bounds = layout.bounds();
2478 let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2480
2481 let is_mouse_over = cursor_position.is_over(bounds);
2482
2483 let appearance = if is_disabled {
2484 theme.disabled(style)
2485 } else if error.is_some() {
2486 theme.error(style)
2487 } else if state.is_focused() {
2488 theme.focused(style)
2489 } else if is_mouse_over {
2490 theme.hovered(style)
2491 } else {
2492 theme.active(style)
2493 };
2494
2495 let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2496 let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2497
2498 if is_disabled {
2500 let background = theme.current_container().component.base.into();
2501 icon_color = icon_color.blend_alpha(background, 0.5);
2502 text_color = text_color.blend_alpha(background, 0.5);
2503 }
2504
2505 if let Some(border_offset) = appearance.border_offset {
2507 let offset_bounds = Rectangle {
2508 x: bounds.x - border_offset,
2509 y: bounds.y - border_offset,
2510 width: border_offset.mul_add(2.0, bounds.width),
2511 height: border_offset.mul_add(2.0, bounds.height),
2512 };
2513 renderer.fill_quad(
2514 renderer::Quad {
2515 bounds,
2516 border: Border {
2517 radius: appearance.border_radius,
2518 width: appearance.border_width,
2519 ..Default::default()
2520 },
2521 shadow: Shadow {
2522 offset: Vector::new(0.0, 1.0),
2523 color: Color::TRANSPARENT,
2524 blur_radius: 0.0,
2525 },
2526 snap: true,
2527 },
2528 appearance.background,
2529 );
2530 renderer.fill_quad(
2531 renderer::Quad {
2532 bounds: offset_bounds,
2533 border: Border {
2534 width: appearance.border_width,
2535 color: appearance.border_color,
2536 radius: appearance.border_radius,
2537 },
2538 shadow: Shadow {
2539 offset: Vector::new(0.0, 1.0),
2540 color: Color::TRANSPARENT,
2541 blur_radius: 0.0,
2542 },
2543 snap: true,
2544 },
2545 Background::Color(Color::TRANSPARENT),
2546 );
2547 } else {
2548 renderer.fill_quad(
2549 renderer::Quad {
2550 bounds,
2551 border: Border {
2552 width: appearance.border_width,
2553 color: appearance.border_color,
2554 radius: appearance.border_radius,
2555 },
2556 shadow: Shadow {
2557 offset: Vector::new(0.0, 1.0),
2558 color: Color::TRANSPARENT,
2559 blur_radius: 0.0,
2560 },
2561 snap: true,
2562 },
2563 appearance.background,
2564 );
2565 }
2566
2567 if let (Some(label_layout), Some(label)) = (label_layout, label) {
2569 renderer.fill_text(
2570 Text {
2571 content: label.to_string(),
2572 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2573 font: font.unwrap_or_else(|| renderer.default_font()),
2574 bounds: label_layout.bounds().size(),
2575 align_x: text::Alignment::Left,
2576 align_y: alignment::Vertical::Top,
2577 line_height,
2578 shaping: text::Shaping::Advanced,
2579 wrapping: text::Wrapping::None,
2580 ellipsize: text::Ellipsize::None,
2581 },
2582 label_layout.bounds().position(),
2583 appearance.label_color,
2584 *viewport,
2585 );
2586 }
2587 let mut child_index = 0;
2588 let leading_icon_tree = children.get(child_index);
2589 let has_start_icon = icon.is_some();
2591 if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2592 let mut children = text_layout.children();
2593 let _ = children.next().unwrap();
2594 let icon_layout = children.next().unwrap();
2595
2596 icon.as_widget().draw(
2597 tree,
2598 renderer,
2599 theme,
2600 &renderer::Style {
2601 icon_color,
2602 text_color,
2603 scale_factor: renderer_style.scale_factor,
2604 },
2605 icon_layout,
2606 cursor_position,
2607 viewport,
2608 );
2609 child_index += 1;
2610 }
2611
2612 let text = value.to_string();
2613 let font = font.unwrap_or_else(|| renderer.default_font());
2614 let size = size.unwrap_or_else(|| renderer.default_size().0);
2615 let text_width = state.value.min_width();
2616 let actual_width = text_width.max(text_bounds.width);
2617
2618 let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2619 #[cfg(all(feature = "wayland", target_os = "linux"))]
2620 let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2621 #[cfg(not(all(feature = "wayland", target_os = "linux")))]
2622 let handling_dnd_offer = false;
2623 let (cursors, offset, is_selecting) = if let Some(focus) =
2624 state.is_focused.filter(|f| f.focused).or_else(|| {
2625 let now = Instant::now();
2626 handling_dnd_offer.then_some(Focus {
2627 needs_update: false,
2628 updated_at: now,
2629 now,
2630 focused: true,
2631 })
2632 }) {
2633 match state.cursor.state(value) {
2634 cursor::State::Index(position) => {
2635 let (text_value_width, _) = measure_cursor_and_scroll_offset(
2636 state.value.raw(),
2637 text_bounds,
2638 position,
2639 value,
2640 state.cursor.affinity(),
2641 state.scroll_offset,
2642 );
2643 let is_cursor_visible = handling_dnd_offer
2644 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2645 .is_multiple_of(2);
2646
2647 if is_cursor_visible && !dnd_icon {
2648 (
2649 vec![(
2650 renderer::Quad {
2651 bounds: Rectangle {
2652 x: (text_bounds.x + text_value_width).floor(),
2653 y: text_bounds.y,
2654 width: 1.0,
2655 height: text_bounds.height,
2656 },
2657 border: Border {
2658 width: 0.0,
2659 color: Color::TRANSPARENT,
2660 radius: radius_0,
2661 },
2662 shadow: Shadow {
2663 offset: Vector::ZERO,
2664 color: Color::TRANSPARENT,
2665 blur_radius: 0.0,
2666 },
2667 snap: true,
2668 },
2669 text_color,
2670 )],
2671 state.scroll_offset,
2672 false,
2673 )
2674 } else {
2675 (
2676 Vec::<(renderer::Quad, Color)>::new(),
2677 if dnd_icon { 0.0 } else { state.scroll_offset },
2678 false,
2679 )
2680 }
2681 }
2682 cursor::State::Selection { start, end } => {
2683 let left = start.min(end);
2684 let right = end.max(start);
2685
2686 if dnd_icon {
2687 (Vec::<(renderer::Quad, Color)>::new(), 0.0, true)
2688 } else {
2689 let lo_byte = value.byte_index_at_grapheme(left);
2690 let hi_byte = value.byte_index_at_grapheme(right);
2691
2692 let rects = state.value.raw().highlight(
2693 0,
2694 (lo_byte, text::Affinity::After),
2695 (hi_byte, text::Affinity::Before),
2696 );
2697
2698 let cursors: Vec<(renderer::Quad, Color)> = rects
2699 .into_iter()
2700 .map(|r| {
2701 (
2702 renderer::Quad {
2703 bounds: Rectangle {
2704 x: text_bounds.x + r.x,
2705 y: text_bounds.y,
2706 width: r.width,
2707 height: text_bounds.height,
2708 },
2709 border: Border {
2710 width: 0.0,
2711 color: Color::TRANSPARENT,
2712 radius: radius_0,
2713 },
2714 shadow: Shadow {
2715 offset: Vector::ZERO,
2716 color: Color::TRANSPARENT,
2717 blur_radius: 0.0,
2718 },
2719 snap: true,
2720 },
2721 appearance.selected_fill,
2722 )
2723 })
2724 .collect();
2725
2726 (cursors, state.scroll_offset, true)
2727 }
2728 }
2729 }
2730 } else {
2731 let unfocused_offset = match effective_alignment(state.value.raw()) {
2732 alignment::Horizontal::Right => {
2733 (state.value.raw().min_width() - text_bounds.width).max(0.0)
2734 }
2735 _ => 0.0,
2736 };
2737
2738 (
2739 Vec::<(renderer::Quad, Color)>::new(),
2740 unfocused_offset,
2741 false,
2742 )
2743 };
2744
2745 let render = |renderer: &mut crate::Renderer| {
2746 let alignment_offset = alignment_offset(
2747 text_bounds.width,
2748 state.value.raw().min_width(),
2749 effective_alignment(state.value.raw()),
2750 );
2751
2752 if cursors.is_empty() {
2753 renderer.with_translation(Vector::ZERO, |_| {});
2754 } else {
2755 renderer.with_translation(Vector::new(alignment_offset - offset, 0.0), |renderer| {
2756 for (quad, color) in &cursors {
2757 renderer.fill_quad(*quad, *color);
2758 }
2759 });
2760 }
2761
2762 let bounds = Rectangle {
2763 x: text_bounds.x + alignment_offset - offset,
2764 y: text_bounds.center_y(),
2765 width: actual_width,
2766 ..text_bounds
2767 };
2768 let color = if text.is_empty() {
2769 appearance.placeholder_color
2770 } else {
2771 text_color
2772 };
2773
2774 renderer.fill_text(
2775 Text {
2776 content: if text.is_empty() {
2777 placeholder.to_string()
2778 } else {
2779 text.clone()
2780 },
2781 font,
2782 bounds: bounds.size(),
2783 size: iced::Pixels(size),
2784 align_x: text::Alignment::Default,
2785 align_y: alignment::Vertical::Center,
2786 line_height: text::LineHeight::default(),
2787 shaping: text::Shaping::Advanced,
2788 wrapping: text::Wrapping::None,
2789 ellipsize: text::Ellipsize::None,
2790 },
2791 bounds.position(),
2792 color,
2793 text_bounds,
2794 );
2795 };
2796
2797 renderer.with_layer(text_bounds, render);
2800
2801 let trailing_icon_tree = children.get(child_index);
2802
2803 if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2805 let mut children = text_layout.children();
2806 let mut icon_layout = children.next().unwrap();
2807 if has_start_icon {
2808 icon_layout = children.next().unwrap();
2809 }
2810 icon_layout = children.next().unwrap();
2811
2812 icon.as_widget().draw(
2813 tree,
2814 renderer,
2815 theme,
2816 &renderer::Style {
2817 icon_color,
2818 text_color,
2819 scale_factor: renderer_style.scale_factor,
2820 },
2821 icon_layout,
2822 cursor_position,
2823 viewport,
2824 );
2825 }
2826
2827 if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
2829 renderer.fill_text(
2830 Text {
2831 content: helper_text.to_string(), size: iced::Pixels(helper_text_size),
2833 font,
2834 bounds: helper_text_layout.bounds().size(),
2835 align_x: text::Alignment::Left,
2836 align_y: alignment::Vertical::Top,
2837 line_height: helper_line_height,
2838 shaping: text::Shaping::Advanced,
2839 wrapping: text::Wrapping::None,
2840 ellipsize: text::Ellipsize::None,
2841 },
2842 helper_text_layout.bounds().position(),
2843 text_color,
2844 *viewport,
2845 );
2846 }
2847}
2848
2849#[must_use]
2851pub fn mouse_interaction(
2852 layout: Layout<'_>,
2853 cursor_position: mouse::Cursor,
2854 is_disabled: bool,
2855) -> mouse::Interaction {
2856 if cursor_position.is_over(layout.bounds()) {
2857 if is_disabled {
2858 mouse::Interaction::NotAllowed
2859 } else {
2860 mouse::Interaction::Text
2861 }
2862 } else {
2863 mouse::Interaction::default()
2864 }
2865}
2866
2867#[derive(Debug, Clone)]
2869pub struct TextInputString(pub String);
2870
2871#[cfg(all(feature = "wayland", target_os = "linux"))]
2872impl AsMimeTypes for TextInputString {
2873 fn available(&self) -> Cow<'static, [String]> {
2874 Cow::Owned(
2875 SUPPORTED_TEXT_MIME_TYPES
2876 .iter()
2877 .cloned()
2878 .map(String::from)
2879 .collect::<Vec<_>>(),
2880 )
2881 }
2882
2883 fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
2884 if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
2885 Some(Cow::Owned(self.0.clone().into_bytes()))
2886 } else {
2887 None
2888 }
2889 }
2890}
2891
2892#[derive(Debug, Clone, PartialEq)]
2893pub(crate) enum DraggingState {
2894 Selection,
2895 #[cfg(all(feature = "wayland", target_os = "linux"))]
2896 PrepareDnd(Point),
2897 #[cfg(all(feature = "wayland", target_os = "linux"))]
2898 Dnd(DndAction, String),
2899}
2900
2901#[cfg(all(feature = "wayland", target_os = "linux"))]
2902#[derive(Debug, Default, Clone)]
2903pub(crate) enum DndOfferState {
2904 #[default]
2905 None,
2906 HandlingOffer(Vec<String>, DndAction),
2907 Dropped,
2908}
2909#[derive(Debug, Default, Clone)]
2910#[cfg(not(all(feature = "wayland", target_os = "linux")))]
2911pub(crate) struct DndOfferState;
2912
2913#[derive(Debug, Default, Clone)]
2915#[must_use]
2916pub struct State {
2917 pub tracked_value: Value,
2918 pub value: crate::Plain,
2919 pub placeholder: crate::Plain,
2920 pub label: crate::Plain,
2921 pub helper_text: crate::Plain,
2922 pub dirty: bool,
2923 pub is_secure: bool,
2924 pub is_read_only: bool,
2925 pub emit_unfocus: bool,
2926 select_on_focus: bool,
2927 double_click_select_delimiter: Option<char>,
2928 is_focused: Option<Focus>,
2929 dragging_state: Option<DraggingState>,
2930 dnd_offer: DndOfferState,
2931 is_pasting: Option<Value>,
2932 last_click: Option<mouse::Click>,
2933 cursor: Cursor,
2934 preedit: Option<Preedit>,
2935 keyboard_modifiers: keyboard::Modifiers,
2936 scroll_offset: f32,
2937}
2938
2939#[derive(Debug, Clone, Copy)]
2940struct Focus {
2941 updated_at: Instant,
2942 now: Instant,
2943 focused: bool,
2944 needs_update: bool,
2945}
2946
2947impl State {
2948 pub fn new(
2950 is_secure: bool,
2951 is_read_only: bool,
2952 always_active: bool,
2953 select_on_focus: bool,
2954 ) -> Self {
2955 Self {
2956 is_secure,
2957 is_read_only,
2958 is_focused: always_active.then(|| {
2959 let now = Instant::now();
2960 Focus {
2961 updated_at: now,
2962 now,
2963 focused: true,
2964 needs_update: false,
2965 }
2966 }),
2967 select_on_focus,
2968 ..Self::default()
2969 }
2970 }
2971
2972 #[must_use]
2974 pub fn selected_text(&self, text: &str) -> Option<String> {
2975 let value = Value::new(text);
2976 match self.cursor.state(&value) {
2977 cursor::State::Index(_) => None,
2978 cursor::State::Selection { start, end } => {
2979 let left = start.min(end);
2980 let right = end.max(start);
2981 Some(text[left..right].to_string())
2982 }
2983 }
2984 }
2985
2986 #[cfg(all(feature = "wayland", target_os = "linux"))]
2987 #[must_use]
2989 pub fn dragged_text(&self) -> Option<String> {
2990 match self.dragging_state.as_ref() {
2991 Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
2992 _ => None,
2993 }
2994 }
2995
2996 pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
2998 Self {
2999 tracked_value: Value::default(),
3000 is_secure,
3001 value: crate::Plain::default(),
3002 placeholder: crate::Plain::default(),
3003 label: crate::Plain::default(),
3004 helper_text: crate::Plain::default(),
3005 is_read_only,
3006 emit_unfocus: false,
3007 is_focused: None,
3008 select_on_focus: false,
3009 double_click_select_delimiter: None,
3010 dragging_state: None,
3011 dnd_offer: DndOfferState::default(),
3012 is_pasting: None,
3013 last_click: None,
3014 cursor: Cursor::default(),
3015 preedit: None,
3016 keyboard_modifiers: keyboard::Modifiers::default(),
3017 scroll_offset: 0.0,
3018 dirty: false,
3019 }
3020 }
3021
3022 #[inline]
3024 #[must_use]
3025 pub fn is_focused(&self) -> bool {
3026 self.is_focused.is_some_and(|f| f.focused)
3027 }
3028
3029 #[inline]
3031 #[must_use]
3032 pub fn cursor(&self) -> Cursor {
3033 self.cursor
3034 }
3035
3036 #[cold]
3038 pub fn focus(&mut self) {
3039 let now = Instant::now();
3040 LAST_FOCUS_UPDATE.with(|x| x.set(now));
3041 let was_focused = self.is_focused.is_some_and(|f| f.focused);
3042 self.is_read_only = false;
3043 self.is_focused = Some(Focus {
3044 updated_at: now,
3045 now,
3046 focused: true,
3047 needs_update: false,
3048 });
3049
3050 if was_focused {
3051 return;
3052 }
3053 if self.select_on_focus {
3054 self.select_all()
3055 } else {
3056 self.move_cursor_to_end();
3057 }
3058 }
3059
3060 #[cold]
3062 pub(super) fn unfocus(&mut self) {
3063 self.move_cursor_to_front();
3064 self.last_click = None;
3065 self.is_focused = self.is_focused.map(|mut f| {
3066 f.focused = false;
3067 f.needs_update = false;
3068 f
3069 });
3070 self.dragging_state = None;
3071 self.is_pasting = None;
3072 self.keyboard_modifiers = keyboard::Modifiers::default();
3073 }
3074
3075 #[inline]
3077 pub fn move_cursor_to_front(&mut self) {
3078 self.cursor.move_to(0);
3079 }
3080
3081 #[inline]
3083 pub fn move_cursor_to_end(&mut self) {
3084 self.cursor.move_to(usize::MAX);
3085 }
3086
3087 #[inline]
3089 pub fn move_cursor_to(&mut self, position: usize) {
3090 self.cursor.move_to(position);
3091 }
3092
3093 #[inline]
3095 pub fn select_all(&mut self) {
3096 self.cursor.select_range(0, usize::MAX);
3097 }
3098
3099 #[inline]
3101 pub fn select_range(&mut self, start: usize, end: usize) {
3102 self.cursor.select_range(start, end);
3103 }
3104
3105 pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
3106 let (position, affinity) = find_cursor_position(bounds, value, self, target)
3107 .unwrap_or((0, text::Affinity::Before));
3108
3109 self.cursor.set_affinity(affinity);
3110 self.cursor.move_to(position);
3111 self.dragging_state = Some(DraggingState::Selection);
3112 }
3113}
3114
3115impl operation::Focusable for State {
3116 #[inline]
3117 fn is_focused(&self) -> bool {
3118 Self::is_focused(self)
3119 }
3120
3121 #[inline]
3122 fn focus(&mut self) {
3123 Self::focus(self);
3124 if let Some(focus) = self.is_focused.as_mut() {
3125 focus.needs_update = true;
3126 }
3127 }
3128
3129 #[inline]
3130 fn unfocus(&mut self) {
3131 Self::unfocus(self);
3132 if let Some(focus) = self.is_focused.as_mut() {
3133 focus.needs_update = true;
3134 }
3135 }
3136}
3137
3138impl operation::TextInput for State {
3139 #[inline]
3140 fn move_cursor_to_front(&mut self) {
3141 Self::move_cursor_to_front(self);
3142 }
3143
3144 #[inline]
3145 fn move_cursor_to_end(&mut self) {
3146 Self::move_cursor_to_end(self);
3147 }
3148
3149 #[inline]
3150 fn move_cursor_to(&mut self, position: usize) {
3151 Self::move_cursor_to(self, position);
3152 }
3153
3154 #[inline]
3155 fn select_all(&mut self) {
3156 Self::select_all(self);
3157 }
3158
3159 fn text(&self) -> &str {
3160 todo!()
3161 }
3162
3163 #[inline]
3164 fn select_range(&mut self, start: usize, end: usize) {
3165 Self::select_range(self, start, end);
3166 }
3167}
3168
3169#[inline(never)]
3170fn measure_cursor_and_scroll_offset(
3171 paragraph: &impl text::Paragraph,
3172 text_bounds: Rectangle,
3173 cursor_index: usize,
3174 value: &Value,
3175 affinity: text::Affinity,
3176 current_offset: f32,
3177) -> (f32, f32) {
3178 let byte_index = value.byte_index_at_grapheme(cursor_index);
3179 let position = paragraph
3180 .cursor_position(0, byte_index, affinity)
3181 .unwrap_or(Point::ORIGIN);
3182
3183 let offset = if position.x > current_offset + text_bounds.width - 5.0 {
3187 (position.x + 5.0) - text_bounds.width
3189 } else if position.x < current_offset + 5.0 {
3190 position.x - 5.0
3192 } else {
3193 current_offset
3195 };
3196
3197 let max_offset = (paragraph.min_width() - text_bounds.width).max(0.0);
3198 let offset = offset.clamp(0.0, max_offset);
3199
3200 (position.x, offset)
3201}
3202
3203#[inline(never)]
3206fn find_cursor_position(
3207 text_bounds: Rectangle,
3208 value: &Value,
3209 state: &State,
3210 x: f32,
3211) -> Option<(usize, text::Affinity)> {
3212 let value_str = value.to_string();
3213
3214 let hit = state.value.raw().hit_test(Point::new(
3215 x + state.scroll_offset,
3216 text_bounds.height / 2.0,
3217 ))?;
3218 let char_offset = hit.cursor();
3219 let affinity = hit.affinity();
3220
3221 let grapheme_count = unicode_segmentation::UnicodeSegmentation::graphemes(
3222 &value_str[..char_offset.min(value_str.len())],
3223 true,
3224 )
3225 .count();
3226
3227 Some((grapheme_count, affinity))
3228}
3229
3230#[inline(never)]
3231fn replace_paragraph(
3232 state: &mut State,
3233 layout: Layout<'_>,
3234 value: &Value,
3235 font: <crate::Renderer as iced_core::text::Renderer>::Font,
3236 text_size: Pixels,
3237 line_height: text::LineHeight,
3238 limits: &layout::Limits,
3239) {
3240 let mut children_layout = layout.children();
3241 let text_bounds = children_layout.next().unwrap();
3242 let bounds = limits.resolve(
3243 Length::Shrink,
3244 Length::Fill,
3245 Size::new(0., text_bounds.bounds().height),
3246 );
3247
3248 state.value = crate::Plain::new(Text {
3249 font,
3250 line_height,
3251 content: value.to_string(),
3252 bounds,
3253 size: text_size,
3254 align_x: text::Alignment::Default,
3255 align_y: alignment::Vertical::Top,
3256 shaping: text::Shaping::Advanced,
3257 wrapping: text::Wrapping::None,
3258 ellipsize: text::Ellipsize::None,
3259 });
3260}
3261
3262const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
3263
3264mod platform {
3265 use iced_core::keyboard;
3266
3267 #[inline]
3268 pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
3269 if cfg!(target_os = "macos") {
3270 modifiers.alt()
3271 } else {
3272 modifiers.control()
3273 }
3274 }
3275}
3276
3277#[inline(never)]
3278fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
3279 if state.is_focused() {
3280 let cursor = state.cursor();
3281
3282 let focus_position = match cursor.state(value) {
3283 cursor::State::Index(i) => i,
3284 cursor::State::Selection { end, .. } => end,
3285 };
3286
3287 let (_, offset) = measure_cursor_and_scroll_offset(
3288 state.value.raw(),
3289 text_bounds,
3290 focus_position,
3291 value,
3292 state.cursor().affinity(),
3293 state.scroll_offset,
3294 );
3295
3296 offset
3297 } else {
3298 match effective_alignment(state.value.raw()) {
3299 alignment::Horizontal::Right => {
3300 (state.value.raw().min_width() - text_bounds.width).max(0.0)
3301 }
3302 _ => 0.0,
3303 }
3304 }
3305}
3306
3307#[inline(never)]
3308fn alignment_offset(
3309 text_bounds_width: f32,
3310 text_min_width: f32,
3311 alignment: alignment::Horizontal,
3312) -> f32 {
3313 if text_min_width > text_bounds_width {
3314 0.0
3315 } else {
3316 match alignment {
3317 alignment::Horizontal::Left => 0.0,
3318 alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0,
3319 alignment::Horizontal::Right => text_bounds_width - text_min_width,
3320 }
3321 }
3322}
3323
3324#[inline(never)]
3325fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizontal {
3326 if paragraph.is_rtl(0).unwrap_or(false) {
3327 alignment::Horizontal::Right
3328 } else {
3329 alignment::Horizontal::Left
3330 }
3331}