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