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 has_text = !state.tracked_value.is_empty();
1058 let clipboard_has_text = state.clipboard_has_text;
1059 let click_position = state.context_menu_position.unwrap();
1060 let menu_bar_state = state.menu_bar_state.clone();
1061 let pending_action = state.pending_action.clone();
1062
1063 crate::widget::text_context_menu::create_text_context_popup(
1064 click_position,
1065 selected_text,
1066 true,
1067 has_selection,
1068 has_text,
1069 clipboard_has_text,
1070 &menu_bar_state,
1071 &pending_action,
1072 renderer,
1073 viewport,
1074 cursor_position,
1075 self.window_id,
1076 );
1077
1078 let state = tree.state.downcast_mut::<State>();
1079 state.context_menu_position = None;
1080 }
1081
1082 let state = tree.state.downcast_ref::<State>();
1084 let pending_action = state.pending_action.clone();
1085 if let Some(action) =
1086 crate::widget::text_context_menu::take_pending_action(&pending_action)
1087 {
1088 let state = tree.state.downcast_mut::<State>();
1089 match action {
1090 crate::widget::text_context_menu::TextCtxAction::Copy => {}
1091 crate::widget::text_context_menu::TextCtxAction::Cut => {
1092 let contents = state.delete_selection();
1093 if let Some(on_input) = self.on_input.as_deref() {
1094 shell.publish((on_input)(contents));
1095 }
1096 }
1097 crate::widget::text_context_menu::TextCtxAction::Paste => {
1098 let content: String = clipboard
1099 .read(iced_core::clipboard::Kind::Standard)
1100 .unwrap_or_default();
1101 let filtered: String =
1102 content.chars().filter(|c| !c.is_control()).collect();
1103 let contents = state.paste_text(&filtered);
1104 if let Some(on_input) = self.on_input.as_deref() {
1105 shell.publish((on_input)(contents));
1106 }
1107 }
1108 crate::widget::text_context_menu::TextCtxAction::SelectAll => {
1109 state.select_all();
1110 }
1111 }
1112 }
1113 }
1114
1115 let state = tree.state.downcast_mut::<State>();
1116 let value = if self.is_secure {
1117 self.value.secure()
1118 } else {
1119 self.value.clone()
1120 };
1121 state.scroll_offset = offset(
1122 text_layout.children().next().unwrap().bounds(),
1123 &value,
1124 state,
1125 );
1126 }
1127
1128 #[inline]
1129 fn draw(
1130 &self,
1131 tree: &Tree,
1132 renderer: &mut crate::Renderer,
1133 theme: &crate::Theme,
1134 style: &renderer::Style,
1135 layout: Layout<'_>,
1136 cursor_position: mouse::Cursor,
1137 viewport: &Rectangle,
1138 ) {
1139 let text_layout = self.text_layout(layout);
1140 draw(
1141 renderer,
1142 theme,
1143 layout,
1144 text_layout,
1145 cursor_position,
1146 tree,
1147 &self.value,
1148 &self.placeholder,
1149 self.size,
1150 self.font,
1151 self.on_input.is_none() && !self.manage_value,
1152 self.is_secure,
1153 self.leading_icon.as_ref(),
1154 self.trailing_icon.as_ref(),
1155 &self.style,
1156 self.dnd_icon,
1157 self.line_height,
1158 self.error.as_deref(),
1159 self.label.as_deref(),
1160 self.helper_text.as_deref(),
1161 self.helper_size,
1162 self.helper_line_height,
1163 viewport,
1164 style,
1165 );
1166 }
1167
1168 fn mouse_interaction(
1169 &self,
1170 state: &Tree,
1171 layout: Layout<'_>,
1172 cursor_position: mouse::Cursor,
1173 viewport: &Rectangle,
1174 renderer: &crate::Renderer,
1175 ) -> mouse::Interaction {
1176 let layout = self.text_layout(layout);
1177 let mut index = 0;
1178 if let (Some(leading_icon), Some(tree)) =
1179 (self.leading_icon.as_ref(), state.children.get(index))
1180 {
1181 let leading_icon_layout = layout.children().nth(1).unwrap();
1182
1183 if cursor_position.is_over(leading_icon_layout.bounds()) {
1184 return leading_icon.as_widget().mouse_interaction(
1185 tree,
1186 layout,
1187 cursor_position,
1188 viewport,
1189 renderer,
1190 );
1191 }
1192 index += 1;
1193 }
1194
1195 if self.trailing_icon.is_some() {
1196 let mut children = layout.children();
1197 children.next();
1198 if self.leading_icon.is_some() {
1200 children.next();
1201 }
1202 let trailing_icon_layout = children.next().unwrap();
1203
1204 if cursor_position.is_over(trailing_icon_layout.bounds()) {
1205 if self.is_editable_variant {
1206 return mouse::Interaction::Pointer;
1207 }
1208
1209 if let Some((trailing_icon, tree)) =
1210 self.trailing_icon.as_ref().zip(state.children.get(index))
1211 {
1212 return trailing_icon.as_widget().mouse_interaction(
1213 tree,
1214 layout,
1215 cursor_position,
1216 viewport,
1217 renderer,
1218 );
1219 }
1220 }
1221 }
1222 let mut children = layout.children();
1223 let layout = children.next().unwrap();
1224 mouse_interaction(
1225 layout,
1226 cursor_position,
1227 self.on_input.is_none() && !self.manage_value,
1228 )
1229 }
1230
1231 #[inline]
1232 fn id(&self) -> Option<Id> {
1233 Some(self.id.clone())
1234 }
1235
1236 #[inline]
1237 fn set_id(&mut self, id: Id) {
1238 self.id = id;
1239 }
1240
1241 fn drag_destinations(
1242 &self,
1243 _state: &Tree,
1244 layout: Layout<'_>,
1245 _renderer: &crate::Renderer,
1246 dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
1247 ) {
1248 if let Some(input) = layout.children().last() {
1249 let Rectangle {
1250 x,
1251 y,
1252 width,
1253 height,
1254 } = input.bounds();
1255 dnd_rectangles.push(iced::clipboard::dnd::DndDestinationRectangle {
1256 id: self.dnd_id(),
1257 rectangle: iced::clipboard::dnd::Rectangle {
1258 x: x as f64,
1259 y: y as f64,
1260 width: width as f64,
1261 height: height as f64,
1262 },
1263 mime_types: SUPPORTED_TEXT_MIME_TYPES
1264 .iter()
1265 .map(|s| Cow::Borrowed(*s))
1266 .collect(),
1267 actions: DndAction::Move,
1268 preferred: DndAction::Move,
1269 });
1270 }
1271 }
1272}
1273
1274impl<'a, Message> From<TextInput<'a, Message>>
1275 for Element<'a, Message, crate::Theme, crate::Renderer>
1276where
1277 Message: 'static + Clone,
1278{
1279 fn from(
1280 text_input: TextInput<'a, Message>,
1281 ) -> Element<'a, Message, crate::Theme, crate::Renderer> {
1282 Element::new(text_input)
1283 }
1284}
1285
1286pub fn focus<Message: 'static>(id: Id) -> Task<Message> {
1288 task::effect(Action::widget(operation::focusable::focus(id)))
1289}
1290
1291pub fn move_cursor_to_end<Message: 'static>(id: Id) -> Task<Message> {
1294 task::effect(Action::widget(operation::text_input::move_cursor_to_end(
1295 id,
1296 )))
1297}
1298
1299pub fn move_cursor_to_front<Message: 'static>(id: Id) -> Task<Message> {
1302 task::effect(Action::widget(operation::text_input::move_cursor_to_front(
1303 id,
1304 )))
1305}
1306
1307pub fn move_cursor_to<Message: 'static>(id: Id, position: usize) -> Task<Message> {
1310 task::effect(Action::widget(operation::text_input::move_cursor_to(
1311 id, position,
1312 )))
1313}
1314
1315pub fn select_all<Message: 'static>(id: Id) -> Task<Message> {
1317 task::effect(Action::widget(operation::text_input::select_all(id)))
1318}
1319
1320pub fn select_range<Message: 'static>(id: Id, start: usize, end: usize) -> Task<Message> {
1323 task::effect(Action::widget(operation::text_input::select_range(
1324 id, start, end,
1325 )))
1326}
1327
1328pub fn select_until_last<Message: 'static>(id: Id, value: &str, ch: char) -> Task<Message> {
1331 let v = Value::new(value);
1332 let end = v.rfind_char(ch).unwrap_or(v.len());
1333 select_range(id, 0, end)
1334}
1335
1336#[allow(clippy::cast_precision_loss)]
1338#[allow(clippy::too_many_arguments)]
1339#[allow(clippy::too_many_lines)]
1340pub fn layout<Message>(
1341 renderer: &crate::Renderer,
1342 limits: &layout::Limits,
1343 width: Length,
1344 padding: Padding,
1345 size: Option<f32>,
1346 leading_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>,
1347 trailing_icon: Option<&mut Element<'_, Message, crate::Theme, crate::Renderer>>,
1348 line_height: text::LineHeight,
1349 label: Option<&str>,
1350 helper_text: Option<&str>,
1351 helper_text_size: f32,
1352 helper_text_line_height: text::LineHeight,
1353 font: iced_core::Font,
1354 tree: &mut Tree,
1355) -> layout::Node {
1356 let limits = limits.width(width);
1357 let spacing = THEME.lock().unwrap().cosmic().space_xxs();
1358 let mut nodes = Vec::with_capacity(3);
1359
1360 let text_pos = if let Some(label) = label {
1361 let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE);
1362 let state = tree.state.downcast_mut::<State>();
1363 let label_paragraph = &mut state.label;
1364 label_paragraph.update(Text {
1365 content: label,
1366 font,
1367 bounds: text_bounds,
1368 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
1369 align_x: text::Alignment::Left,
1370 align_y: alignment::Vertical::Center,
1371 line_height,
1372 shaping: text::Shaping::Advanced,
1373 wrapping: text::Wrapping::None,
1374 ellipsize: text::Ellipsize::None,
1375 });
1376 let label_size = label_paragraph.min_bounds();
1377
1378 nodes.push(layout::Node::new(label_size));
1379 Vector::new(0.0, label_size.height + f32::from(spacing))
1380 } else {
1381 Vector::ZERO
1382 };
1383
1384 let text_size = size.unwrap_or_else(|| renderer.default_size().0);
1385 let mut text_input_height = line_height.to_absolute(text_size.into()).0;
1386 let padding = padding.fit(Size::ZERO, limits.max());
1387
1388 let helper_pos = if leading_icon.is_some() || trailing_icon.is_some() {
1389 let children = &mut tree.children;
1390 let limits_copy = limits;
1392
1393 let limits = limits.shrink(padding);
1394 let icon_spacing = 8.0;
1395 let mut c_i = 0;
1396 let (leading_icon_width, mut leading_icon) =
1397 if let Some((icon, tree)) = leading_icon.zip(children.get_mut(c_i)) {
1398 let size = icon.as_widget().size();
1399 let icon_node = icon.as_widget_mut().layout(
1400 tree,
1401 renderer,
1402 &Limits::NONE.width(size.width).height(size.height),
1403 );
1404 text_input_height = text_input_height.max(icon_node.bounds().height);
1405 c_i += 1;
1406 (icon_node.bounds().width + icon_spacing, Some(icon_node))
1407 } else {
1408 (0.0, None)
1409 };
1410
1411 let (trailing_icon_width, mut trailing_icon) =
1412 if let Some((icon, tree)) = trailing_icon.zip(children.get_mut(c_i)) {
1413 let size = icon.as_widget().size();
1414 let icon_node = icon.as_widget_mut().layout(
1415 tree,
1416 renderer,
1417 &Limits::NONE.width(size.width).height(size.height),
1418 );
1419 text_input_height = text_input_height.max(icon_node.bounds().height);
1420 (icon_node.bounds().width + icon_spacing, Some(icon_node))
1421 } else {
1422 (0.0, None)
1423 };
1424 let text_limits = limits
1425 .width(width)
1426 .height(line_height.to_absolute(text_size.into()));
1427 let text_bounds = text_limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE);
1428 let text_node = layout::Node::new(
1429 text_bounds - Size::new(leading_icon_width + trailing_icon_width, 0.0),
1430 )
1431 .move_to(Point::new(
1432 padding.left + leading_icon_width,
1433 padding.top
1434 + ((text_input_height - line_height.to_absolute(text_size.into()).0) / 2.0)
1435 .max(0.0),
1436 ));
1437 let mut node_list: Vec<_> = Vec::with_capacity(3);
1438
1439 let text_node_bounds = text_node.bounds();
1440 node_list.push(text_node);
1441
1442 if let Some(leading_icon) = leading_icon.take() {
1443 node_list.push(leading_icon.clone().move_to(Point::new(
1444 padding.left,
1445 padding.top + ((text_input_height - leading_icon.bounds().height) / 2.0).max(0.0),
1446 )));
1447 }
1448 if let Some(trailing_icon) = trailing_icon.take() {
1449 let trailing_icon = trailing_icon.clone().move_to(Point::new(
1450 text_node_bounds.x + text_node_bounds.width + f32::from(spacing),
1451 padding.top + ((text_input_height - trailing_icon.bounds().height) / 2.0).max(0.0),
1452 ));
1453 node_list.push(trailing_icon);
1454 }
1455
1456 let text_input_size = Size::new(
1457 text_node_bounds.x + text_node_bounds.width + trailing_icon_width,
1458 text_input_height,
1459 )
1460 .expand(padding);
1461
1462 let input_limits = limits_copy
1463 .width(width)
1464 .height(text_input_height.max(text_input_size.height))
1465 .min_width(text_input_size.width);
1466 let input_bounds = input_limits.resolve(
1467 width,
1468 text_input_height.max(text_input_size.height),
1469 text_input_size,
1470 );
1471 let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos);
1472 let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing);
1473 nodes.push(input_node);
1474
1475 Vector::new(0.0, y_pos)
1476 } else {
1477 let limits = limits
1478 .width(width)
1479 .height(text_input_height + padding.y())
1480 .shrink(padding);
1481 let text_bounds = limits.resolve(Length::Shrink, Length::Shrink, Size::INFINITE);
1482
1483 let text = layout::Node::new(text_bounds).move_to(Point::new(padding.left, padding.top));
1484
1485 let node = layout::Node::with_children(text_bounds.expand(padding), vec![text])
1486 .translate(text_pos);
1487 let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
1488
1489 nodes.push(node);
1490
1491 Vector::new(0.0, y_pos)
1492 };
1493
1494 if let Some(helper_text) = helper_text {
1495 let limits = limits
1496 .width(width)
1497 .shrink(padding)
1498 .height(helper_text_line_height.to_absolute(helper_text_size.into()));
1499 let text_bounds = limits.resolve(width, Length::Shrink, Size::INFINITE);
1500 let state = tree.state.downcast_mut::<State>();
1501 let helper_text_paragraph = &mut state.helper_text;
1502 helper_text_paragraph.update(Text {
1503 content: helper_text,
1504 font,
1505 bounds: text_bounds,
1506 size: iced::Pixels(helper_text_size),
1507 align_x: text::Alignment::Left,
1508 align_y: alignment::Vertical::Center,
1509 line_height: helper_text_line_height,
1510 shaping: text::Shaping::Advanced,
1511 wrapping: text::Wrapping::None,
1512 ellipsize: text::Ellipsize::None,
1513 });
1514 let helper_text_size = helper_text_paragraph.min_bounds();
1515 let helper_text_node = layout::Node::new(helper_text_size).translate(helper_pos);
1516 nodes.push(helper_text_node);
1517 };
1518
1519 let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
1520 Size::new(
1521 size.width.max(node.bounds().width),
1522 size.height + node.bounds().height,
1523 )
1524 });
1525 size.height += (nodes.len() - 1) as f32 * f32::from(spacing);
1526
1527 let limits = limits
1528 .width(width)
1529 .height(size.height)
1530 .min_width(size.width);
1531
1532 layout::Node::with_children(limits.resolve(width, size.height, size), nodes)
1533}
1534
1535#[allow(clippy::too_many_arguments)]
1539#[allow(clippy::too_many_lines)]
1540#[allow(clippy::missing_panics_doc)]
1541#[allow(clippy::cast_lossless)]
1542#[allow(clippy::cast_possible_truncation)]
1543pub fn update<'a, Message: Clone + 'static>(
1544 id: Option<Id>,
1545 event: &Event,
1546 text_layout: Layout<'_>,
1547 edit_button_layout: Option<Layout<'_>>,
1548 cursor: mouse::Cursor,
1549 clipboard: &mut dyn Clipboard,
1550 shell: &mut Shell<'_, Message>,
1551 value: &mut Value,
1552 size: f32,
1553 font: <crate::Renderer as iced_core::text::Renderer>::Font,
1554 is_editable_variant: bool,
1555 is_secure: bool,
1556 on_focus: Option<&Message>,
1557 on_unfocus: Option<&Message>,
1558 on_input: Option<&dyn Fn(String) -> Message>,
1559 on_paste: Option<&dyn Fn(String) -> Message>,
1560 on_submit: Option<&dyn Fn(String) -> Message>,
1561 on_tab: Option<&Message>,
1562 on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
1563 state: impl FnOnce() -> &'a mut State,
1564 #[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
1565 #[allow(unused_variables)] dnd_id: u128,
1566 line_height: text::LineHeight,
1567 layout: Layout<'_>,
1568 manage_value: bool,
1569 drag_threshold: f32,
1570 always_active: bool,
1571) {
1572 let update_cache = |state, value| {
1573 replace_paragraph(
1574 state,
1575 layout,
1576 value,
1577 font,
1578 iced::Pixels(size),
1579 line_height,
1580 &Limits::NONE.max_width(text_layout.bounds().width),
1581 );
1582 };
1583
1584 let mut secured_value = if is_secure {
1585 value.secure()
1586 } else {
1587 value.clone()
1588 };
1589 let unsecured_value = value;
1590 let value = &mut secured_value;
1591
1592 #[inline]
1596 #[cold]
1597 fn cold() {}
1598
1599 let state = state();
1600
1601 if matches!(
1603 event,
1604 Event::Mouse(mouse::Event::ButtonPressed(_))
1605 | Event::Touch(touch::Event::FingerPressed { .. })
1606 ) && cursor.position_over(layout.bounds()).is_none()
1607 {
1608 state.is_focused = None;
1609 state.context_menu_position = None;
1610 state.dragging_state = None;
1611 if let Some(on_unfocus) = on_unfocus {
1612 shell.publish(on_unfocus.clone());
1613 }
1614 return;
1615 }
1616
1617 match event {
1618 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => {
1619 if let Some(pos) = cursor.position_over(layout.bounds()) {
1620 if !state.is_focused() {
1621 state.focus();
1622 }
1623 state.context_menu_position = Some(pos);
1624 state.clipboard_has_text = iced_core::widget::text::clipboard_has_text(clipboard);
1625 shell.capture_event();
1626 return;
1627 }
1628 }
1629
1630 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
1631 | Event::Touch(touch::Event::FingerPressed { .. }) => {
1632 cold();
1633
1634 if state.context_menu_position.take().is_some() {
1635 shell.capture_event();
1636 return;
1637 }
1638
1639 let click_position = if on_input.is_some() || manage_value {
1640 cursor.position_over(layout.bounds())
1641 } else {
1642 None
1643 };
1644
1645 if let Some(cursor_position) = click_position {
1646 if state.dragging_state.is_none()
1648 && edit_button_layout.is_some_and(|l| cursor.is_over(l.bounds()))
1649 {
1650 if is_editable_variant {
1651 let has_content = !unsecured_value.is_empty();
1652 let is_editing = !state.is_read_only;
1653
1654 if is_editing && has_content {
1655 if let Some(on_input) = on_input {
1656 shell.publish((on_input)(String::new()));
1657 }
1658
1659 if manage_value {
1660 *unsecured_value = Value::new("");
1661 state.tracked_value = unsecured_value.clone();
1662
1663 let cleared_value = if is_secure {
1664 unsecured_value.secure()
1665 } else {
1666 unsecured_value.clone()
1667 };
1668
1669 update_cache(state, &cleared_value);
1670 }
1671
1672 state.move_cursor_to_end();
1673 } else if is_editing {
1674 state.is_read_only = true;
1676 state.unfocus();
1677
1678 if let Some(on_toggle_edit) = on_toggle_edit {
1679 shell.publish(on_toggle_edit(false));
1680 }
1681 } else {
1682 state.is_read_only = false;
1684 state.cursor.select_range(0, value.len());
1685
1686 if let Some(on_toggle_edit) = on_toggle_edit {
1687 shell.publish(on_toggle_edit(true));
1688 }
1689
1690 let now = Instant::now();
1691 LAST_FOCUS_UPDATE.with(|x| x.set(now));
1692 state.is_focused = Some(Focus {
1693 updated_at: now,
1694 now,
1695 focused: true,
1696 needs_update: false,
1697 });
1698 }
1699 }
1700
1701 shell.capture_event();
1702 return;
1703 }
1704
1705 let target = {
1706 let text_bounds = text_layout.bounds();
1707
1708 let alignment_offset = alignment_offset(
1709 text_bounds.width,
1710 state.value.raw().min_width(),
1711 effective_alignment(state.value.raw()),
1712 );
1713
1714 cursor_position.x - text_bounds.x - alignment_offset
1715 };
1716
1717 let click =
1718 mouse::Click::new(cursor_position, mouse::Button::Left, state.last_click);
1719
1720 match (
1721 &state.dragging_state,
1722 click.kind(),
1723 state.cursor().state(value),
1724 ) {
1725 #[cfg(wayland_platform)]
1726 (None, click::Kind::Single, cursor::State::Selection { start, end }) => {
1727 let left = start.min(end);
1728 let right = end.max(start);
1729
1730 let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
1731 state.value.raw(),
1732 text_layout.bounds(),
1733 left,
1734 value,
1735 state.cursor.affinity(),
1736 state.scroll_offset,
1737 );
1738
1739 let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
1740 state.value.raw(),
1741 text_layout.bounds(),
1742 right,
1743 value,
1744 state.cursor.affinity(),
1745 state.scroll_offset,
1746 );
1747
1748 let selection_start = left_position.min(right_position);
1749 let width = (right_position - left_position).abs();
1750 let alignment_offset = alignment_offset(
1751 text_layout.bounds().width,
1752 state.value.raw().min_width(),
1753 effective_alignment(state.value.raw()),
1754 );
1755 let selection_bounds = Rectangle {
1756 x: text_layout.bounds().x + alignment_offset + selection_start
1757 - state.scroll_offset,
1758 y: text_layout.bounds().y,
1759 width,
1760 height: text_layout.bounds().height,
1761 };
1762
1763 if cursor.is_over(selection_bounds) && (on_input.is_some() || manage_value)
1764 {
1765 state.dragging_state = Some(DraggingState::PrepareDnd(cursor_position));
1766 shell.capture_event();
1767 return;
1768 }
1769 update_cache(state, value);
1771 state.setting_selection(value, text_layout.bounds(), target);
1772 state.dragging_state = None;
1773 shell.capture_event();
1774 return;
1775 }
1776 (None, click::Kind::Single, _) => {
1777 state.setting_selection(value, text_layout.bounds(), target);
1778 }
1779 (None | Some(DraggingState::Selection), click::Kind::Double, _) => {
1780 update_cache(state, value);
1781
1782 if is_secure {
1783 state.cursor.select_all(value);
1784 } else {
1785 let (position, affinity) =
1786 find_cursor_position(text_layout.bounds(), value, state, target)
1787 .unwrap_or((0, text::Affinity::Before));
1788
1789 state.cursor.set_affinity(affinity);
1790
1791 if let Some(delimiter) = state.double_click_select_delimiter {
1792 if let Some(delim_pos) = value.rfind_char(delimiter) {
1793 if position <= delim_pos {
1794 state.cursor.select_range(0, delim_pos);
1795 } else {
1796 state.cursor.select_range(delim_pos + 1, value.len());
1797 }
1798 } else {
1799 state.cursor.select_all(value);
1800 }
1801 } else {
1802 state.cursor.select_range(
1803 value.previous_start_of_word(position),
1804 value.next_end_of_word(position),
1805 );
1806 }
1807 }
1808 state.dragging_state = Some(DraggingState::Selection);
1809 }
1810 (None | Some(DraggingState::Selection), click::Kind::Triple, _) => {
1811 update_cache(state, value);
1812 state.cursor.select_all(value);
1813 state.dragging_state = Some(DraggingState::Selection);
1814 }
1815 _ => {
1816 state.dragging_state = None;
1817 }
1818 }
1819
1820 if matches!(state.dragging_state, None | Some(DraggingState::Selection))
1822 && (!state.is_focused() || (is_editable_variant && state.is_read_only))
1823 {
1824 if !state.is_focused() {
1825 if let Some(on_focus) = on_focus {
1826 shell.publish(on_focus.clone());
1827 }
1828 }
1829
1830 if state.is_read_only {
1831 state.is_read_only = false;
1832 state.cursor.select_range(0, value.len());
1833 if let Some(on_toggle_edit) = on_toggle_edit {
1834 let message = (on_toggle_edit)(true);
1835 shell.publish(message);
1836 }
1837 }
1838
1839 let now = Instant::now();
1840 LAST_FOCUS_UPDATE.with(|x| x.set(now));
1841
1842 state.is_focused = Some(Focus {
1843 updated_at: now,
1844 now,
1845 focused: true,
1846 needs_update: false,
1847 });
1848 }
1849
1850 state.last_click = Some(click);
1851
1852 shell.request_redraw();
1853 shell.capture_event();
1854 return;
1855 } else {
1856 state.unfocus();
1857
1858 if let Some(on_unfocus) = on_unfocus {
1859 shell.publish(on_unfocus.clone());
1860 }
1861 }
1862 }
1863 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
1864 | Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
1865 cold();
1866 #[cfg(wayland_platform)]
1867 if matches!(state.dragging_state, Some(DraggingState::PrepareDnd(_))) {
1868 update_cache(state, value);
1870 if let Some(position) = cursor.position_over(layout.bounds()) {
1871 let target = {
1872 let text_bounds = text_layout.bounds();
1873
1874 let alignment_offset = alignment_offset(
1875 text_bounds.width,
1876 state.value.raw().min_width(),
1877 effective_alignment(state.value.raw()),
1878 );
1879
1880 position.x - text_bounds.x - alignment_offset
1881 };
1882 state.setting_selection(value, text_layout.bounds(), target);
1883 }
1884 }
1885 state.dragging_state = None;
1886 if cursor.is_over(layout.bounds()) {
1887 shell.capture_event();
1888 }
1889 return;
1890 }
1891 Event::Mouse(mouse::Event::CursorMoved { position })
1892 | Event::Touch(touch::Event::FingerMoved { position, .. }) => {
1893 if matches!(state.dragging_state, Some(DraggingState::Selection)) {
1894 let target = {
1895 let text_bounds = text_layout.bounds();
1896
1897 let alignment_offset = alignment_offset(
1898 text_bounds.width,
1899 state.value.raw().min_width(),
1900 effective_alignment(state.value.raw()),
1901 );
1902
1903 position.x - text_bounds.x - alignment_offset
1904 };
1905
1906 update_cache(state, value);
1907 let (position, affinity) =
1908 find_cursor_position(text_layout.bounds(), value, state, target)
1909 .unwrap_or((0, text::Affinity::Before));
1910
1911 state.cursor.set_affinity(affinity);
1912 state
1913 .cursor
1914 .select_range(state.cursor.start(value), position);
1915
1916 shell.request_redraw();
1917 shell.capture_event();
1918 return;
1919 }
1920 #[cfg(wayland_platform)]
1921 if let Some(DraggingState::PrepareDnd(start_position)) = state.dragging_state {
1922 let distance = ((position.x - start_position.x).powi(2)
1923 + (position.y - start_position.y).powi(2))
1924 .sqrt();
1925
1926 if distance >= drag_threshold {
1927 if is_secure {
1928 return;
1929 }
1930
1931 let input_text = state.selected_text(&value.to_string()).unwrap_or_default();
1932 state.dragging_state =
1933 Some(DraggingState::Dnd(DndAction::empty(), input_text.clone()));
1934 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
1935 editor.delete();
1936
1937 let contents = editor.contents();
1938 let unsecured_value = Value::new(&contents);
1939 state.tracked_value = unsecured_value.clone();
1940 if let Some(on_input) = on_input {
1941 let message = (on_input)(contents);
1942 shell.publish(message);
1943 }
1944 if let Some(on_start_dnd) = on_start_dnd_source {
1945 shell.publish(on_start_dnd(state.clone()));
1946 }
1947 let state_clone = state.clone();
1948
1949 iced_core::clipboard::start_dnd(
1950 clipboard,
1951 false,
1952 id.map(iced_core::clipboard::DndSource::Widget),
1953 Some(iced_core::clipboard::IconSurface::new(
1954 Element::from(
1955 TextInput::<'static, ()>::new("", input_text.clone())
1956 .dnd_icon(true),
1957 ),
1958 iced_core::widget::tree::State::new(state_clone),
1959 Vector::ZERO,
1960 )),
1961 Box::new(TextInputString(input_text)),
1962 DndAction::Move,
1963 );
1964
1965 update_cache(state, &unsecured_value);
1966 } else {
1967 state.dragging_state = Some(DraggingState::PrepareDnd(start_position));
1968 }
1969
1970 shell.capture_event();
1971 return;
1972 }
1973 }
1974 Event::Keyboard(keyboard::Event::KeyPressed {
1975 key,
1976 text,
1977 physical_key,
1978 modifiers,
1979 ..
1980 }) => {
1981 state.keyboard_modifiers = *modifiers;
1982
1983 if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
1984 if state.is_read_only || (!manage_value && on_input.is_none()) {
1985 return;
1986 };
1987 let modifiers = state.keyboard_modifiers;
1988 focus.updated_at = Instant::now();
1989 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
1990
1991 let clip_key = match key.as_ref() {
1993 keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.shift() => {
1994 Some('v')
1995 }
1996 keyboard::Key::Named(keyboard::key::Named::Insert) if modifiers.command() => {
1997 Some('c')
1998 }
1999 keyboard::Key::Named(keyboard::key::Named::Delete) if modifiers.shift() => {
2000 Some('x')
2001 }
2002 _ if modifiers.command() => key.to_latin(*physical_key),
2003 _ => None,
2004 };
2005 {
2006 match clip_key {
2007 Some('c') => {
2008 if !is_secure {
2009 if let Some((start, end)) = state.cursor.selection(value) {
2010 clipboard.write(
2011 iced_core::clipboard::Kind::Standard,
2012 value.select(start, end).to_string(),
2013 );
2014 }
2015 }
2016 }
2017 Some('x') => {
2020 if !is_secure {
2021 if let Some((start, end)) = state.cursor.selection(value) {
2022 clipboard.write(
2023 iced_core::clipboard::Kind::Standard,
2024 value.select(start, end).to_string(),
2025 );
2026 }
2027
2028 let mut editor = Editor::new(value, &mut state.cursor);
2029 editor.delete();
2030 let content = editor.contents();
2031 state.tracked_value = Value::new(&content);
2032 if let Some(on_input) = on_input {
2033 let message = (on_input)(content);
2034 shell.publish(message);
2035 }
2036 }
2037 }
2038 Some('v') => {
2039 let content = if let Some(content) = state.is_pasting.take() {
2040 content
2041 } else {
2042 let content: String = clipboard
2043 .read(iced_core::clipboard::Kind::Standard)
2044 .unwrap_or_default()
2045 .chars()
2046 .filter(|c| !c.is_control())
2047 .collect();
2048
2049 Value::new(&content)
2050 };
2051
2052 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2053
2054 editor.paste(content.clone());
2055
2056 let contents = editor.contents();
2057 let unsecured_value = Value::new(&contents);
2058 state.tracked_value = unsecured_value.clone();
2059
2060 if let Some(on_input) = on_input {
2061 let message = if let Some(paste) = &on_paste {
2062 (paste)(contents)
2063 } else {
2064 (on_input)(contents)
2065 };
2066
2067 shell.publish(message);
2068 }
2069
2070 state.is_pasting = Some(content);
2071
2072 let value = if is_secure {
2073 unsecured_value.secure()
2074 } else {
2075 unsecured_value
2076 };
2077
2078 update_cache(state, &value);
2079 shell.capture_event();
2080 return;
2081 }
2082
2083 Some('a') => {
2084 state.cursor.select_all(value);
2085 shell.capture_event();
2086 return;
2087 }
2088
2089 _ => {}
2090 }
2091 }
2092
2093 if let Some(c) = text
2095 .as_ref()
2096 .and_then(|t| t.chars().next().filter(|c| !c.is_control()))
2097 {
2098 if state.is_read_only || (!manage_value && on_input.is_none()) {
2099 return;
2100 };
2101
2102 state.is_pasting = None;
2103
2104 if !state.keyboard_modifiers.command() && !modifiers.control() {
2105 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2106
2107 editor.insert(c);
2108
2109 let contents = editor.contents();
2110 let unsecured_value = Value::new(&contents);
2111 state.tracked_value = unsecured_value.clone();
2112
2113 if let Some(on_input) = on_input {
2114 let message = (on_input)(contents);
2115 shell.publish(message);
2116 }
2117
2118 focus.updated_at = Instant::now();
2119 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
2120
2121 let value = if is_secure {
2122 unsecured_value.secure()
2123 } else {
2124 unsecured_value
2125 };
2126
2127 update_cache(state, &value);
2128
2129 shell.capture_event();
2130 return;
2131 }
2132 }
2133
2134 match key.as_ref() {
2135 keyboard::Key::Named(keyboard::key::Named::Enter) => {
2136 if let Some(on_submit) = on_submit {
2137 shell.publish((on_submit)(unsecured_value.to_string()));
2138 }
2139 }
2140 keyboard::Key::Named(keyboard::key::Named::Backspace) => {
2141 if platform::is_jump_modifier_pressed(modifiers)
2142 && state.cursor.selection(value).is_none()
2143 {
2144 if is_secure {
2145 let cursor_pos = state.cursor.end(value);
2146 state.cursor.select_range(0, cursor_pos);
2147 } else {
2148 state.cursor.select_left_by_words(value);
2149 }
2150 }
2151
2152 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2153 editor.backspace();
2154
2155 let contents = editor.contents();
2156 let unsecured_value = Value::new(&contents);
2157 state.tracked_value = unsecured_value.clone();
2158 if let Some(on_input) = on_input {
2159 let message = (on_input)(editor.contents());
2160 shell.publish(message);
2161 }
2162 let value = if is_secure {
2163 unsecured_value.secure()
2164 } else {
2165 unsecured_value
2166 };
2167 update_cache(state, &value);
2168 }
2169 keyboard::Key::Named(keyboard::key::Named::Delete) => {
2170 if platform::is_jump_modifier_pressed(modifiers)
2171 && state.cursor.selection(value).is_none()
2172 {
2173 if is_secure {
2174 let cursor_pos = state.cursor.end(unsecured_value);
2175 state.cursor.select_range(cursor_pos, unsecured_value.len());
2176 } else {
2177 state.cursor.select_right_by_words(unsecured_value);
2178 }
2179 }
2180
2181 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2182 editor.delete();
2183 let contents = editor.contents();
2184 let unsecured_value = Value::new(&contents);
2185 if let Some(on_input) = on_input {
2186 let message = (on_input)(contents);
2187 state.tracked_value = unsecured_value.clone();
2188 shell.publish(message);
2189 }
2190
2191 let value = if is_secure {
2192 unsecured_value.secure()
2193 } else {
2194 unsecured_value
2195 };
2196
2197 update_cache(state, &value);
2198 }
2199 keyboard::Key::Named(keyboard::key::Named::ArrowLeft) => {
2200 let rtl = state.value.raw().is_rtl(0).unwrap_or(false);
2201 let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure;
2202
2203 if modifiers.shift() {
2204 state.cursor.select_visual(false, by_words, rtl, value);
2205 } else {
2206 state.cursor.move_visual(false, by_words, rtl, value);
2207 }
2208 }
2209 keyboard::Key::Named(keyboard::key::Named::ArrowRight) => {
2210 let rtl = state.value.raw().is_rtl(0).unwrap_or(false);
2211 let by_words = platform::is_jump_modifier_pressed(modifiers) && !is_secure;
2212
2213 if modifiers.shift() {
2214 state.cursor.select_visual(true, by_words, rtl, value);
2215 } else {
2216 state.cursor.move_visual(true, by_words, rtl, value);
2217 }
2218 }
2219 keyboard::Key::Named(keyboard::key::Named::Home) => {
2220 if modifiers.shift() {
2221 state.cursor.select_range(state.cursor.start(value), 0);
2222 } else {
2223 state.cursor.move_to(0);
2224 }
2225 }
2226 keyboard::Key::Named(keyboard::key::Named::End) => {
2227 if modifiers.shift() {
2228 state
2229 .cursor
2230 .select_range(state.cursor.start(value), value.len());
2231 } else {
2232 state.cursor.move_to(value.len());
2233 }
2234 }
2235 keyboard::Key::Named(keyboard::key::Named::Escape) => {
2236 state.unfocus();
2237 state.is_read_only = true;
2238
2239 if let Some(on_unfocus) = on_unfocus {
2240 shell.publish(on_unfocus.clone());
2241 }
2242
2243 shell.request_redraw();
2244 return;
2245 }
2246
2247 keyboard::Key::Named(keyboard::key::Named::Tab) => {
2248 if let Some(on_tab) = on_tab {
2249 shell.publish(on_tab.clone());
2253 } else {
2254 state.is_read_only = true;
2255
2256 if let Some(on_unfocus) = on_unfocus {
2257 shell.publish(on_unfocus.clone());
2258 }
2259
2260 return;
2261 };
2262 }
2263
2264 keyboard::Key::Named(
2265 keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
2266 ) => {
2267 return;
2268 }
2269 _ => {}
2270 }
2271
2272 shell.request_redraw();
2273 shell.capture_event();
2274 return;
2275 }
2276 }
2277 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
2278 if state.is_focused() {
2279 match key {
2280 keyboard::Key::Character(c) if "v" == c => {
2281 state.is_pasting = None;
2282 }
2283 keyboard::Key::Named(keyboard::key::Named::Insert) => {
2284 state.is_pasting = None;
2285 }
2286 keyboard::Key::Named(keyboard::key::Named::Tab)
2287 | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
2288 | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
2289 return;
2290 }
2291 _ => {}
2292 }
2293
2294 shell.capture_event();
2295 return;
2296 }
2297 }
2298 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
2299 state.keyboard_modifiers = *modifiers;
2300 }
2301 Event::InputMethod(event) => match event {
2302 input_method::Event::Opened | input_method::Event::Closed => {
2303 state.preedit =
2304 matches!(event, input_method::Event::Opened).then(input_method::Preedit::new);
2305 shell.capture_event();
2306 return;
2307 }
2308 input_method::Event::Preedit(content, selection) => {
2309 if state.is_focused() {
2310 state.preedit = Some(input_method::Preedit {
2311 content: content.to_owned(),
2312 selection: selection.clone(),
2313 text_size: Some(size.into()),
2314 });
2315 shell.capture_event();
2316 return;
2317 }
2318 }
2319 input_method::Event::Commit(text) => {
2320 let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
2321 return;
2322 };
2323 let Some(on_input) = on_input else {
2324 return;
2325 };
2326 if state.is_read_only {
2327 return;
2328 }
2329
2330 focus.updated_at = Instant::now();
2331 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
2332
2333 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2334 editor.paste(Value::new(&text));
2335
2336 let contents = editor.contents();
2337 let unsecured_value = Value::new(&contents);
2338 let message = if let Some(paste) = &on_paste {
2339 (paste)(contents)
2340 } else {
2341 (on_input)(contents)
2342 };
2343 shell.publish(message);
2344
2345 state.is_pasting = None;
2346 let value = if is_secure {
2347 unsecured_value.secure()
2348 } else {
2349 unsecured_value
2350 };
2351
2352 update_cache(state, &value);
2353 shell.capture_event();
2354 return;
2355 }
2356 },
2357 Event::Window(window::Event::RedrawRequested(now)) => {
2358 if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
2359 focus.now = *now;
2360
2361 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
2362 - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
2363 shell.request_redraw_at(window::RedrawRequest::At(
2364 now.checked_add(Duration::from_millis(millis_until_redraw as u64))
2365 .unwrap_or(*now),
2366 ));
2367
2368 shell.request_input_method(&input_method(state, text_layout, unsecured_value));
2369 } else if always_active {
2370 shell.request_redraw();
2371 }
2372 }
2373 #[cfg(wayland_platform)]
2374 Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
2375 cold();
2376 if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
2377 state.dragging_state = None;
2379 shell.capture_event();
2380 return;
2381 }
2382 }
2383 #[cfg(wayland_platform)]
2384 Event::Dnd(DndEvent::Offer(
2385 rectangle,
2386 OfferEvent::Enter {
2387 x,
2388 y,
2389 mime_types,
2390 surface,
2391 },
2392 )) if *rectangle == Some(dnd_id) => {
2393 cold();
2394 let is_clicked = text_layout.bounds().contains(Point {
2395 x: *x as f32,
2396 y: *y as f32,
2397 });
2398
2399 let mut accepted = false;
2400 for m in mime_types {
2401 if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
2402 let clone = m.clone();
2403 accepted = true;
2404 }
2405 }
2406 if accepted {
2407 let target = {
2408 let text_bounds = text_layout.bounds();
2409
2410 let alignment_offset = alignment_offset(
2411 text_bounds.width,
2412 state.value.raw().min_width(),
2413 effective_alignment(state.value.raw()),
2414 );
2415
2416 *x as f32 - text_bounds.x - alignment_offset
2417 };
2418 state.dnd_offer =
2419 DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
2420 update_cache(state, value);
2422 let (position, affinity) =
2423 find_cursor_position(text_layout.bounds(), value, state, target)
2424 .unwrap_or((0, text::Affinity::Before));
2425
2426 state.cursor.set_affinity(affinity);
2427 state.cursor.move_to(position);
2428 shell.capture_event();
2429 return;
2430 }
2431 }
2432 #[cfg(wayland_platform)]
2433 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
2434 if *rectangle == Some(dnd_id) =>
2435 {
2436 let target = {
2437 let text_bounds = text_layout.bounds();
2438
2439 let alignment_offset = alignment_offset(
2440 text_bounds.width,
2441 state.value.raw().min_width(),
2442 effective_alignment(state.value.raw()),
2443 );
2444
2445 *x as f32 - text_bounds.x - alignment_offset
2446 };
2447 update_cache(state, value);
2449 let (position, affinity) =
2450 find_cursor_position(text_layout.bounds(), value, state, target)
2451 .unwrap_or((0, text::Affinity::Before));
2452
2453 state.cursor.set_affinity(affinity);
2454 state.cursor.move_to(position);
2455 shell.capture_event();
2456 return;
2457 }
2458 #[cfg(wayland_platform)]
2459 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
2460 cold();
2461 if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
2462 let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
2463 .iter()
2464 .find(|&&m| mime_types.iter().any(|t| t == m))
2465 else {
2466 state.dnd_offer = DndOfferState::None;
2467 shell.capture_event();
2468 return;
2469 };
2470 state.dnd_offer = DndOfferState::Dropped;
2471 }
2472
2473 return;
2474 }
2475 #[cfg(wayland_platform)]
2476 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {}
2477 #[cfg(wayland_platform)]
2478 Event::Dnd(DndEvent::Offer(
2479 rectangle,
2480 OfferEvent::Leave | OfferEvent::LeaveDestination,
2481 )) => {
2482 cold();
2483 match state.dnd_offer {
2486 DndOfferState::Dropped => {}
2487 _ => {
2488 state.dnd_offer = DndOfferState::None;
2489 }
2490 };
2491 shell.capture_event();
2492 return;
2493 }
2494 #[cfg(wayland_platform)]
2495 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2496 if *rectangle == Some(dnd_id) =>
2497 {
2498 cold();
2499 if matches!(&state.dnd_offer, DndOfferState::Dropped) {
2500 state.dnd_offer = DndOfferState::None;
2501 if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2502 shell.capture_event();
2503 return;
2504 }
2505 let Ok(content) = String::from_utf8(data.clone()) else {
2506 shell.capture_event();
2507 return;
2508 };
2509
2510 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2511
2512 editor.paste(Value::new(content.as_str()));
2513 let contents = editor.contents();
2514 let unsecured_value = Value::new(&contents);
2515 state.tracked_value = unsecured_value.clone();
2516 if let Some(on_paste) = on_paste.as_ref() {
2517 let message = (on_paste)(contents);
2518 shell.publish(message);
2519 }
2520
2521 let value = if is_secure {
2522 unsecured_value.secure()
2523 } else {
2524 unsecured_value
2525 };
2526 update_cache(state, &value);
2527 shell.capture_event();
2528 return;
2529 }
2530 return;
2531 }
2532 _ => {}
2533 }
2534}
2535
2536fn input_method<'b>(
2537 state: &'b State,
2538 text_layout: Layout<'_>,
2539 value: &Value,
2540) -> InputMethod<&'b str> {
2541 if !state.is_focused() {
2542 return InputMethod::Disabled;
2543 };
2544
2545 let text_bounds = text_layout.bounds();
2546 let cursor_index = match state.cursor.state(value) {
2547 cursor::State::Index(position) => position,
2548 cursor::State::Selection { start, end } => start.min(end),
2549 };
2550 let (cursor, offset) = measure_cursor_and_scroll_offset(
2551 state.value.raw(),
2552 text_bounds,
2553 cursor_index,
2554 value,
2555 state.cursor.affinity(),
2556 state.scroll_offset,
2557 );
2558 InputMethod::Enabled {
2559 cursor: Rectangle::new(
2560 Point::new(text_bounds.x + cursor - offset, text_bounds.y),
2561 Size::new(1.0, text_bounds.height),
2562 ),
2563 purpose: if state.is_secure {
2564 input_method::Purpose::Secure
2565 } else {
2566 input_method::Purpose::Normal
2567 },
2568 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
2569 }
2570}
2571
2572#[allow(clippy::too_many_arguments)]
2577#[allow(clippy::too_many_lines)]
2578#[allow(clippy::missing_panics_doc)]
2579pub fn draw<'a, Message>(
2580 renderer: &mut crate::Renderer,
2581 theme: &crate::Theme,
2582 layout: Layout<'_>,
2583 text_layout: Layout<'_>,
2584 cursor_position: mouse::Cursor,
2585 tree: &Tree,
2586 value: &Value,
2587 placeholder: &str,
2588 size: Option<f32>,
2589 font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2590 is_disabled: bool,
2591 is_secure: bool,
2592 icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2593 trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2594 style: &<crate::Theme as StyleSheet>::Style,
2595 dnd_icon: bool,
2596 line_height: text::LineHeight,
2597 error: Option<&str>,
2598 label: Option<&str>,
2599 helper_text: Option<&str>,
2600 helper_text_size: f32,
2601 helper_line_height: text::LineHeight,
2602 viewport: &Rectangle,
2603 renderer_style: &renderer::Style,
2604) {
2605 let children = &tree.children;
2607
2608 let state = tree.state.downcast_ref::<State>();
2609 let secure_value = is_secure.then(|| value.secure());
2610 let value = secure_value.as_ref().unwrap_or(value);
2611
2612 let mut children_layout = layout.children();
2613
2614 let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2615 let label_layout = children_layout.next();
2616 let layout = children_layout.next().unwrap();
2617 let helper_text_layout = children_layout.next();
2618 (label_layout, layout, helper_text_layout)
2619 } else if label.is_some() {
2620 let label_layout = children_layout.next();
2621 let layout = children_layout.next().unwrap();
2622 (label_layout, layout, None)
2623 } else if helper_text.is_some() {
2624 let layout = children_layout.next().unwrap();
2625 let helper_text_layout = children_layout.next();
2626 (None, layout, helper_text_layout)
2627 } else {
2628 let layout = children_layout.next().unwrap();
2629
2630 (None, layout, None)
2631 };
2632
2633 let mut children_layout = layout.children();
2634 let bounds = layout.bounds();
2635 let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2637
2638 let is_mouse_over = cursor_position.is_over(bounds);
2639
2640 let appearance = if is_disabled {
2641 theme.disabled(style)
2642 } else if error.is_some() {
2643 theme.error(style)
2644 } else if state.is_focused() {
2645 theme.focused(style)
2646 } else if is_mouse_over {
2647 theme.hovered(style)
2648 } else {
2649 theme.active(style)
2650 };
2651
2652 let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2653 let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2654
2655 if is_disabled {
2657 let background = theme.current_container().component.base.into();
2658 icon_color = icon_color.blend_alpha(background, 0.5);
2659 text_color = text_color.blend_alpha(background, 0.5);
2660 }
2661
2662 if let Some(border_offset) = appearance.border_offset {
2664 let offset_bounds = Rectangle {
2665 x: bounds.x - border_offset,
2666 y: bounds.y - border_offset,
2667 width: border_offset.mul_add(2.0, bounds.width),
2668 height: border_offset.mul_add(2.0, bounds.height),
2669 };
2670 renderer.fill_quad(
2671 renderer::Quad {
2672 bounds,
2673 border: Border {
2674 radius: appearance.border_radius,
2675 width: appearance.border_width,
2676 ..Default::default()
2677 },
2678 shadow: Shadow {
2679 offset: Vector::new(0.0, 1.0),
2680 color: Color::TRANSPARENT,
2681 blur_radius: 0.0,
2682 },
2683 snap: true,
2684 },
2685 appearance.background,
2686 );
2687 renderer.fill_quad(
2688 renderer::Quad {
2689 bounds: offset_bounds,
2690 border: Border {
2691 width: appearance.border_width,
2692 color: appearance.border_color,
2693 radius: appearance.border_radius,
2694 },
2695 shadow: Shadow {
2696 offset: Vector::new(0.0, 1.0),
2697 color: Color::TRANSPARENT,
2698 blur_radius: 0.0,
2699 },
2700 snap: true,
2701 },
2702 Background::Color(Color::TRANSPARENT),
2703 );
2704 } else {
2705 renderer.fill_quad(
2706 renderer::Quad {
2707 bounds,
2708 border: Border {
2709 width: appearance.border_width,
2710 color: appearance.border_color,
2711 radius: appearance.border_radius,
2712 },
2713 shadow: Shadow {
2714 offset: Vector::new(0.0, 1.0),
2715 color: Color::TRANSPARENT,
2716 blur_radius: 0.0,
2717 },
2718 snap: true,
2719 },
2720 appearance.background,
2721 );
2722 }
2723
2724 if let (Some(label_layout), Some(label)) = (label_layout, label) {
2726 renderer.fill_text(
2727 Text {
2728 content: label.to_string(),
2729 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2730 font: font.unwrap_or_else(|| renderer.default_font()),
2731 bounds: label_layout.bounds().size(),
2732 align_x: text::Alignment::Left,
2733 align_y: alignment::Vertical::Top,
2734 line_height,
2735 shaping: text::Shaping::Advanced,
2736 wrapping: text::Wrapping::None,
2737 ellipsize: text::Ellipsize::None,
2738 },
2739 label_layout.bounds().position(),
2740 appearance.label_color,
2741 *viewport,
2742 );
2743 }
2744 let mut child_index = 0;
2745 let leading_icon_tree = children.get(child_index);
2746 let has_start_icon = icon.is_some();
2748 if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2749 let mut children = text_layout.children();
2750 let _ = children.next().unwrap();
2751 let icon_layout = children.next().unwrap();
2752
2753 icon.as_widget().draw(
2754 tree,
2755 renderer,
2756 theme,
2757 &renderer::Style {
2758 icon_color,
2759 text_color,
2760 scale_factor: renderer_style.scale_factor,
2761 },
2762 icon_layout,
2763 cursor_position,
2764 viewport,
2765 );
2766 child_index += 1;
2767 }
2768
2769 let text = value.to_string();
2770 let font = font.unwrap_or_else(|| renderer.default_font());
2771 let size = size.unwrap_or_else(|| renderer.default_size().0);
2772 let text_width = state.value.min_width();
2773 let actual_width = text_width.max(text_bounds.width);
2774
2775 let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2776 #[cfg(wayland_platform)]
2777 let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2778 #[cfg(not(wayland_platform))]
2779 let handling_dnd_offer = false;
2780 let (cursors, offset, is_selecting) = if let Some(focus) =
2781 state.is_focused.filter(|f| f.focused).or_else(|| {
2782 let now = Instant::now();
2783 handling_dnd_offer.then_some(Focus {
2784 needs_update: false,
2785 updated_at: now,
2786 now,
2787 focused: true,
2788 })
2789 }) {
2790 match state.cursor.state(value) {
2791 cursor::State::Index(position) => {
2792 let (text_value_width, _) = measure_cursor_and_scroll_offset(
2793 state.value.raw(),
2794 text_bounds,
2795 position,
2796 value,
2797 state.cursor.affinity(),
2798 state.scroll_offset,
2799 );
2800 let is_cursor_visible = handling_dnd_offer
2801 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2802 .is_multiple_of(2);
2803
2804 if is_cursor_visible && !dnd_icon {
2805 (
2806 vec![(
2807 renderer::Quad {
2808 bounds: Rectangle {
2809 x: (text_bounds.x + text_value_width).floor(),
2810 y: text_bounds.y,
2811 width: 1.0,
2812 height: text_bounds.height,
2813 },
2814 border: Border {
2815 width: 0.0,
2816 color: Color::TRANSPARENT,
2817 radius: radius_0,
2818 },
2819 shadow: Shadow {
2820 offset: Vector::ZERO,
2821 color: Color::TRANSPARENT,
2822 blur_radius: 0.0,
2823 },
2824 snap: true,
2825 },
2826 text_color,
2827 )],
2828 state.scroll_offset,
2829 false,
2830 )
2831 } else {
2832 (
2833 Vec::<(renderer::Quad, Color)>::new(),
2834 if dnd_icon { 0.0 } else { state.scroll_offset },
2835 false,
2836 )
2837 }
2838 }
2839 cursor::State::Selection { start, end } => {
2840 let left = start.min(end);
2841 let right = end.max(start);
2842
2843 if dnd_icon {
2844 (Vec::<(renderer::Quad, Color)>::new(), 0.0, true)
2845 } else {
2846 let lo_byte = value.byte_index_at_grapheme(left);
2847 let hi_byte = value.byte_index_at_grapheme(right);
2848
2849 let rects = state.value.raw().highlight(
2850 0,
2851 (lo_byte, text::Affinity::After),
2852 (hi_byte, text::Affinity::Before),
2853 );
2854
2855 let cursors: Vec<(renderer::Quad, Color)> = rects
2856 .into_iter()
2857 .map(|r| {
2858 (
2859 renderer::Quad {
2860 bounds: Rectangle {
2861 x: text_bounds.x + r.x,
2862 y: text_bounds.y,
2863 width: r.width,
2864 height: text_bounds.height,
2865 },
2866 border: Border {
2867 width: 0.0,
2868 color: Color::TRANSPARENT,
2869 radius: radius_0,
2870 },
2871 shadow: Shadow {
2872 offset: Vector::ZERO,
2873 color: Color::TRANSPARENT,
2874 blur_radius: 0.0,
2875 },
2876 snap: true,
2877 },
2878 appearance.selected_fill,
2879 )
2880 })
2881 .collect();
2882
2883 (cursors, state.scroll_offset, true)
2884 }
2885 }
2886 }
2887 } else {
2888 let unfocused_offset = match effective_alignment(state.value.raw()) {
2889 alignment::Horizontal::Right => {
2890 (state.value.raw().min_width() - text_bounds.width).max(0.0)
2891 }
2892 _ => 0.0,
2893 };
2894
2895 (
2896 Vec::<(renderer::Quad, Color)>::new(),
2897 unfocused_offset,
2898 false,
2899 )
2900 };
2901
2902 let render = |renderer: &mut crate::Renderer| {
2903 let alignment_offset = alignment_offset(
2904 text_bounds.width,
2905 state.value.raw().min_width(),
2906 effective_alignment(state.value.raw()),
2907 );
2908
2909 let shift = Vector::new(alignment_offset - offset, 0.0);
2910 let fill_cursors = |renderer: &mut crate::Renderer| {
2911 renderer.with_translation(shift, |renderer| {
2912 for (quad, color) in &cursors {
2913 renderer.fill_quad(*quad, *color);
2914 }
2915 });
2916 };
2917
2918 if !is_selecting {
2919 fill_cursors(renderer);
2920 }
2921
2922 let bounds = Rectangle {
2923 x: text_bounds.x + alignment_offset - offset,
2924 y: text_bounds.center_y(),
2925 width: actual_width,
2926 ..text_bounds
2927 };
2928 let color = if text.is_empty() {
2929 appearance.placeholder_color
2930 } else {
2931 text_color
2932 };
2933
2934 let text = Text {
2935 content: if text.is_empty() {
2936 placeholder.to_string()
2937 } else {
2938 text.clone()
2939 },
2940 font,
2941 bounds: bounds.size(),
2942 size: iced::Pixels(size),
2943 align_x: text::Alignment::Default,
2944 align_y: alignment::Vertical::Center,
2945 line_height: text::LineHeight::default(),
2946 shaping: text::Shaping::Advanced,
2947 wrapping: text::Wrapping::None,
2948 ellipsize: text::Ellipsize::None,
2949 };
2950 renderer.fill_text(text.clone(), bounds.position(), color, text_bounds);
2951
2952 if is_selecting {
2955 fill_cursors(renderer);
2956 for (quad, _) in &cursors {
2957 renderer.with_layer(quad.bounds + shift, |renderer| {
2958 renderer.fill_text(
2959 text.clone(),
2960 bounds.position(),
2961 appearance.selected_text_color,
2962 text_bounds,
2963 );
2964 });
2965 }
2966 }
2967 };
2968
2969 renderer.with_layer(text_bounds, render);
2972
2973 let trailing_icon_tree = children.get(child_index);
2974
2975 if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2977 let mut children = text_layout.children();
2978 let mut icon_layout = children.next().unwrap();
2979 if has_start_icon {
2980 icon_layout = children.next().unwrap();
2981 }
2982 icon_layout = children.next().unwrap();
2983
2984 icon.as_widget().draw(
2985 tree,
2986 renderer,
2987 theme,
2988 &renderer::Style {
2989 icon_color,
2990 text_color,
2991 scale_factor: renderer_style.scale_factor,
2992 },
2993 icon_layout,
2994 cursor_position,
2995 viewport,
2996 );
2997 }
2998
2999 if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
3001 renderer.fill_text(
3002 Text {
3003 content: helper_text.to_string(), size: iced::Pixels(helper_text_size),
3005 font,
3006 bounds: helper_text_layout.bounds().size(),
3007 align_x: text::Alignment::Left,
3008 align_y: alignment::Vertical::Top,
3009 line_height: helper_line_height,
3010 shaping: text::Shaping::Advanced,
3011 wrapping: text::Wrapping::None,
3012 ellipsize: text::Ellipsize::None,
3013 },
3014 helper_text_layout.bounds().position(),
3015 text_color,
3016 *viewport,
3017 );
3018 }
3019}
3020
3021#[must_use]
3023pub fn mouse_interaction(
3024 layout: Layout<'_>,
3025 cursor_position: mouse::Cursor,
3026 is_disabled: bool,
3027) -> mouse::Interaction {
3028 if cursor_position.is_over(layout.bounds()) {
3029 if is_disabled {
3030 mouse::Interaction::NotAllowed
3031 } else {
3032 mouse::Interaction::Text
3033 }
3034 } else {
3035 mouse::Interaction::default()
3036 }
3037}
3038
3039#[derive(Debug, Clone)]
3041pub struct TextInputString(pub String);
3042
3043#[cfg(wayland_platform)]
3044impl AsMimeTypes for TextInputString {
3045 fn available(&self) -> Cow<'static, [String]> {
3046 Cow::Owned(
3047 SUPPORTED_TEXT_MIME_TYPES
3048 .iter()
3049 .cloned()
3050 .map(String::from)
3051 .collect::<Vec<_>>(),
3052 )
3053 }
3054
3055 fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
3056 if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
3057 Some(Cow::Owned(self.0.clone().into_bytes()))
3058 } else {
3059 None
3060 }
3061 }
3062}
3063
3064#[derive(Debug, Clone, PartialEq)]
3065pub(crate) enum DraggingState {
3066 Selection,
3067 #[cfg(wayland_platform)]
3068 PrepareDnd(Point),
3069 #[cfg(wayland_platform)]
3070 Dnd(DndAction, String),
3071}
3072
3073#[cfg(wayland_platform)]
3074#[derive(Debug, Default, Clone)]
3075pub(crate) enum DndOfferState {
3076 #[default]
3077 None,
3078 HandlingOffer(Vec<String>, DndAction),
3079 Dropped,
3080}
3081#[derive(Debug, Default, Clone)]
3082#[cfg(not(wayland_platform))]
3083pub(crate) struct DndOfferState;
3084
3085#[derive(Default, Clone)]
3087#[must_use]
3088pub struct State {
3089 pub tracked_value: Value,
3090 pub value: crate::Plain,
3091 pub placeholder: crate::Plain,
3092 pub label: crate::Plain,
3093 pub helper_text: crate::Plain,
3094 pub dirty: bool,
3095 pub is_secure: bool,
3096 pub is_read_only: bool,
3097 pub emit_unfocus: bool,
3098 select_on_focus: bool,
3099 double_click_select_delimiter: Option<char>,
3100 is_focused: Option<Focus>,
3101 dragging_state: Option<DraggingState>,
3102 dnd_offer: DndOfferState,
3103 is_pasting: Option<Value>,
3104 last_click: Option<mouse::Click>,
3105 cursor: Cursor,
3106 preedit: Option<Preedit>,
3107 keyboard_modifiers: keyboard::Modifiers,
3108 scroll_offset: f32,
3109 context_menu_position: Option<iced_core::Point>,
3110 clipboard_has_text: bool,
3111 pub(crate) menu_bar_state: crate::widget::menu::MenuBarState,
3112 pub(crate) pending_action: crate::widget::text_context_menu::PendingAction,
3113}
3114
3115impl std::fmt::Debug for State {
3116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3117 f.debug_struct("State")
3118 .field("is_secure", &self.is_secure)
3119 .field("is_read_only", &self.is_read_only)
3120 .field("dirty", &self.dirty)
3121 .finish_non_exhaustive()
3122 }
3123}
3124
3125#[derive(Debug, Clone, Copy)]
3126struct Focus {
3127 updated_at: Instant,
3128 now: Instant,
3129 focused: bool,
3130 needs_update: bool,
3131}
3132
3133impl State {
3134 pub fn new(
3136 is_secure: bool,
3137 is_read_only: bool,
3138 always_active: bool,
3139 select_on_focus: bool,
3140 ) -> Self {
3141 Self {
3142 is_secure,
3143 is_read_only,
3144 is_focused: always_active.then(|| {
3145 let now = Instant::now();
3146 Focus {
3147 updated_at: now,
3148 now,
3149 focused: true,
3150 needs_update: false,
3151 }
3152 }),
3153 select_on_focus,
3154 ..Self::default()
3155 }
3156 }
3157
3158 #[must_use]
3160 pub fn selected_text(&self, text: &str) -> Option<String> {
3161 let value = Value::new(text);
3162 match self.cursor.state(&value) {
3163 cursor::State::Index(_) => None,
3164 cursor::State::Selection { start, end } => {
3165 let left = start.min(end);
3166 let right = end.max(start);
3167 Some(value.select(left, right).to_string())
3168 }
3169 }
3170 }
3171
3172 #[cfg(wayland_platform)]
3173 #[must_use]
3175 pub fn dragged_text(&self) -> Option<String> {
3176 match self.dragging_state.as_ref() {
3177 Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
3178 _ => None,
3179 }
3180 }
3181
3182 pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
3184 Self {
3185 tracked_value: Value::default(),
3186 is_secure,
3187 value: crate::Plain::default(),
3188 placeholder: crate::Plain::default(),
3189 label: crate::Plain::default(),
3190 helper_text: crate::Plain::default(),
3191 is_read_only,
3192 emit_unfocus: false,
3193 is_focused: None,
3194 select_on_focus: false,
3195 double_click_select_delimiter: None,
3196 dragging_state: None,
3197 dnd_offer: DndOfferState::default(),
3198 is_pasting: None,
3199 last_click: None,
3200 cursor: Cursor::default(),
3201 preedit: None,
3202 keyboard_modifiers: keyboard::Modifiers::default(),
3203 scroll_offset: 0.0,
3204 dirty: false,
3205 context_menu_position: None,
3206 clipboard_has_text: false,
3207 menu_bar_state: crate::widget::menu::MenuBarState::default(),
3208 pending_action: crate::widget::text_context_menu::pending_action(),
3209 }
3210 }
3211
3212 #[inline]
3214 #[must_use]
3215 pub fn is_focused(&self) -> bool {
3216 self.is_focused.is_some_and(|f| f.focused)
3217 }
3218
3219 #[inline]
3221 #[must_use]
3222 pub fn cursor(&self) -> Cursor {
3223 self.cursor
3224 }
3225
3226 #[cold]
3228 pub fn focus(&mut self) {
3229 let now = Instant::now();
3230 LAST_FOCUS_UPDATE.with(|x| x.set(now));
3231 let was_focused = self.is_focused.is_some_and(|f| f.focused);
3232 self.is_read_only = false;
3233 self.is_focused = Some(Focus {
3234 updated_at: now,
3235 now,
3236 focused: true,
3237 needs_update: false,
3238 });
3239
3240 if was_focused {
3241 return;
3242 }
3243 if self.select_on_focus {
3244 self.select_all()
3245 } else {
3246 self.move_cursor_to_end();
3247 }
3248 }
3249
3250 #[cold]
3252 pub(super) fn unfocus(&mut self) {
3253 self.cursor.clear_selection();
3254 self.last_click = None;
3255 self.is_focused = self.is_focused.map(|mut f| {
3256 f.focused = false;
3257 f.needs_update = false;
3258 f
3259 });
3260 self.dragging_state = None;
3261 self.is_pasting = None;
3262 self.keyboard_modifiers = keyboard::Modifiers::default();
3263 }
3264
3265 #[inline]
3267 pub fn move_cursor_to_front(&mut self) {
3268 self.cursor.move_to(0);
3269 }
3270
3271 #[inline]
3273 pub fn move_cursor_to_end(&mut self) {
3274 self.cursor.move_to(usize::MAX);
3275 }
3276
3277 #[inline]
3279 pub fn move_cursor_to(&mut self, position: usize) {
3280 self.cursor.move_to(position);
3281 }
3282
3283 #[inline]
3285 pub fn select_all(&mut self) {
3286 self.cursor.select_range(0, usize::MAX);
3287 }
3288
3289 #[inline]
3291 pub fn select_range(&mut self, start: usize, end: usize) {
3292 self.cursor.select_range(start, end);
3293 }
3294
3295 pub fn context_menu_position(&self) -> Option<iced_core::Point> {
3297 self.context_menu_position
3298 }
3299
3300 pub fn set_context_menu_position(&mut self, pos: Option<iced_core::Point>) {
3302 self.context_menu_position = pos;
3303 }
3304
3305 pub fn delete_selection(&mut self) -> String {
3307 let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
3308 editor.delete();
3309 editor.contents()
3310 }
3311
3312 pub fn paste_text(&mut self, text: &str) -> String {
3314 let paste_value = super::value::Value::new(text);
3315 let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
3316 editor.paste(paste_value);
3317 let contents = editor.contents();
3318 self.tracked_value = super::value::Value::new(&contents);
3319 contents
3320 }
3321
3322 pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
3323 let (position, affinity) = find_cursor_position(bounds, value, self, target)
3324 .unwrap_or((0, text::Affinity::Before));
3325
3326 self.cursor.set_affinity(affinity);
3327 self.cursor.move_to(position);
3328 self.dragging_state = Some(DraggingState::Selection);
3329 }
3330}
3331
3332impl operation::Focusable for State {
3333 #[inline]
3334 fn is_focused(&self) -> bool {
3335 Self::is_focused(self)
3336 }
3337
3338 #[inline]
3339 fn focus(&mut self) {
3340 Self::focus(self);
3341 if let Some(focus) = self.is_focused.as_mut() {
3342 focus.needs_update = true;
3343 }
3344 }
3345
3346 #[inline]
3347 fn unfocus(&mut self) {
3348 Self::unfocus(self);
3349 if let Some(focus) = self.is_focused.as_mut() {
3350 focus.needs_update = true;
3351 }
3352 }
3353}
3354
3355impl operation::TextInput for State {
3356 #[inline]
3357 fn move_cursor_to_front(&mut self) {
3358 Self::move_cursor_to_front(self);
3359 }
3360
3361 #[inline]
3362 fn move_cursor_to_end(&mut self) {
3363 Self::move_cursor_to_end(self);
3364 }
3365
3366 #[inline]
3367 fn move_cursor_to(&mut self, position: usize) {
3368 Self::move_cursor_to(self, position);
3369 }
3370
3371 #[inline]
3372 fn select_all(&mut self) {
3373 Self::select_all(self);
3374 }
3375
3376 fn text(&self) -> &str {
3377 todo!()
3378 }
3379
3380 #[inline]
3381 fn select_range(&mut self, start: usize, end: usize) {
3382 Self::select_range(self, start, end);
3383 }
3384}
3385
3386#[inline(never)]
3387fn measure_cursor_and_scroll_offset(
3388 paragraph: &impl text::Paragraph,
3389 text_bounds: Rectangle,
3390 cursor_index: usize,
3391 value: &Value,
3392 affinity: text::Affinity,
3393 current_offset: f32,
3394) -> (f32, f32) {
3395 let byte_index = value.byte_index_at_grapheme(cursor_index);
3396 let position = paragraph
3397 .cursor_position(0, byte_index, affinity)
3398 .unwrap_or(Point::ORIGIN);
3399
3400 let offset = if position.x > current_offset + text_bounds.width - 5.0 {
3404 (position.x + 5.0) - text_bounds.width
3406 } else if position.x < current_offset + 5.0 {
3407 position.x - 5.0
3409 } else {
3410 current_offset
3412 };
3413
3414 let max_offset = (paragraph.min_width() - text_bounds.width).max(0.0);
3415 let offset = offset.clamp(0.0, max_offset);
3416
3417 (position.x, offset)
3418}
3419
3420#[inline(never)]
3423fn find_cursor_position(
3424 text_bounds: Rectangle,
3425 value: &Value,
3426 state: &State,
3427 x: f32,
3428) -> Option<(usize, text::Affinity)> {
3429 let value_str = value.to_string();
3430
3431 let hit = state.value.raw().hit_test(Point::new(
3432 x + state.scroll_offset,
3433 text_bounds.height / 2.0,
3434 ))?;
3435 let char_offset = hit.cursor();
3436 let affinity = hit.affinity();
3437
3438 let grapheme_count = unicode_segmentation::UnicodeSegmentation::graphemes(
3439 &value_str[..char_offset.min(value_str.len())],
3440 true,
3441 )
3442 .count();
3443
3444 Some((grapheme_count, affinity))
3445}
3446
3447#[inline(never)]
3448fn replace_paragraph(
3449 state: &mut State,
3450 layout: Layout<'_>,
3451 value: &Value,
3452 font: <crate::Renderer as iced_core::text::Renderer>::Font,
3453 text_size: Pixels,
3454 line_height: text::LineHeight,
3455 limits: &layout::Limits,
3456) {
3457 let mut children_layout = layout.children();
3458 let text_bounds = children_layout.next().unwrap();
3459 let bounds = limits.resolve(
3460 Length::Shrink,
3461 Length::Fill,
3462 Size::new(0., text_bounds.bounds().height),
3463 );
3464
3465 state.value = crate::Plain::new(Text {
3466 font,
3467 line_height,
3468 content: value.to_string(),
3469 bounds,
3470 size: text_size,
3471 align_x: text::Alignment::Default,
3472 align_y: alignment::Vertical::Top,
3473 shaping: text::Shaping::Advanced,
3474 wrapping: text::Wrapping::None,
3475 ellipsize: text::Ellipsize::None,
3476 });
3477}
3478
3479const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
3480
3481mod platform {
3482 use iced_core::keyboard;
3483
3484 #[inline]
3485 pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
3486 if cfg!(target_os = "macos") {
3487 modifiers.alt()
3488 } else {
3489 modifiers.control()
3490 }
3491 }
3492}
3493
3494#[inline(never)]
3495fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
3496 if state.is_focused() {
3497 let cursor = state.cursor();
3498
3499 let focus_position = match cursor.state(value) {
3500 cursor::State::Index(i) => i,
3501 cursor::State::Selection { end, .. } => end,
3502 };
3503
3504 let (_, offset) = measure_cursor_and_scroll_offset(
3505 state.value.raw(),
3506 text_bounds,
3507 focus_position,
3508 value,
3509 state.cursor().affinity(),
3510 state.scroll_offset,
3511 );
3512
3513 offset
3514 } else {
3515 match effective_alignment(state.value.raw()) {
3516 alignment::Horizontal::Right => {
3517 (state.value.raw().min_width() - text_bounds.width).max(0.0)
3518 }
3519 _ => 0.0,
3520 }
3521 }
3522}
3523
3524#[inline(never)]
3525fn alignment_offset(
3526 text_bounds_width: f32,
3527 text_min_width: f32,
3528 alignment: alignment::Horizontal,
3529) -> f32 {
3530 if text_min_width > text_bounds_width {
3531 0.0
3532 } else {
3533 match alignment {
3534 alignment::Horizontal::Left => 0.0,
3535 alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0,
3536 alignment::Horizontal::Right => text_bounds_width - text_min_width,
3537 }
3538 }
3539}
3540
3541#[inline(never)]
3542fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizontal {
3543 if paragraph.is_rtl(0).unwrap_or(false) {
3544 alignment::Horizontal::Right
3545 } else {
3546 alignment::Horizontal::Left
3547 }
3548}
3549
3550use iced_core::widget::tree::Tree as WidgetTree;
3551
3552impl<Message: Clone + 'static> iced_core::widget::text::HasSelectableText
3553 for TextInput<'_, Message>
3554{
3555 fn selected_text(&self, tree: &WidgetTree) -> Option<String> {
3556 let state = tree.state.downcast_ref::<State>();
3557 let (start, end) = state.cursor().selection(&state.tracked_value)?;
3558 Some(state.tracked_value.select(start, end).to_string())
3559 }
3560
3561 fn select_all(&self, tree: &mut WidgetTree) {
3562 let state = tree.state.downcast_mut::<State>();
3563 state.select_all();
3564 }
3565
3566 fn is_editable(&self) -> bool {
3567 true
3568 }
3569
3570 fn has_text(&self, tree: &WidgetTree) -> bool {
3571 !tree.state.downcast_ref::<State>().tracked_value.is_empty()
3572 }
3573
3574 fn clipboard_has_text(&self, tree: &WidgetTree) -> bool {
3575 tree.state.downcast_ref::<State>().clipboard_has_text
3576 }
3577
3578 fn is_focused(&self, tree: &WidgetTree) -> bool {
3579 tree.state.downcast_ref::<State>().is_focused()
3580 }
3581
3582 fn context_menu_position(&self, tree: &WidgetTree) -> Option<iced_core::Point> {
3583 tree.state.downcast_ref::<State>().context_menu_position
3584 }
3585
3586 fn set_context_menu_position(&self, tree: &mut WidgetTree, pos: Option<iced_core::Point>) {
3587 tree.state.downcast_mut::<State>().context_menu_position = pos;
3588 }
3589
3590 fn delete_selection(&self, tree: &mut WidgetTree) -> Option<String> {
3591 let state = tree.state.downcast_mut::<State>();
3592 Some(state.delete_selection())
3593 }
3594
3595 fn paste_text(&self, tree: &mut WidgetTree, text: &str) -> Option<String> {
3596 let filtered: String = text.chars().filter(|c| !c.is_control()).collect();
3597 let state = tree.state.downcast_mut::<State>();
3598 Some(state.paste_text(&filtered))
3599 }
3600}