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 if state.keyboard_modifiers.command() {
1826 match key.to_latin(*physical_key) {
1827 Some('c') => {
1828 if !is_secure {
1829 if let Some((start, end)) = state.cursor.selection(value) {
1830 clipboard.write(
1831 iced_core::clipboard::Kind::Standard,
1832 value.select(start, end).to_string(),
1833 );
1834 }
1835 }
1836 }
1837 Some('x') => {
1840 if !is_secure {
1841 if let Some((start, end)) = state.cursor.selection(value) {
1842 clipboard.write(
1843 iced_core::clipboard::Kind::Standard,
1844 value.select(start, end).to_string(),
1845 );
1846 }
1847
1848 let mut editor = Editor::new(value, &mut state.cursor);
1849 editor.delete();
1850 let content = editor.contents();
1851 state.tracked_value = Value::new(&content);
1852 if let Some(on_input) = on_input {
1853 let message = (on_input)(content);
1854 shell.publish(message);
1855 }
1856 }
1857 }
1858 Some('v') => {
1859 let content = if let Some(content) = state.is_pasting.take() {
1860 content
1861 } else {
1862 let content: String = clipboard
1863 .read(iced_core::clipboard::Kind::Standard)
1864 .unwrap_or_default()
1865 .chars()
1866 .filter(|c| !c.is_control())
1867 .collect();
1868
1869 Value::new(&content)
1870 };
1871
1872 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1873
1874 editor.paste(content.clone());
1875
1876 let contents = editor.contents();
1877 let unsecured_value = Value::new(&contents);
1878 state.tracked_value = unsecured_value.clone();
1879
1880 if let Some(on_input) = on_input {
1881 let message = if let Some(paste) = &on_paste {
1882 (paste)(contents)
1883 } else {
1884 (on_input)(contents)
1885 };
1886
1887 shell.publish(message);
1888 }
1889
1890 state.is_pasting = Some(content);
1891
1892 let value = if is_secure {
1893 unsecured_value.secure()
1894 } else {
1895 unsecured_value
1896 };
1897
1898 update_cache(state, &value);
1899 shell.capture_event();
1900 return;
1901 }
1902
1903 Some('a') => {
1904 state.cursor.select_all(value);
1905 shell.capture_event();
1906 return;
1907 }
1908
1909 _ => {}
1910 }
1911 }
1912
1913 if let Some(c) = text
1915 .as_ref()
1916 .and_then(|t| t.chars().next().filter(|c| !c.is_control()))
1917 {
1918 if state.is_read_only || (!manage_value && on_input.is_none()) {
1919 return;
1920 };
1921
1922 state.is_pasting = None;
1923
1924 if !state.keyboard_modifiers.command() && !modifiers.control() {
1925 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1926
1927 editor.insert(c);
1928
1929 let contents = editor.contents();
1930 let unsecured_value = Value::new(&contents);
1931 state.tracked_value = unsecured_value.clone();
1932
1933 if let Some(on_input) = on_input {
1934 let message = (on_input)(contents);
1935 shell.publish(message);
1936 }
1937
1938 focus.updated_at = Instant::now();
1939 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1940
1941 let value = if is_secure {
1942 unsecured_value.secure()
1943 } else {
1944 unsecured_value
1945 };
1946
1947 update_cache(state, &value);
1948
1949 shell.capture_event();
1950 return;
1951 }
1952 }
1953
1954 match key.as_ref() {
1955 keyboard::Key::Named(keyboard::key::Named::Enter) => {
1956 if let Some(on_submit) = on_submit {
1957 shell.publish((on_submit)(unsecured_value.to_string()));
1958 }
1959 }
1960 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
1961 if platform::is_jump_modifier_pressed(modifiers)
1962 && state.cursor.selection(value).is_none()
1963 {
1964 if is_secure {
1965 let cursor_pos = state.cursor.end(value);
1966 state.cursor.select_range(0, cursor_pos);
1967 } else {
1968 state.cursor.select_left_by_words(value);
1969 }
1970 }
1971
1972 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1973 editor.backspace();
1974
1975 let contents = editor.contents();
1976 let unsecured_value = Value::new(&contents);
1977 state.tracked_value = unsecured_value.clone();
1978 if let Some(on_input) = on_input {
1979 let message = (on_input)(editor.contents());
1980 shell.publish(message);
1981 }
1982 let value = if is_secure {
1983 unsecured_value.secure()
1984 } else {
1985 unsecured_value
1986 };
1987 update_cache(state, &value);
1988 }
1989 keyboard::Key::Named(keyboard::key::Named::Delete) => {
1990 if platform::is_jump_modifier_pressed(modifiers)
1991 && state.cursor.selection(value).is_none()
1992 {
1993 if is_secure {
1994 let cursor_pos = state.cursor.end(unsecured_value);
1995 state.cursor.select_range(cursor_pos, unsecured_value.len());
1996 } else {
1997 state.cursor.select_right_by_words(unsecured_value);
1998 }
1999 }
2000
2001 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2002 editor.delete();
2003 let contents = editor.contents();
2004 let unsecured_value = Value::new(&contents);
2005 if let Some(on_input) = on_input {
2006 let message = (on_input)(contents);
2007 state.tracked_value = unsecured_value.clone();
2008 shell.publish(message);
2009 }
2010
2011 let value = if is_secure {
2012 unsecured_value.secure()
2013 } else {
2014 unsecured_value
2015 };
2016
2017 update_cache(state, &value);
2018 }
2019 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
2020 let rtl = state.value.raw().is_rtl(0).unwrap_or(false);
2021 let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure;
2022
2023 if modifiers.shift() {
2024 state.cursor.select_visual(false, by_words, rtl, value);
2025 } else {
2026 state.cursor.move_visual(false, by_words, rtl, value);
2027 }
2028 }
2029 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
2030 let rtl = state.value.raw().is_rtl(0).unwrap_or(false);
2031 let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure;
2032
2033 if modifiers.shift() {
2034 state.cursor.select_visual(true, by_words, rtl, value);
2035 } else {
2036 state.cursor.move_visual(true, by_words, rtl, value);
2037 }
2038 }
2039 keyboard::Key::Named(keyboard::key::Named::Home) => {
2040 if modifiers.shift() {
2041 state.cursor.select_range(state.cursor.start(value), 0);
2042 } else {
2043 state.cursor.move_to(0);
2044 }
2045 }
2046 keyboard::Key::Named(keyboard::key::Named::End) => {
2047 if modifiers.shift() {
2048 state
2049 .cursor
2050 .select_range(state.cursor.start(value), value.len());
2051 } else {
2052 state.cursor.move_to(value.len());
2053 }
2054 }
2055 keyboard::Key::Named(keyboard::key::Named::Escape) => {
2056 state.unfocus();
2057 state.is_read_only = true;
2058
2059 if let Some(on_unfocus) = on_unfocus {
2060 shell.publish(on_unfocus.clone());
2061 }
2062 }
2063
2064 keyboard::Key::Named(keyboard::key::Named::Tab) => {
2065 if let Some(on_tab) = on_tab {
2066 shell.publish(on_tab.clone());
2070 } else {
2071 state.is_read_only = true;
2072
2073 if let Some(on_unfocus) = on_unfocus {
2074 shell.publish(on_unfocus.clone());
2075 }
2076
2077 return;
2078 };
2079 }
2080
2081 keyboard::Key::Named(
2082 keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
2083 ) => {
2084 return;
2085 }
2086 _ => {}
2087 }
2088
2089 shell.capture_event();
2090 return;
2091 }
2092 }
2093 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
2094 let state = state();
2095
2096 if state.is_focused() {
2097 match key {
2098 keyboard::Key::Character(c) if "v" == c => {
2099 state.is_pasting = None;
2100 }
2101 keyboard::Key::Named(keyboard::key::Named::Tab)
2102 | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
2103 | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
2104 return;
2105 }
2106 _ => {}
2107 }
2108
2109 shell.capture_event();
2110 return;
2111 }
2112 }
2113 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
2114 let state = state();
2115
2116 state.keyboard_modifiers = *modifiers;
2117 }
2118 Event::InputMethod(event) => {
2119 let state = state();
2120
2121 match event {
2122 input_method::Event::Opened | input_method::Event::Closed => {
2123 state.preedit = matches!(event, input_method::Event::Opened)
2124 .then(input_method::Preedit::new);
2125 shell.capture_event();
2126 return;
2127 }
2128 input_method::Event::Preedit(content, selection) => {
2129 if state.is_focused() {
2130 state.preedit = Some(input_method::Preedit {
2131 content: content.to_owned(),
2132 selection: selection.clone(),
2133 text_size: Some(size.into()),
2134 });
2135 shell.capture_event();
2136 return;
2137 }
2138 }
2139 input_method::Event::Commit(text) => {
2140 let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
2141 return;
2142 };
2143 let Some(on_input) = on_input else {
2144 return;
2145 };
2146 if state.is_read_only {
2147 return;
2148 }
2149
2150 focus.updated_at = Instant::now();
2151 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
2152
2153 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2154 editor.paste(Value::new(&text));
2155
2156 let contents = editor.contents();
2157 let unsecured_value = Value::new(&contents);
2158 let message = if let Some(paste) = &on_paste {
2159 (paste)(contents)
2160 } else {
2161 (on_input)(contents)
2162 };
2163 shell.publish(message);
2164
2165 state.is_pasting = None;
2166 let value = if is_secure {
2167 unsecured_value.secure()
2168 } else {
2169 unsecured_value
2170 };
2171
2172 update_cache(state, &value);
2173 shell.capture_event();
2174 return;
2175 }
2176 }
2177 }
2178 Event::Window(window::Event::RedrawRequested(now)) => {
2179 let state = state();
2180
2181 if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
2182 focus.now = *now;
2183
2184 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
2185 - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
2186 shell.request_redraw_at(window::RedrawRequest::At(
2187 now.checked_add(Duration::from_millis(millis_until_redraw as u64))
2188 .unwrap_or(*now),
2189 ));
2190
2191 shell.request_input_method(&input_method(state, text_layout, unsecured_value));
2192 } else if always_active {
2193 shell.request_redraw();
2194 }
2195 }
2196 #[cfg(all(feature = "wayland", target_os = "linux"))]
2197 Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
2198 cold();
2199 let state = state();
2200 if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
2201 state.dragging_state = None;
2203 shell.capture_event();
2204 return;
2205 }
2206 }
2207 #[cfg(all(feature = "wayland", target_os = "linux"))]
2208 Event::Dnd(DndEvent::Offer(
2209 rectangle,
2210 OfferEvent::Enter {
2211 x,
2212 y,
2213 mime_types,
2214 surface,
2215 },
2216 )) if *rectangle == Some(dnd_id) => {
2217 cold();
2218 let state = state();
2219 let is_clicked = text_layout.bounds().contains(Point {
2220 x: *x as f32,
2221 y: *y as f32,
2222 });
2223
2224 let mut accepted = false;
2225 for m in mime_types {
2226 if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
2227 let clone = m.clone();
2228 accepted = true;
2229 }
2230 }
2231 if accepted {
2232 let target = {
2233 let text_bounds = text_layout.bounds();
2234
2235 let alignment_offset = alignment_offset(
2236 text_bounds.width,
2237 state.value.raw().min_width(),
2238 effective_alignment(state.value.raw()),
2239 );
2240
2241 *x as f32 - text_bounds.x - alignment_offset
2242 };
2243 state.dnd_offer =
2244 DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
2245 update_cache(state, value);
2247 let (position, affinity) =
2248 find_cursor_position(text_layout.bounds(), value, state, target)
2249 .unwrap_or((0, text::Affinity::Before));
2250
2251 state.cursor.set_affinity(affinity);
2252 state.cursor.move_to(position);
2253 shell.capture_event();
2254 return;
2255 }
2256 }
2257 #[cfg(all(feature = "wayland", target_os = "linux"))]
2258 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
2259 if *rectangle == Some(dnd_id) =>
2260 {
2261 let state = state();
2262
2263 let target = {
2264 let text_bounds = text_layout.bounds();
2265
2266 let alignment_offset = alignment_offset(
2267 text_bounds.width,
2268 state.value.raw().min_width(),
2269 effective_alignment(state.value.raw()),
2270 );
2271
2272 *x as f32 - text_bounds.x - alignment_offset
2273 };
2274 update_cache(state, value);
2276 let (position, affinity) =
2277 find_cursor_position(text_layout.bounds(), value, state, target)
2278 .unwrap_or((0, text::Affinity::Before));
2279
2280 state.cursor.set_affinity(affinity);
2281 state.cursor.move_to(position);
2282 shell.capture_event();
2283 return;
2284 }
2285 #[cfg(all(feature = "wayland", target_os = "linux"))]
2286 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
2287 cold();
2288 let state = state();
2289 if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
2290 let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
2291 .iter()
2292 .find(|&&m| mime_types.iter().any(|t| t == m))
2293 else {
2294 state.dnd_offer = DndOfferState::None;
2295 shell.capture_event();
2296 return;
2297 };
2298 state.dnd_offer = DndOfferState::Dropped;
2299 }
2300
2301 return;
2302 }
2303 #[cfg(all(feature = "wayland", target_os = "linux"))]
2304 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {}
2305 #[cfg(all(feature = "wayland", target_os = "linux"))]
2306 Event::Dnd(DndEvent::Offer(
2307 rectangle,
2308 OfferEvent::Leave | OfferEvent::LeaveDestination,
2309 )) => {
2310 cold();
2311 let state = state();
2312 match state.dnd_offer {
2315 DndOfferState::Dropped => {}
2316 _ => {
2317 state.dnd_offer = DndOfferState::None;
2318 }
2319 };
2320 shell.capture_event();
2321 return;
2322 }
2323 #[cfg(all(feature = "wayland", target_os = "linux"))]
2324 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2325 if *rectangle == Some(dnd_id) =>
2326 {
2327 cold();
2328 let state = state();
2329 if matches!(&state.dnd_offer, DndOfferState::Dropped) {
2330 state.dnd_offer = DndOfferState::None;
2331 if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2332 shell.capture_event();
2333 return;
2334 }
2335 let Ok(content) = String::from_utf8(data.clone()) else {
2336 shell.capture_event();
2337 return;
2338 };
2339
2340 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2341
2342 editor.paste(Value::new(content.as_str()));
2343 let contents = editor.contents();
2344 let unsecured_value = Value::new(&contents);
2345 state.tracked_value = unsecured_value.clone();
2346 if let Some(on_paste) = on_paste.as_ref() {
2347 let message = (on_paste)(contents);
2348 shell.publish(message);
2349 }
2350
2351 let value = if is_secure {
2352 unsecured_value.secure()
2353 } else {
2354 unsecured_value
2355 };
2356 update_cache(state, &value);
2357 shell.capture_event();
2358 return;
2359 }
2360 return;
2361 }
2362 _ => {}
2363 }
2364}
2365
2366fn input_method<'b>(
2367 state: &'b State,
2368 text_layout: Layout<'_>,
2369 value: &Value,
2370) -> InputMethod<&'b str> {
2371 if !state.is_focused() {
2372 return InputMethod::Disabled;
2373 };
2374
2375 let text_bounds = text_layout.bounds();
2376 let cursor_index = match state.cursor.state(value) {
2377 cursor::State::Index(position) => position,
2378 cursor::State::Selection { start, end } => start.min(end),
2379 };
2380 let (cursor, offset) = measure_cursor_and_scroll_offset(
2381 state.value.raw(),
2382 text_bounds,
2383 cursor_index,
2384 value,
2385 state.cursor.affinity(),
2386 state.scroll_offset,
2387 );
2388 InputMethod::Enabled {
2389 cursor: Rectangle::new(
2390 Point::new(text_bounds.x + cursor - offset, text_bounds.y),
2391 Size::new(1.0, text_bounds.height),
2392 ),
2393 purpose: if state.is_secure {
2394 input_method::Purpose::Secure
2395 } else {
2396 input_method::Purpose::Normal
2397 },
2398 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
2399 }
2400}
2401
2402#[allow(clippy::too_many_arguments)]
2407#[allow(clippy::too_many_lines)]
2408#[allow(clippy::missing_panics_doc)]
2409pub fn draw<'a, Message>(
2410 renderer: &mut crate::Renderer,
2411 theme: &crate::Theme,
2412 layout: Layout<'_>,
2413 text_layout: Layout<'_>,
2414 cursor_position: mouse::Cursor,
2415 tree: &Tree,
2416 value: &Value,
2417 placeholder: &str,
2418 size: Option<f32>,
2419 font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2420 is_disabled: bool,
2421 is_secure: bool,
2422 icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2423 trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2424 style: &<crate::Theme as StyleSheet>::Style,
2425 dnd_icon: bool,
2426 line_height: text::LineHeight,
2427 error: Option<&str>,
2428 label: Option<&str>,
2429 helper_text: Option<&str>,
2430 helper_text_size: f32,
2431 helper_line_height: text::LineHeight,
2432 viewport: &Rectangle,
2433 renderer_style: &renderer::Style,
2434) {
2435 let children = &tree.children;
2437
2438 let state = tree.state.downcast_ref::<State>();
2439 let secure_value = is_secure.then(|| value.secure());
2440 let value = secure_value.as_ref().unwrap_or(value);
2441
2442 let mut children_layout = layout.children();
2443
2444 let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2445 let label_layout = children_layout.next();
2446 let layout = children_layout.next().unwrap();
2447 let helper_text_layout = children_layout.next();
2448 (label_layout, layout, helper_text_layout)
2449 } else if label.is_some() {
2450 let label_layout = children_layout.next();
2451 let layout = children_layout.next().unwrap();
2452 (label_layout, layout, None)
2453 } else if helper_text.is_some() {
2454 let layout = children_layout.next().unwrap();
2455 let helper_text_layout = children_layout.next();
2456 (None, layout, helper_text_layout)
2457 } else {
2458 let layout = children_layout.next().unwrap();
2459
2460 (None, layout, None)
2461 };
2462
2463 let mut children_layout = layout.children();
2464 let bounds = layout.bounds();
2465 let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2467
2468 let is_mouse_over = cursor_position.is_over(bounds);
2469
2470 let appearance = if is_disabled {
2471 theme.disabled(style)
2472 } else if error.is_some() {
2473 theme.error(style)
2474 } else if state.is_focused() {
2475 theme.focused(style)
2476 } else if is_mouse_over {
2477 theme.hovered(style)
2478 } else {
2479 theme.active(style)
2480 };
2481
2482 let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2483 let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2484
2485 if is_disabled {
2487 let background = theme.current_container().component.base.into();
2488 icon_color = icon_color.blend_alpha(background, 0.5);
2489 text_color = text_color.blend_alpha(background, 0.5);
2490 }
2491
2492 if let Some(border_offset) = appearance.border_offset {
2494 let offset_bounds = Rectangle {
2495 x: bounds.x - border_offset,
2496 y: bounds.y - border_offset,
2497 width: border_offset.mul_add(2.0, bounds.width),
2498 height: border_offset.mul_add(2.0, bounds.height),
2499 };
2500 renderer.fill_quad(
2501 renderer::Quad {
2502 bounds,
2503 border: Border {
2504 radius: appearance.border_radius,
2505 width: appearance.border_width,
2506 ..Default::default()
2507 },
2508 shadow: Shadow {
2509 offset: Vector::new(0.0, 1.0),
2510 color: Color::TRANSPARENT,
2511 blur_radius: 0.0,
2512 },
2513 snap: true,
2514 },
2515 appearance.background,
2516 );
2517 renderer.fill_quad(
2518 renderer::Quad {
2519 bounds: offset_bounds,
2520 border: Border {
2521 width: appearance.border_width,
2522 color: appearance.border_color,
2523 radius: appearance.border_radius,
2524 },
2525 shadow: Shadow {
2526 offset: Vector::new(0.0, 1.0),
2527 color: Color::TRANSPARENT,
2528 blur_radius: 0.0,
2529 },
2530 snap: true,
2531 },
2532 Background::Color(Color::TRANSPARENT),
2533 );
2534 } else {
2535 renderer.fill_quad(
2536 renderer::Quad {
2537 bounds,
2538 border: Border {
2539 width: appearance.border_width,
2540 color: appearance.border_color,
2541 radius: appearance.border_radius,
2542 },
2543 shadow: Shadow {
2544 offset: Vector::new(0.0, 1.0),
2545 color: Color::TRANSPARENT,
2546 blur_radius: 0.0,
2547 },
2548 snap: true,
2549 },
2550 appearance.background,
2551 );
2552 }
2553
2554 if let (Some(label_layout), Some(label)) = (label_layout, label) {
2556 renderer.fill_text(
2557 Text {
2558 content: label.to_string(),
2559 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2560 font: font.unwrap_or_else(|| renderer.default_font()),
2561 bounds: label_layout.bounds().size(),
2562 align_x: text::Alignment::Left,
2563 align_y: alignment::Vertical::Top,
2564 line_height,
2565 shaping: text::Shaping::Advanced,
2566 wrapping: text::Wrapping::None,
2567 ellipsize: text::Ellipsize::None,
2568 },
2569 label_layout.bounds().position(),
2570 appearance.label_color,
2571 *viewport,
2572 );
2573 }
2574 let mut child_index = 0;
2575 let leading_icon_tree = children.get(child_index);
2576 let has_start_icon = icon.is_some();
2578 if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2579 let mut children = text_layout.children();
2580 let _ = children.next().unwrap();
2581 let icon_layout = children.next().unwrap();
2582
2583 icon.as_widget().draw(
2584 tree,
2585 renderer,
2586 theme,
2587 &renderer::Style {
2588 icon_color,
2589 text_color,
2590 scale_factor: renderer_style.scale_factor,
2591 },
2592 icon_layout,
2593 cursor_position,
2594 viewport,
2595 );
2596 child_index += 1;
2597 }
2598
2599 let text = value.to_string();
2600 let font = font.unwrap_or_else(|| renderer.default_font());
2601 let size = size.unwrap_or_else(|| renderer.default_size().0);
2602 let text_width = state.value.min_width();
2603 let actual_width = text_width.max(text_bounds.width);
2604
2605 let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2606 #[cfg(all(feature = "wayland", target_os = "linux"))]
2607 let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2608 #[cfg(not(all(feature = "wayland", target_os = "linux")))]
2609 let handling_dnd_offer = false;
2610 let (cursors, offset, is_selecting) = if let Some(focus) =
2611 state.is_focused.filter(|f| f.focused).or_else(|| {
2612 let now = Instant::now();
2613 handling_dnd_offer.then_some(Focus {
2614 needs_update: false,
2615 updated_at: now,
2616 now,
2617 focused: true,
2618 })
2619 }) {
2620 match state.cursor.state(value) {
2621 cursor::State::Index(position) => {
2622 let (text_value_width, _) = measure_cursor_and_scroll_offset(
2623 state.value.raw(),
2624 text_bounds,
2625 position,
2626 value,
2627 state.cursor.affinity(),
2628 state.scroll_offset,
2629 );
2630 let is_cursor_visible = handling_dnd_offer
2631 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2632 .is_multiple_of(2);
2633
2634 if is_cursor_visible && !dnd_icon {
2635 (
2636 vec![(
2637 renderer::Quad {
2638 bounds: Rectangle {
2639 x: (text_bounds.x + text_value_width).floor(),
2640 y: text_bounds.y,
2641 width: 1.0,
2642 height: text_bounds.height,
2643 },
2644 border: Border {
2645 width: 0.0,
2646 color: Color::TRANSPARENT,
2647 radius: radius_0,
2648 },
2649 shadow: Shadow {
2650 offset: Vector::ZERO,
2651 color: Color::TRANSPARENT,
2652 blur_radius: 0.0,
2653 },
2654 snap: true,
2655 },
2656 text_color,
2657 )],
2658 state.scroll_offset,
2659 false,
2660 )
2661 } else {
2662 (
2663 Vec::<(renderer::Quad, Color)>::new(),
2664 if dnd_icon { 0.0 } else { state.scroll_offset },
2665 false,
2666 )
2667 }
2668 }
2669 cursor::State::Selection { start, end } => {
2670 let left = start.min(end);
2671 let right = end.max(start);
2672
2673 if dnd_icon {
2674 (Vec::<(renderer::Quad, Color)>::new(), 0.0, true)
2675 } else {
2676 let lo_byte = value.byte_index_at_grapheme(left);
2677 let hi_byte = value.byte_index_at_grapheme(right);
2678
2679 let rects = state.value.raw().highlight(
2680 0,
2681 (lo_byte, text::Affinity::After),
2682 (hi_byte, text::Affinity::Before),
2683 );
2684
2685 let cursors: Vec<(renderer::Quad, Color)> = rects
2686 .into_iter()
2687 .map(|r| {
2688 (
2689 renderer::Quad {
2690 bounds: Rectangle {
2691 x: text_bounds.x + r.x,
2692 y: text_bounds.y,
2693 width: r.width,
2694 height: text_bounds.height,
2695 },
2696 border: Border {
2697 width: 0.0,
2698 color: Color::TRANSPARENT,
2699 radius: radius_0,
2700 },
2701 shadow: Shadow {
2702 offset: Vector::ZERO,
2703 color: Color::TRANSPARENT,
2704 blur_radius: 0.0,
2705 },
2706 snap: true,
2707 },
2708 appearance.selected_fill,
2709 )
2710 })
2711 .collect();
2712
2713 (cursors, state.scroll_offset, true)
2714 }
2715 }
2716 }
2717 } else {
2718 let unfocused_offset = match effective_alignment(state.value.raw()) {
2719 alignment::Horizontal::Right => {
2720 (state.value.raw().min_width() - text_bounds.width).max(0.0)
2721 }
2722 _ => 0.0,
2723 };
2724
2725 (
2726 Vec::<(renderer::Quad, Color)>::new(),
2727 unfocused_offset,
2728 false,
2729 )
2730 };
2731
2732 let render = |renderer: &mut crate::Renderer| {
2733 let alignment_offset = alignment_offset(
2734 text_bounds.width,
2735 state.value.raw().min_width(),
2736 effective_alignment(state.value.raw()),
2737 );
2738
2739 if cursors.is_empty() {
2740 renderer.with_translation(Vector::ZERO, |_| {});
2741 } else {
2742 renderer.with_translation(Vector::new(alignment_offset - offset, 0.0), |renderer| {
2743 for (quad, color) in &cursors {
2744 renderer.fill_quad(*quad, *color);
2745 }
2746 });
2747 }
2748
2749 let bounds = Rectangle {
2750 x: text_bounds.x + alignment_offset - offset,
2751 y: text_bounds.center_y(),
2752 width: actual_width,
2753 ..text_bounds
2754 };
2755 let color = if text.is_empty() {
2756 appearance.placeholder_color
2757 } else {
2758 text_color
2759 };
2760
2761 renderer.fill_text(
2762 Text {
2763 content: if text.is_empty() {
2764 placeholder.to_string()
2765 } else {
2766 text.clone()
2767 },
2768 font,
2769 bounds: bounds.size(),
2770 size: iced::Pixels(size),
2771 align_x: text::Alignment::Default,
2772 align_y: alignment::Vertical::Center,
2773 line_height: text::LineHeight::default(),
2774 shaping: text::Shaping::Advanced,
2775 wrapping: text::Wrapping::None,
2776 ellipsize: text::Ellipsize::None,
2777 },
2778 bounds.position(),
2779 color,
2780 text_bounds,
2781 );
2782 };
2783
2784 renderer.with_layer(text_bounds, render);
2787
2788 let trailing_icon_tree = children.get(child_index);
2789
2790 if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2792 let mut children = text_layout.children();
2793 let mut icon_layout = children.next().unwrap();
2794 if has_start_icon {
2795 icon_layout = children.next().unwrap();
2796 }
2797 icon_layout = children.next().unwrap();
2798
2799 icon.as_widget().draw(
2800 tree,
2801 renderer,
2802 theme,
2803 &renderer::Style {
2804 icon_color,
2805 text_color,
2806 scale_factor: renderer_style.scale_factor,
2807 },
2808 icon_layout,
2809 cursor_position,
2810 viewport,
2811 );
2812 }
2813
2814 if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
2816 renderer.fill_text(
2817 Text {
2818 content: helper_text.to_string(), size: iced::Pixels(helper_text_size),
2820 font,
2821 bounds: helper_text_layout.bounds().size(),
2822 align_x: text::Alignment::Left,
2823 align_y: alignment::Vertical::Top,
2824 line_height: helper_line_height,
2825 shaping: text::Shaping::Advanced,
2826 wrapping: text::Wrapping::None,
2827 ellipsize: text::Ellipsize::None,
2828 },
2829 helper_text_layout.bounds().position(),
2830 text_color,
2831 *viewport,
2832 );
2833 }
2834}
2835
2836#[must_use]
2838pub fn mouse_interaction(
2839 layout: Layout<'_>,
2840 cursor_position: mouse::Cursor,
2841 is_disabled: bool,
2842) -> mouse::Interaction {
2843 if cursor_position.is_over(layout.bounds()) {
2844 if is_disabled {
2845 mouse::Interaction::NotAllowed
2846 } else {
2847 mouse::Interaction::Text
2848 }
2849 } else {
2850 mouse::Interaction::default()
2851 }
2852}
2853
2854#[derive(Debug, Clone)]
2856pub struct TextInputString(pub String);
2857
2858#[cfg(all(feature = "wayland", target_os = "linux"))]
2859impl AsMimeTypes for TextInputString {
2860 fn available(&self) -> Cow<'static, [String]> {
2861 Cow::Owned(
2862 SUPPORTED_TEXT_MIME_TYPES
2863 .iter()
2864 .cloned()
2865 .map(String::from)
2866 .collect::<Vec<_>>(),
2867 )
2868 }
2869
2870 fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
2871 if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
2872 Some(Cow::Owned(self.0.clone().into_bytes()))
2873 } else {
2874 None
2875 }
2876 }
2877}
2878
2879#[derive(Debug, Clone, PartialEq)]
2880pub(crate) enum DraggingState {
2881 Selection,
2882 #[cfg(all(feature = "wayland", target_os = "linux"))]
2883 PrepareDnd(Point),
2884 #[cfg(all(feature = "wayland", target_os = "linux"))]
2885 Dnd(DndAction, String),
2886}
2887
2888#[cfg(all(feature = "wayland", target_os = "linux"))]
2889#[derive(Debug, Default, Clone)]
2890pub(crate) enum DndOfferState {
2891 #[default]
2892 None,
2893 HandlingOffer(Vec<String>, DndAction),
2894 Dropped,
2895}
2896#[derive(Debug, Default, Clone)]
2897#[cfg(not(all(feature = "wayland", target_os = "linux")))]
2898pub(crate) struct DndOfferState;
2899
2900#[derive(Debug, Default, Clone)]
2902#[must_use]
2903pub struct State {
2904 pub tracked_value: Value,
2905 pub value: crate::Plain,
2906 pub placeholder: crate::Plain,
2907 pub label: crate::Plain,
2908 pub helper_text: crate::Plain,
2909 pub dirty: bool,
2910 pub is_secure: bool,
2911 pub is_read_only: bool,
2912 pub emit_unfocus: bool,
2913 select_on_focus: bool,
2914 double_click_select_delimiter: Option<char>,
2915 is_focused: Option<Focus>,
2916 dragging_state: Option<DraggingState>,
2917 dnd_offer: DndOfferState,
2918 is_pasting: Option<Value>,
2919 last_click: Option<mouse::Click>,
2920 cursor: Cursor,
2921 preedit: Option<Preedit>,
2922 keyboard_modifiers: keyboard::Modifiers,
2923 scroll_offset: f32,
2924}
2925
2926#[derive(Debug, Clone, Copy)]
2927struct Focus {
2928 updated_at: Instant,
2929 now: Instant,
2930 focused: bool,
2931 needs_update: bool,
2932}
2933
2934impl State {
2935 pub fn new(
2937 is_secure: bool,
2938 is_read_only: bool,
2939 always_active: bool,
2940 select_on_focus: bool,
2941 ) -> Self {
2942 Self {
2943 is_secure,
2944 is_read_only,
2945 is_focused: always_active.then(|| {
2946 let now = Instant::now();
2947 Focus {
2948 updated_at: now,
2949 now,
2950 focused: true,
2951 needs_update: false,
2952 }
2953 }),
2954 select_on_focus,
2955 ..Self::default()
2956 }
2957 }
2958
2959 #[must_use]
2961 pub fn selected_text(&self, text: &str) -> Option<String> {
2962 let value = Value::new(text);
2963 match self.cursor.state(&value) {
2964 cursor::State::Index(_) => None,
2965 cursor::State::Selection { start, end } => {
2966 let left = start.min(end);
2967 let right = end.max(start);
2968 Some(text[left..right].to_string())
2969 }
2970 }
2971 }
2972
2973 #[cfg(all(feature = "wayland", target_os = "linux"))]
2974 #[must_use]
2976 pub fn dragged_text(&self) -> Option<String> {
2977 match self.dragging_state.as_ref() {
2978 Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
2979 _ => None,
2980 }
2981 }
2982
2983 pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
2985 Self {
2986 tracked_value: Value::default(),
2987 is_secure,
2988 value: crate::Plain::default(),
2989 placeholder: crate::Plain::default(),
2990 label: crate::Plain::default(),
2991 helper_text: crate::Plain::default(),
2992 is_read_only,
2993 emit_unfocus: false,
2994 is_focused: None,
2995 select_on_focus: false,
2996 double_click_select_delimiter: None,
2997 dragging_state: None,
2998 dnd_offer: DndOfferState::default(),
2999 is_pasting: None,
3000 last_click: None,
3001 cursor: Cursor::default(),
3002 preedit: None,
3003 keyboard_modifiers: keyboard::Modifiers::default(),
3004 scroll_offset: 0.0,
3005 dirty: false,
3006 }
3007 }
3008
3009 #[inline]
3011 #[must_use]
3012 pub fn is_focused(&self) -> bool {
3013 self.is_focused.is_some_and(|f| f.focused)
3014 }
3015
3016 #[inline]
3018 #[must_use]
3019 pub fn cursor(&self) -> Cursor {
3020 self.cursor
3021 }
3022
3023 #[cold]
3025 pub fn focus(&mut self) {
3026 let now = Instant::now();
3027 LAST_FOCUS_UPDATE.with(|x| x.set(now));
3028 let was_focused = self.is_focused.is_some_and(|f| f.focused);
3029 self.is_read_only = false;
3030 self.is_focused = Some(Focus {
3031 updated_at: now,
3032 now,
3033 focused: true,
3034 needs_update: false,
3035 });
3036
3037 if was_focused {
3038 return;
3039 }
3040 if self.select_on_focus {
3041 self.select_all()
3042 } else {
3043 self.move_cursor_to_end();
3044 }
3045 }
3046
3047 #[cold]
3049 pub(super) fn unfocus(&mut self) {
3050 self.move_cursor_to_front();
3051 self.last_click = None;
3052 self.is_focused = self.is_focused.map(|mut f| {
3053 f.focused = false;
3054 f.needs_update = false;
3055 f
3056 });
3057 self.dragging_state = None;
3058 self.is_pasting = None;
3059 self.keyboard_modifiers = keyboard::Modifiers::default();
3060 }
3061
3062 #[inline]
3064 pub fn move_cursor_to_front(&mut self) {
3065 self.cursor.move_to(0);
3066 }
3067
3068 #[inline]
3070 pub fn move_cursor_to_end(&mut self) {
3071 self.cursor.move_to(usize::MAX);
3072 }
3073
3074 #[inline]
3076 pub fn move_cursor_to(&mut self, position: usize) {
3077 self.cursor.move_to(position);
3078 }
3079
3080 #[inline]
3082 pub fn select_all(&mut self) {
3083 self.cursor.select_range(0, usize::MAX);
3084 }
3085
3086 #[inline]
3088 pub fn select_range(&mut self, start: usize, end: usize) {
3089 self.cursor.select_range(start, end);
3090 }
3091
3092 pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
3093 let (position, affinity) = find_cursor_position(bounds, value, self, target)
3094 .unwrap_or((0, text::Affinity::Before));
3095
3096 self.cursor.set_affinity(affinity);
3097 self.cursor.move_to(position);
3098 self.dragging_state = Some(DraggingState::Selection);
3099 }
3100}
3101
3102impl operation::Focusable for State {
3103 #[inline]
3104 fn is_focused(&self) -> bool {
3105 Self::is_focused(self)
3106 }
3107
3108 #[inline]
3109 fn focus(&mut self) {
3110 Self::focus(self);
3111 if let Some(focus) = self.is_focused.as_mut() {
3112 focus.needs_update = true;
3113 }
3114 }
3115
3116 #[inline]
3117 fn unfocus(&mut self) {
3118 Self::unfocus(self);
3119 if let Some(focus) = self.is_focused.as_mut() {
3120 focus.needs_update = true;
3121 }
3122 }
3123}
3124
3125impl operation::TextInput for State {
3126 #[inline]
3127 fn move_cursor_to_front(&mut self) {
3128 Self::move_cursor_to_front(self);
3129 }
3130
3131 #[inline]
3132 fn move_cursor_to_end(&mut self) {
3133 Self::move_cursor_to_end(self);
3134 }
3135
3136 #[inline]
3137 fn move_cursor_to(&mut self, position: usize) {
3138 Self::move_cursor_to(self, position);
3139 }
3140
3141 #[inline]
3142 fn select_all(&mut self) {
3143 Self::select_all(self);
3144 }
3145
3146 fn text(&self) -> &str {
3147 todo!()
3148 }
3149
3150 #[inline]
3151 fn select_range(&mut self, start: usize, end: usize) {
3152 Self::select_range(self, start, end);
3153 }
3154}
3155
3156#[inline(never)]
3157fn measure_cursor_and_scroll_offset(
3158 paragraph: &impl text::Paragraph,
3159 text_bounds: Rectangle,
3160 cursor_index: usize,
3161 value: &Value,
3162 affinity: text::Affinity,
3163 current_offset: f32,
3164) -> (f32, f32) {
3165 let byte_index = value.byte_index_at_grapheme(cursor_index);
3166 let position = paragraph
3167 .cursor_position(0, byte_index, affinity)
3168 .unwrap_or(Point::ORIGIN);
3169
3170 let offset = if position.x > current_offset + text_bounds.width - 5.0 {
3174 (position.x + 5.0) - text_bounds.width
3176 } else if position.x < current_offset + 5.0 {
3177 position.x - 5.0
3179 } else {
3180 current_offset
3182 };
3183
3184 let max_offset = (paragraph.min_width() - text_bounds.width).max(0.0);
3185 let offset = offset.clamp(0.0, max_offset);
3186
3187 (position.x, offset)
3188}
3189
3190#[inline(never)]
3193fn find_cursor_position(
3194 text_bounds: Rectangle,
3195 value: &Value,
3196 state: &State,
3197 x: f32,
3198) -> Option<(usize, text::Affinity)> {
3199 let value_str = value.to_string();
3200
3201 let hit = state.value.raw().hit_test(Point::new(
3202 x + state.scroll_offset,
3203 text_bounds.height / 2.0,
3204 ))?;
3205 let char_offset = hit.cursor();
3206 let affinity = hit.affinity();
3207
3208 let grapheme_count = unicode_segmentation::UnicodeSegmentation::graphemes(
3209 &value_str[..char_offset.min(value_str.len())],
3210 true,
3211 )
3212 .count();
3213
3214 Some((grapheme_count, affinity))
3215}
3216
3217#[inline(never)]
3218fn replace_paragraph(
3219 state: &mut State,
3220 layout: Layout<'_>,
3221 value: &Value,
3222 font: <crate::Renderer as iced_core::text::Renderer>::Font,
3223 text_size: Pixels,
3224 line_height: text::LineHeight,
3225 limits: &layout::Limits,
3226) {
3227 let mut children_layout = layout.children();
3228 let text_bounds = children_layout.next().unwrap();
3229 let bounds = limits.resolve(
3230 Length::Shrink,
3231 Length::Fill,
3232 Size::new(0., text_bounds.bounds().height),
3233 );
3234
3235 state.value = crate::Plain::new(Text {
3236 font,
3237 line_height,
3238 content: value.to_string(),
3239 bounds,
3240 size: text_size,
3241 align_x: text::Alignment::Default,
3242 align_y: alignment::Vertical::Top,
3243 shaping: text::Shaping::Advanced,
3244 wrapping: text::Wrapping::None,
3245 ellipsize: text::Ellipsize::None,
3246 });
3247}
3248
3249const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
3250
3251mod platform {
3252 use iced_core::keyboard;
3253
3254 #[inline]
3255 pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
3256 if cfg!(target_os = "macos") {
3257 modifiers.alt()
3258 } else {
3259 modifiers.control()
3260 }
3261 }
3262}
3263
3264#[inline(never)]
3265fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
3266 if state.is_focused() {
3267 let cursor = state.cursor();
3268
3269 let focus_position = match cursor.state(value) {
3270 cursor::State::Index(i) => i,
3271 cursor::State::Selection { end, .. } => end,
3272 };
3273
3274 let (_, offset) = measure_cursor_and_scroll_offset(
3275 state.value.raw(),
3276 text_bounds,
3277 focus_position,
3278 value,
3279 state.cursor().affinity(),
3280 state.scroll_offset,
3281 );
3282
3283 offset
3284 } else {
3285 match effective_alignment(state.value.raw()) {
3286 alignment::Horizontal::Right => {
3287 (state.value.raw().min_width() - text_bounds.width).max(0.0)
3288 }
3289 _ => 0.0,
3290 }
3291 }
3292}
3293
3294#[inline(never)]
3295fn alignment_offset(
3296 text_bounds_width: f32,
3297 text_min_width: f32,
3298 alignment: alignment::Horizontal,
3299) -> f32 {
3300 if text_min_width > text_bounds_width {
3301 0.0
3302 } else {
3303 match alignment {
3304 alignment::Horizontal::Left => 0.0,
3305 alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0,
3306 alignment::Horizontal::Right => text_bounds_width - text_min_width,
3307 }
3308 }
3309}
3310
3311#[inline(never)]
3312fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizontal {
3313 if paragraph.is_rtl(0).unwrap_or(false) {
3314 alignment::Horizontal::Right
3315 } else {
3316 alignment::Horizontal::Left
3317 }
3318}