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