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
2244 keyboard::Key::Named(keyboard::key::Named::Tab) => {
2245 if let Some(on_tab) = on_tab {
2246 shell.publish(on_tab.clone());
2250 } else {
2251 state.is_read_only = true;
2252
2253 if let Some(on_unfocus) = on_unfocus {
2254 shell.publish(on_unfocus.clone());
2255 }
2256
2257 return;
2258 };
2259 }
2260
2261 keyboard::Key::Named(
2262 keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
2263 ) => {
2264 return;
2265 }
2266 _ => {}
2267 }
2268
2269 shell.request_redraw();
2270 shell.capture_event();
2271 return;
2272 }
2273 }
2274 Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {
2275 if state.is_focused() {
2276 match key {
2277 keyboard::Key::Character(c) if "v" == c => {
2278 state.is_pasting = None;
2279 }
2280 keyboard::Key::Named(keyboard::key::Named::Insert) => {
2281 state.is_pasting = None;
2282 }
2283 keyboard::Key::Named(keyboard::key::Named::Tab)
2284 | keyboard::Key::Named(keyboard::key::Named::ArrowUp)
2285 | keyboard::Key::Named(keyboard::key::Named::ArrowDown) => {
2286 return;
2287 }
2288 _ => {}
2289 }
2290
2291 shell.capture_event();
2292 return;
2293 }
2294 }
2295 Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
2296 state.keyboard_modifiers = *modifiers;
2297 }
2298 Event::InputMethod(event) => match event {
2299 input_method::Event::Opened | input_method::Event::Closed => {
2300 state.preedit =
2301 matches!(event, input_method::Event::Opened).then(input_method::Preedit::new);
2302 shell.capture_event();
2303 return;
2304 }
2305 input_method::Event::Preedit(content, selection) => {
2306 if state.is_focused() {
2307 state.preedit = Some(input_method::Preedit {
2308 content: content.to_owned(),
2309 selection: selection.clone(),
2310 text_size: Some(size.into()),
2311 });
2312 shell.capture_event();
2313 return;
2314 }
2315 }
2316 input_method::Event::Commit(text) => {
2317 let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) else {
2318 return;
2319 };
2320 let Some(on_input) = on_input else {
2321 return;
2322 };
2323 if state.is_read_only {
2324 return;
2325 }
2326
2327 focus.updated_at = Instant::now();
2328 LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
2329
2330 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2331 editor.paste(Value::new(&text));
2332
2333 let contents = editor.contents();
2334 let unsecured_value = Value::new(&contents);
2335 let message = if let Some(paste) = &on_paste {
2336 (paste)(contents)
2337 } else {
2338 (on_input)(contents)
2339 };
2340 shell.publish(message);
2341
2342 state.is_pasting = None;
2343 let value = if is_secure {
2344 unsecured_value.secure()
2345 } else {
2346 unsecured_value
2347 };
2348
2349 update_cache(state, &value);
2350 shell.capture_event();
2351 return;
2352 }
2353 },
2354 Event::Window(window::Event::RedrawRequested(now)) => {
2355 if let Some(focus) = state.is_focused.as_mut().filter(|f| f.focused) {
2356 focus.now = *now;
2357
2358 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
2359 - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS;
2360 shell.request_redraw_at(window::RedrawRequest::At(
2361 now.checked_add(Duration::from_millis(millis_until_redraw as u64))
2362 .unwrap_or(*now),
2363 ));
2364
2365 shell.request_input_method(&input_method(state, text_layout, unsecured_value));
2366 } else if always_active {
2367 shell.request_redraw();
2368 }
2369 }
2370 #[cfg(wayland_platform)]
2371 Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
2372 cold();
2373 if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
2374 state.dragging_state = None;
2376 shell.capture_event();
2377 return;
2378 }
2379 }
2380 #[cfg(wayland_platform)]
2381 Event::Dnd(DndEvent::Offer(
2382 rectangle,
2383 OfferEvent::Enter {
2384 x,
2385 y,
2386 mime_types,
2387 surface,
2388 },
2389 )) if *rectangle == Some(dnd_id) => {
2390 cold();
2391 let is_clicked = text_layout.bounds().contains(Point {
2392 x: *x as f32,
2393 y: *y as f32,
2394 });
2395
2396 let mut accepted = false;
2397 for m in mime_types {
2398 if SUPPORTED_TEXT_MIME_TYPES.contains(&m.as_str()) {
2399 let clone = m.clone();
2400 accepted = true;
2401 }
2402 }
2403 if accepted {
2404 let target = {
2405 let text_bounds = text_layout.bounds();
2406
2407 let alignment_offset = alignment_offset(
2408 text_bounds.width,
2409 state.value.raw().min_width(),
2410 effective_alignment(state.value.raw()),
2411 );
2412
2413 *x as f32 - text_bounds.x - alignment_offset
2414 };
2415 state.dnd_offer =
2416 DndOfferState::HandlingOffer(mime_types.clone(), DndAction::empty());
2417 update_cache(state, value);
2419 let (position, affinity) =
2420 find_cursor_position(text_layout.bounds(), value, state, target)
2421 .unwrap_or((0, text::Affinity::Before));
2422
2423 state.cursor.set_affinity(affinity);
2424 state.cursor.move_to(position);
2425 shell.capture_event();
2426 return;
2427 }
2428 }
2429 #[cfg(wayland_platform)]
2430 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }))
2431 if *rectangle == Some(dnd_id) =>
2432 {
2433 let target = {
2434 let text_bounds = text_layout.bounds();
2435
2436 let alignment_offset = alignment_offset(
2437 text_bounds.width,
2438 state.value.raw().min_width(),
2439 effective_alignment(state.value.raw()),
2440 );
2441
2442 *x as f32 - text_bounds.x - alignment_offset
2443 };
2444 update_cache(state, value);
2446 let (position, affinity) =
2447 find_cursor_position(text_layout.bounds(), value, state, target)
2448 .unwrap_or((0, text::Affinity::Before));
2449
2450 state.cursor.set_affinity(affinity);
2451 state.cursor.move_to(position);
2452 shell.capture_event();
2453 return;
2454 }
2455 #[cfg(wayland_platform)]
2456 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if *rectangle == Some(dnd_id) => {
2457 cold();
2458 if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
2459 let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
2460 .iter()
2461 .find(|&&m| mime_types.iter().any(|t| t == m))
2462 else {
2463 state.dnd_offer = DndOfferState::None;
2464 shell.capture_event();
2465 return;
2466 };
2467 state.dnd_offer = DndOfferState::Dropped;
2468 }
2469
2470 return;
2471 }
2472 #[cfg(wayland_platform)]
2473 Event::Dnd(DndEvent::Offer(id, OfferEvent::LeaveDestination)) if Some(dnd_id) != *id => {}
2474 #[cfg(wayland_platform)]
2475 Event::Dnd(DndEvent::Offer(
2476 rectangle,
2477 OfferEvent::Leave | OfferEvent::LeaveDestination,
2478 )) => {
2479 cold();
2480 match state.dnd_offer {
2483 DndOfferState::Dropped => {}
2484 _ => {
2485 state.dnd_offer = DndOfferState::None;
2486 }
2487 };
2488 shell.capture_event();
2489 return;
2490 }
2491 #[cfg(wayland_platform)]
2492 Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
2493 if *rectangle == Some(dnd_id) =>
2494 {
2495 cold();
2496 if matches!(&state.dnd_offer, DndOfferState::Dropped) {
2497 state.dnd_offer = DndOfferState::None;
2498 if !SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type.as_str()) || data.is_empty() {
2499 shell.capture_event();
2500 return;
2501 }
2502 let Ok(content) = String::from_utf8(data.clone()) else {
2503 shell.capture_event();
2504 return;
2505 };
2506
2507 let mut editor = Editor::new(unsecured_value, &mut state.cursor);
2508
2509 editor.paste(Value::new(content.as_str()));
2510 let contents = editor.contents();
2511 let unsecured_value = Value::new(&contents);
2512 state.tracked_value = unsecured_value.clone();
2513 if let Some(on_paste) = on_paste.as_ref() {
2514 let message = (on_paste)(contents);
2515 shell.publish(message);
2516 }
2517
2518 let value = if is_secure {
2519 unsecured_value.secure()
2520 } else {
2521 unsecured_value
2522 };
2523 update_cache(state, &value);
2524 shell.capture_event();
2525 return;
2526 }
2527 return;
2528 }
2529 _ => {}
2530 }
2531}
2532
2533fn input_method<'b>(
2534 state: &'b State,
2535 text_layout: Layout<'_>,
2536 value: &Value,
2537) -> InputMethod<&'b str> {
2538 if !state.is_focused() {
2539 return InputMethod::Disabled;
2540 };
2541
2542 let text_bounds = text_layout.bounds();
2543 let cursor_index = match state.cursor.state(value) {
2544 cursor::State::Index(position) => position,
2545 cursor::State::Selection { start, end } => start.min(end),
2546 };
2547 let (cursor, offset) = measure_cursor_and_scroll_offset(
2548 state.value.raw(),
2549 text_bounds,
2550 cursor_index,
2551 value,
2552 state.cursor.affinity(),
2553 state.scroll_offset,
2554 );
2555 InputMethod::Enabled {
2556 cursor: Rectangle::new(
2557 Point::new(text_bounds.x + cursor - offset, text_bounds.y),
2558 Size::new(1.0, text_bounds.height),
2559 ),
2560 purpose: if state.is_secure {
2561 input_method::Purpose::Secure
2562 } else {
2563 input_method::Purpose::Normal
2564 },
2565 preedit: state.preedit.as_ref().map(input_method::Preedit::as_ref),
2566 }
2567}
2568
2569#[allow(clippy::too_many_arguments)]
2574#[allow(clippy::too_many_lines)]
2575#[allow(clippy::missing_panics_doc)]
2576pub fn draw<'a, Message>(
2577 renderer: &mut crate::Renderer,
2578 theme: &crate::Theme,
2579 layout: Layout<'_>,
2580 text_layout: Layout<'_>,
2581 cursor_position: mouse::Cursor,
2582 tree: &Tree,
2583 value: &Value,
2584 placeholder: &str,
2585 size: Option<f32>,
2586 font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
2587 is_disabled: bool,
2588 is_secure: bool,
2589 icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2590 trailing_icon: Option<&Element<'a, Message, crate::Theme, crate::Renderer>>,
2591 style: &<crate::Theme as StyleSheet>::Style,
2592 dnd_icon: bool,
2593 line_height: text::LineHeight,
2594 error: Option<&str>,
2595 label: Option<&str>,
2596 helper_text: Option<&str>,
2597 helper_text_size: f32,
2598 helper_line_height: text::LineHeight,
2599 viewport: &Rectangle,
2600 renderer_style: &renderer::Style,
2601) {
2602 let children = &tree.children;
2604
2605 let state = tree.state.downcast_ref::<State>();
2606 let secure_value = is_secure.then(|| value.secure());
2607 let value = secure_value.as_ref().unwrap_or(value);
2608
2609 let mut children_layout = layout.children();
2610
2611 let (label_layout, layout, helper_text_layout) = if label.is_some() && helper_text.is_some() {
2612 let label_layout = children_layout.next();
2613 let layout = children_layout.next().unwrap();
2614 let helper_text_layout = children_layout.next();
2615 (label_layout, layout, helper_text_layout)
2616 } else if label.is_some() {
2617 let label_layout = children_layout.next();
2618 let layout = children_layout.next().unwrap();
2619 (label_layout, layout, None)
2620 } else if helper_text.is_some() {
2621 let layout = children_layout.next().unwrap();
2622 let helper_text_layout = children_layout.next();
2623 (None, layout, helper_text_layout)
2624 } else {
2625 let layout = children_layout.next().unwrap();
2626
2627 (None, layout, None)
2628 };
2629
2630 let mut children_layout = layout.children();
2631 let bounds = layout.bounds();
2632 let text_bounds = children_layout.next().unwrap_or(text_layout).bounds();
2634
2635 let is_mouse_over = cursor_position.is_over(bounds);
2636
2637 let appearance = if is_disabled {
2638 theme.disabled(style)
2639 } else if error.is_some() {
2640 theme.error(style)
2641 } else if state.is_focused() {
2642 theme.focused(style)
2643 } else if is_mouse_over {
2644 theme.hovered(style)
2645 } else {
2646 theme.active(style)
2647 };
2648
2649 let mut icon_color = appearance.icon_color.unwrap_or(renderer_style.icon_color);
2650 let mut text_color = appearance.text_color.unwrap_or(renderer_style.text_color);
2651
2652 if is_disabled {
2654 let background = theme.current_container().component.base.into();
2655 icon_color = icon_color.blend_alpha(background, 0.5);
2656 text_color = text_color.blend_alpha(background, 0.5);
2657 }
2658
2659 if let Some(border_offset) = appearance.border_offset {
2661 let offset_bounds = Rectangle {
2662 x: bounds.x - border_offset,
2663 y: bounds.y - border_offset,
2664 width: border_offset.mul_add(2.0, bounds.width),
2665 height: border_offset.mul_add(2.0, bounds.height),
2666 };
2667 renderer.fill_quad(
2668 renderer::Quad {
2669 bounds,
2670 border: Border {
2671 radius: appearance.border_radius,
2672 width: appearance.border_width,
2673 ..Default::default()
2674 },
2675 shadow: Shadow {
2676 offset: Vector::new(0.0, 1.0),
2677 color: Color::TRANSPARENT,
2678 blur_radius: 0.0,
2679 },
2680 snap: true,
2681 },
2682 appearance.background,
2683 );
2684 renderer.fill_quad(
2685 renderer::Quad {
2686 bounds: offset_bounds,
2687 border: Border {
2688 width: appearance.border_width,
2689 color: appearance.border_color,
2690 radius: appearance.border_radius,
2691 },
2692 shadow: Shadow {
2693 offset: Vector::new(0.0, 1.0),
2694 color: Color::TRANSPARENT,
2695 blur_radius: 0.0,
2696 },
2697 snap: true,
2698 },
2699 Background::Color(Color::TRANSPARENT),
2700 );
2701 } else {
2702 renderer.fill_quad(
2703 renderer::Quad {
2704 bounds,
2705 border: Border {
2706 width: appearance.border_width,
2707 color: appearance.border_color,
2708 radius: appearance.border_radius,
2709 },
2710 shadow: Shadow {
2711 offset: Vector::new(0.0, 1.0),
2712 color: Color::TRANSPARENT,
2713 blur_radius: 0.0,
2714 },
2715 snap: true,
2716 },
2717 appearance.background,
2718 );
2719 }
2720
2721 if let (Some(label_layout), Some(label)) = (label_layout, label) {
2723 renderer.fill_text(
2724 Text {
2725 content: label.to_string(),
2726 size: iced::Pixels(size.unwrap_or_else(|| renderer.default_size().0)),
2727 font: font.unwrap_or_else(|| renderer.default_font()),
2728 bounds: label_layout.bounds().size(),
2729 align_x: text::Alignment::Left,
2730 align_y: alignment::Vertical::Top,
2731 line_height,
2732 shaping: text::Shaping::Advanced,
2733 wrapping: text::Wrapping::None,
2734 ellipsize: text::Ellipsize::None,
2735 },
2736 label_layout.bounds().position(),
2737 appearance.label_color,
2738 *viewport,
2739 );
2740 }
2741 let mut child_index = 0;
2742 let leading_icon_tree = children.get(child_index);
2743 let has_start_icon = icon.is_some();
2745 if let (Some(icon), Some(tree)) = (icon, leading_icon_tree) {
2746 let mut children = text_layout.children();
2747 let _ = children.next().unwrap();
2748 let icon_layout = children.next().unwrap();
2749
2750 icon.as_widget().draw(
2751 tree,
2752 renderer,
2753 theme,
2754 &renderer::Style {
2755 icon_color,
2756 text_color,
2757 scale_factor: renderer_style.scale_factor,
2758 },
2759 icon_layout,
2760 cursor_position,
2761 viewport,
2762 );
2763 child_index += 1;
2764 }
2765
2766 let text = value.to_string();
2767 let font = font.unwrap_or_else(|| renderer.default_font());
2768 let size = size.unwrap_or_else(|| renderer.default_size().0);
2769 let text_width = state.value.min_width();
2770 let actual_width = text_width.max(text_bounds.width);
2771
2772 let radius_0 = THEME.lock().unwrap().cosmic().corner_radii.radius_0.into();
2773 #[cfg(wayland_platform)]
2774 let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
2775 #[cfg(not(wayland_platform))]
2776 let handling_dnd_offer = false;
2777 let (cursors, offset, is_selecting) = if let Some(focus) =
2778 state.is_focused.filter(|f| f.focused).or_else(|| {
2779 let now = Instant::now();
2780 handling_dnd_offer.then_some(Focus {
2781 needs_update: false,
2782 updated_at: now,
2783 now,
2784 focused: true,
2785 })
2786 }) {
2787 match state.cursor.state(value) {
2788 cursor::State::Index(position) => {
2789 let (text_value_width, _) = measure_cursor_and_scroll_offset(
2790 state.value.raw(),
2791 text_bounds,
2792 position,
2793 value,
2794 state.cursor.affinity(),
2795 state.scroll_offset,
2796 );
2797 let is_cursor_visible = handling_dnd_offer
2798 || ((focus.now - focus.updated_at).as_millis() / CURSOR_BLINK_INTERVAL_MILLIS)
2799 .is_multiple_of(2);
2800
2801 if is_cursor_visible && !dnd_icon {
2802 (
2803 vec![(
2804 renderer::Quad {
2805 bounds: Rectangle {
2806 x: (text_bounds.x + text_value_width).floor(),
2807 y: text_bounds.y,
2808 width: 1.0,
2809 height: text_bounds.height,
2810 },
2811 border: Border {
2812 width: 0.0,
2813 color: Color::TRANSPARENT,
2814 radius: radius_0,
2815 },
2816 shadow: Shadow {
2817 offset: Vector::ZERO,
2818 color: Color::TRANSPARENT,
2819 blur_radius: 0.0,
2820 },
2821 snap: true,
2822 },
2823 text_color,
2824 )],
2825 state.scroll_offset,
2826 false,
2827 )
2828 } else {
2829 (
2830 Vec::<(renderer::Quad, Color)>::new(),
2831 if dnd_icon { 0.0 } else { state.scroll_offset },
2832 false,
2833 )
2834 }
2835 }
2836 cursor::State::Selection { start, end } => {
2837 let left = start.min(end);
2838 let right = end.max(start);
2839
2840 if dnd_icon {
2841 (Vec::<(renderer::Quad, Color)>::new(), 0.0, true)
2842 } else {
2843 let lo_byte = value.byte_index_at_grapheme(left);
2844 let hi_byte = value.byte_index_at_grapheme(right);
2845
2846 let rects = state.value.raw().highlight(
2847 0,
2848 (lo_byte, text::Affinity::After),
2849 (hi_byte, text::Affinity::Before),
2850 );
2851
2852 let cursors: Vec<(renderer::Quad, Color)> = rects
2853 .into_iter()
2854 .map(|r| {
2855 (
2856 renderer::Quad {
2857 bounds: Rectangle {
2858 x: text_bounds.x + r.x,
2859 y: text_bounds.y,
2860 width: r.width,
2861 height: text_bounds.height,
2862 },
2863 border: Border {
2864 width: 0.0,
2865 color: Color::TRANSPARENT,
2866 radius: radius_0,
2867 },
2868 shadow: Shadow {
2869 offset: Vector::ZERO,
2870 color: Color::TRANSPARENT,
2871 blur_radius: 0.0,
2872 },
2873 snap: true,
2874 },
2875 appearance.selected_fill,
2876 )
2877 })
2878 .collect();
2879
2880 (cursors, state.scroll_offset, true)
2881 }
2882 }
2883 }
2884 } else {
2885 let unfocused_offset = match effective_alignment(state.value.raw()) {
2886 alignment::Horizontal::Right => {
2887 (state.value.raw().min_width() - text_bounds.width).max(0.0)
2888 }
2889 _ => 0.0,
2890 };
2891
2892 (
2893 Vec::<(renderer::Quad, Color)>::new(),
2894 unfocused_offset,
2895 false,
2896 )
2897 };
2898
2899 let render = |renderer: &mut crate::Renderer| {
2900 let alignment_offset = alignment_offset(
2901 text_bounds.width,
2902 state.value.raw().min_width(),
2903 effective_alignment(state.value.raw()),
2904 );
2905
2906 let shift = Vector::new(alignment_offset - offset, 0.0);
2907 let fill_cursors = |renderer: &mut crate::Renderer| {
2908 renderer.with_translation(shift, |renderer| {
2909 for (quad, color) in &cursors {
2910 renderer.fill_quad(*quad, *color);
2911 }
2912 });
2913 };
2914
2915 if !is_selecting {
2916 fill_cursors(renderer);
2917 }
2918
2919 let bounds = Rectangle {
2920 x: text_bounds.x + alignment_offset - offset,
2921 y: text_bounds.center_y(),
2922 width: actual_width,
2923 ..text_bounds
2924 };
2925 let color = if text.is_empty() {
2926 appearance.placeholder_color
2927 } else {
2928 text_color
2929 };
2930
2931 let text = Text {
2932 content: if text.is_empty() {
2933 placeholder.to_string()
2934 } else {
2935 text.clone()
2936 },
2937 font,
2938 bounds: bounds.size(),
2939 size: iced::Pixels(size),
2940 align_x: text::Alignment::Default,
2941 align_y: alignment::Vertical::Center,
2942 line_height: text::LineHeight::default(),
2943 shaping: text::Shaping::Advanced,
2944 wrapping: text::Wrapping::None,
2945 ellipsize: text::Ellipsize::None,
2946 };
2947 renderer.fill_text(text.clone(), bounds.position(), color, text_bounds);
2948
2949 if is_selecting {
2952 fill_cursors(renderer);
2953 for (quad, _) in &cursors {
2954 renderer.with_layer(quad.bounds + shift, |renderer| {
2955 renderer.fill_text(
2956 text.clone(),
2957 bounds.position(),
2958 appearance.selected_text_color,
2959 text_bounds,
2960 );
2961 });
2962 }
2963 }
2964 };
2965
2966 renderer.with_layer(text_bounds, render);
2969
2970 let trailing_icon_tree = children.get(child_index);
2971
2972 if let (Some(icon), Some(tree)) = (trailing_icon, trailing_icon_tree) {
2974 let mut children = text_layout.children();
2975 let mut icon_layout = children.next().unwrap();
2976 if has_start_icon {
2977 icon_layout = children.next().unwrap();
2978 }
2979 icon_layout = children.next().unwrap();
2980
2981 icon.as_widget().draw(
2982 tree,
2983 renderer,
2984 theme,
2985 &renderer::Style {
2986 icon_color,
2987 text_color,
2988 scale_factor: renderer_style.scale_factor,
2989 },
2990 icon_layout,
2991 cursor_position,
2992 viewport,
2993 );
2994 }
2995
2996 if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
2998 renderer.fill_text(
2999 Text {
3000 content: helper_text.to_string(), size: iced::Pixels(helper_text_size),
3002 font,
3003 bounds: helper_text_layout.bounds().size(),
3004 align_x: text::Alignment::Left,
3005 align_y: alignment::Vertical::Top,
3006 line_height: helper_line_height,
3007 shaping: text::Shaping::Advanced,
3008 wrapping: text::Wrapping::None,
3009 ellipsize: text::Ellipsize::None,
3010 },
3011 helper_text_layout.bounds().position(),
3012 text_color,
3013 *viewport,
3014 );
3015 }
3016}
3017
3018#[must_use]
3020pub fn mouse_interaction(
3021 layout: Layout<'_>,
3022 cursor_position: mouse::Cursor,
3023 is_disabled: bool,
3024) -> mouse::Interaction {
3025 if cursor_position.is_over(layout.bounds()) {
3026 if is_disabled {
3027 mouse::Interaction::NotAllowed
3028 } else {
3029 mouse::Interaction::Text
3030 }
3031 } else {
3032 mouse::Interaction::default()
3033 }
3034}
3035
3036#[derive(Debug, Clone)]
3038pub struct TextInputString(pub String);
3039
3040#[cfg(wayland_platform)]
3041impl AsMimeTypes for TextInputString {
3042 fn available(&self) -> Cow<'static, [String]> {
3043 Cow::Owned(
3044 SUPPORTED_TEXT_MIME_TYPES
3045 .iter()
3046 .cloned()
3047 .map(String::from)
3048 .collect::<Vec<_>>(),
3049 )
3050 }
3051
3052 fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
3053 if SUPPORTED_TEXT_MIME_TYPES.contains(&mime_type) {
3054 Some(Cow::Owned(self.0.clone().into_bytes()))
3055 } else {
3056 None
3057 }
3058 }
3059}
3060
3061#[derive(Debug, Clone, PartialEq)]
3062pub(crate) enum DraggingState {
3063 Selection,
3064 #[cfg(wayland_platform)]
3065 PrepareDnd(Point),
3066 #[cfg(wayland_platform)]
3067 Dnd(DndAction, String),
3068}
3069
3070#[cfg(wayland_platform)]
3071#[derive(Debug, Default, Clone)]
3072pub(crate) enum DndOfferState {
3073 #[default]
3074 None,
3075 HandlingOffer(Vec<String>, DndAction),
3076 Dropped,
3077}
3078#[derive(Debug, Default, Clone)]
3079#[cfg(not(wayland_platform))]
3080pub(crate) struct DndOfferState;
3081
3082#[derive(Default, Clone)]
3084#[must_use]
3085pub struct State {
3086 pub tracked_value: Value,
3087 pub value: crate::Plain,
3088 pub placeholder: crate::Plain,
3089 pub label: crate::Plain,
3090 pub helper_text: crate::Plain,
3091 pub dirty: bool,
3092 pub is_secure: bool,
3093 pub is_read_only: bool,
3094 pub emit_unfocus: bool,
3095 select_on_focus: bool,
3096 double_click_select_delimiter: Option<char>,
3097 is_focused: Option<Focus>,
3098 dragging_state: Option<DraggingState>,
3099 dnd_offer: DndOfferState,
3100 is_pasting: Option<Value>,
3101 last_click: Option<mouse::Click>,
3102 cursor: Cursor,
3103 preedit: Option<Preedit>,
3104 keyboard_modifiers: keyboard::Modifiers,
3105 scroll_offset: f32,
3106 context_menu_position: Option<iced_core::Point>,
3107 clipboard_has_text: bool,
3108 pub(crate) menu_bar_state: crate::widget::menu::MenuBarState,
3109 pub(crate) pending_action: crate::widget::text_context_menu::PendingAction,
3110}
3111
3112impl std::fmt::Debug for State {
3113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3114 f.debug_struct("State")
3115 .field("is_secure", &self.is_secure)
3116 .field("is_read_only", &self.is_read_only)
3117 .field("dirty", &self.dirty)
3118 .finish_non_exhaustive()
3119 }
3120}
3121
3122#[derive(Debug, Clone, Copy)]
3123struct Focus {
3124 updated_at: Instant,
3125 now: Instant,
3126 focused: bool,
3127 needs_update: bool,
3128}
3129
3130impl State {
3131 pub fn new(
3133 is_secure: bool,
3134 is_read_only: bool,
3135 always_active: bool,
3136 select_on_focus: bool,
3137 ) -> Self {
3138 Self {
3139 is_secure,
3140 is_read_only,
3141 is_focused: always_active.then(|| {
3142 let now = Instant::now();
3143 Focus {
3144 updated_at: now,
3145 now,
3146 focused: true,
3147 needs_update: false,
3148 }
3149 }),
3150 select_on_focus,
3151 ..Self::default()
3152 }
3153 }
3154
3155 #[must_use]
3157 pub fn selected_text(&self, text: &str) -> Option<String> {
3158 let value = Value::new(text);
3159 match self.cursor.state(&value) {
3160 cursor::State::Index(_) => None,
3161 cursor::State::Selection { start, end } => {
3162 let left = start.min(end);
3163 let right = end.max(start);
3164 Some(value.select(left, right).to_string())
3165 }
3166 }
3167 }
3168
3169 #[cfg(wayland_platform)]
3170 #[must_use]
3172 pub fn dragged_text(&self) -> Option<String> {
3173 match self.dragging_state.as_ref() {
3174 Some(DraggingState::Dnd(_, text)) => Some(text.clone()),
3175 _ => None,
3176 }
3177 }
3178
3179 pub fn focused(is_secure: bool, is_read_only: bool) -> Self {
3181 Self {
3182 tracked_value: Value::default(),
3183 is_secure,
3184 value: crate::Plain::default(),
3185 placeholder: crate::Plain::default(),
3186 label: crate::Plain::default(),
3187 helper_text: crate::Plain::default(),
3188 is_read_only,
3189 emit_unfocus: false,
3190 is_focused: None,
3191 select_on_focus: false,
3192 double_click_select_delimiter: None,
3193 dragging_state: None,
3194 dnd_offer: DndOfferState::default(),
3195 is_pasting: None,
3196 last_click: None,
3197 cursor: Cursor::default(),
3198 preedit: None,
3199 keyboard_modifiers: keyboard::Modifiers::default(),
3200 scroll_offset: 0.0,
3201 dirty: false,
3202 context_menu_position: None,
3203 clipboard_has_text: false,
3204 menu_bar_state: crate::widget::menu::MenuBarState::default(),
3205 pending_action: crate::widget::text_context_menu::pending_action(),
3206 }
3207 }
3208
3209 #[inline]
3211 #[must_use]
3212 pub fn is_focused(&self) -> bool {
3213 self.is_focused.is_some_and(|f| f.focused)
3214 }
3215
3216 #[inline]
3218 #[must_use]
3219 pub fn cursor(&self) -> Cursor {
3220 self.cursor
3221 }
3222
3223 #[cold]
3225 pub fn focus(&mut self) {
3226 let now = Instant::now();
3227 LAST_FOCUS_UPDATE.with(|x| x.set(now));
3228 let was_focused = self.is_focused.is_some_and(|f| f.focused);
3229 self.is_read_only = false;
3230 self.is_focused = Some(Focus {
3231 updated_at: now,
3232 now,
3233 focused: true,
3234 needs_update: false,
3235 });
3236
3237 if was_focused {
3238 return;
3239 }
3240 if self.select_on_focus {
3241 self.select_all()
3242 } else {
3243 self.move_cursor_to_end();
3244 }
3245 }
3246
3247 #[cold]
3249 pub(super) fn unfocus(&mut self) {
3250 self.cursor.clear_selection();
3251 self.last_click = None;
3252 self.is_focused = self.is_focused.map(|mut f| {
3253 f.focused = false;
3254 f.needs_update = false;
3255 f
3256 });
3257 self.dragging_state = None;
3258 self.is_pasting = None;
3259 self.keyboard_modifiers = keyboard::Modifiers::default();
3260 }
3261
3262 #[inline]
3264 pub fn move_cursor_to_front(&mut self) {
3265 self.cursor.move_to(0);
3266 }
3267
3268 #[inline]
3270 pub fn move_cursor_to_end(&mut self) {
3271 self.cursor.move_to(usize::MAX);
3272 }
3273
3274 #[inline]
3276 pub fn move_cursor_to(&mut self, position: usize) {
3277 self.cursor.move_to(position);
3278 }
3279
3280 #[inline]
3282 pub fn select_all(&mut self) {
3283 self.cursor.select_range(0, usize::MAX);
3284 }
3285
3286 #[inline]
3288 pub fn select_range(&mut self, start: usize, end: usize) {
3289 self.cursor.select_range(start, end);
3290 }
3291
3292 pub fn context_menu_position(&self) -> Option<iced_core::Point> {
3294 self.context_menu_position
3295 }
3296
3297 pub fn set_context_menu_position(&mut self, pos: Option<iced_core::Point>) {
3299 self.context_menu_position = pos;
3300 }
3301
3302 pub fn delete_selection(&mut self) -> String {
3304 let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
3305 editor.delete();
3306 editor.contents()
3307 }
3308
3309 pub fn paste_text(&mut self, text: &str) -> String {
3311 let paste_value = super::value::Value::new(text);
3312 let mut editor = super::editor::Editor::new(&mut self.tracked_value, &mut self.cursor);
3313 editor.paste(paste_value);
3314 let contents = editor.contents();
3315 self.tracked_value = super::value::Value::new(&contents);
3316 contents
3317 }
3318
3319 pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
3320 let (position, affinity) = find_cursor_position(bounds, value, self, target)
3321 .unwrap_or((0, text::Affinity::Before));
3322
3323 self.cursor.set_affinity(affinity);
3324 self.cursor.move_to(position);
3325 self.dragging_state = Some(DraggingState::Selection);
3326 }
3327}
3328
3329impl operation::Focusable for State {
3330 #[inline]
3331 fn is_focused(&self) -> bool {
3332 Self::is_focused(self)
3333 }
3334
3335 #[inline]
3336 fn focus(&mut self) {
3337 Self::focus(self);
3338 if let Some(focus) = self.is_focused.as_mut() {
3339 focus.needs_update = true;
3340 }
3341 }
3342
3343 #[inline]
3344 fn unfocus(&mut self) {
3345 Self::unfocus(self);
3346 if let Some(focus) = self.is_focused.as_mut() {
3347 focus.needs_update = true;
3348 }
3349 }
3350}
3351
3352impl operation::TextInput for State {
3353 #[inline]
3354 fn move_cursor_to_front(&mut self) {
3355 Self::move_cursor_to_front(self);
3356 }
3357
3358 #[inline]
3359 fn move_cursor_to_end(&mut self) {
3360 Self::move_cursor_to_end(self);
3361 }
3362
3363 #[inline]
3364 fn move_cursor_to(&mut self, position: usize) {
3365 Self::move_cursor_to(self, position);
3366 }
3367
3368 #[inline]
3369 fn select_all(&mut self) {
3370 Self::select_all(self);
3371 }
3372
3373 fn text(&self) -> &str {
3374 todo!()
3375 }
3376
3377 #[inline]
3378 fn select_range(&mut self, start: usize, end: usize) {
3379 Self::select_range(self, start, end);
3380 }
3381}
3382
3383#[inline(never)]
3384fn measure_cursor_and_scroll_offset(
3385 paragraph: &impl text::Paragraph,
3386 text_bounds: Rectangle,
3387 cursor_index: usize,
3388 value: &Value,
3389 affinity: text::Affinity,
3390 current_offset: f32,
3391) -> (f32, f32) {
3392 let byte_index = value.byte_index_at_grapheme(cursor_index);
3393 let position = paragraph
3394 .cursor_position(0, byte_index, affinity)
3395 .unwrap_or(Point::ORIGIN);
3396
3397 let offset = if position.x > current_offset + text_bounds.width - 5.0 {
3401 (position.x + 5.0) - text_bounds.width
3403 } else if position.x < current_offset + 5.0 {
3404 position.x - 5.0
3406 } else {
3407 current_offset
3409 };
3410
3411 let max_offset = (paragraph.min_width() - text_bounds.width).max(0.0);
3412 let offset = offset.clamp(0.0, max_offset);
3413
3414 (position.x, offset)
3415}
3416
3417#[inline(never)]
3420fn find_cursor_position(
3421 text_bounds: Rectangle,
3422 value: &Value,
3423 state: &State,
3424 x: f32,
3425) -> Option<(usize, text::Affinity)> {
3426 let value_str = value.to_string();
3427
3428 let hit = state.value.raw().hit_test(Point::new(
3429 x + state.scroll_offset,
3430 text_bounds.height / 2.0,
3431 ))?;
3432 let char_offset = hit.cursor();
3433 let affinity = hit.affinity();
3434
3435 let grapheme_count = unicode_segmentation::UnicodeSegmentation::graphemes(
3436 &value_str[..char_offset.min(value_str.len())],
3437 true,
3438 )
3439 .count();
3440
3441 Some((grapheme_count, affinity))
3442}
3443
3444#[inline(never)]
3445fn replace_paragraph(
3446 state: &mut State,
3447 layout: Layout<'_>,
3448 value: &Value,
3449 font: <crate::Renderer as iced_core::text::Renderer>::Font,
3450 text_size: Pixels,
3451 line_height: text::LineHeight,
3452 limits: &layout::Limits,
3453) {
3454 let mut children_layout = layout.children();
3455 let text_bounds = children_layout.next().unwrap();
3456 let bounds = limits.resolve(
3457 Length::Shrink,
3458 Length::Fill,
3459 Size::new(0., text_bounds.bounds().height),
3460 );
3461
3462 state.value = crate::Plain::new(Text {
3463 font,
3464 line_height,
3465 content: value.to_string(),
3466 bounds,
3467 size: text_size,
3468 align_x: text::Alignment::Default,
3469 align_y: alignment::Vertical::Top,
3470 shaping: text::Shaping::Advanced,
3471 wrapping: text::Wrapping::None,
3472 ellipsize: text::Ellipsize::None,
3473 });
3474}
3475
3476const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
3477
3478mod platform {
3479 use iced_core::keyboard;
3480
3481 #[inline]
3482 pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
3483 if cfg!(target_os = "macos") {
3484 modifiers.alt()
3485 } else {
3486 modifiers.control()
3487 }
3488 }
3489}
3490
3491#[inline(never)]
3492fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
3493 if state.is_focused() {
3494 let cursor = state.cursor();
3495
3496 let focus_position = match cursor.state(value) {
3497 cursor::State::Index(i) => i,
3498 cursor::State::Selection { end, .. } => end,
3499 };
3500
3501 let (_, offset) = measure_cursor_and_scroll_offset(
3502 state.value.raw(),
3503 text_bounds,
3504 focus_position,
3505 value,
3506 state.cursor().affinity(),
3507 state.scroll_offset,
3508 );
3509
3510 offset
3511 } else {
3512 match effective_alignment(state.value.raw()) {
3513 alignment::Horizontal::Right => {
3514 (state.value.raw().min_width() - text_bounds.width).max(0.0)
3515 }
3516 _ => 0.0,
3517 }
3518 }
3519}
3520
3521#[inline(never)]
3522fn alignment_offset(
3523 text_bounds_width: f32,
3524 text_min_width: f32,
3525 alignment: alignment::Horizontal,
3526) -> f32 {
3527 if text_min_width > text_bounds_width {
3528 0.0
3529 } else {
3530 match alignment {
3531 alignment::Horizontal::Left => 0.0,
3532 alignment::Horizontal::Center => (text_bounds_width - text_min_width) / 2.0,
3533 alignment::Horizontal::Right => text_bounds_width - text_min_width,
3534 }
3535 }
3536}
3537
3538#[inline(never)]
3539fn effective_alignment(paragraph: &impl text::Paragraph) -> alignment::Horizontal {
3540 if paragraph.is_rtl(0).unwrap_or(false) {
3541 alignment::Horizontal::Right
3542 } else {
3543 alignment::Horizontal::Left
3544 }
3545}
3546
3547use iced_core::widget::tree::Tree as WidgetTree;
3548
3549impl<Message: Clone + 'static> iced_core::widget::text::HasSelectableText
3550 for TextInput<'_, Message>
3551{
3552 fn selected_text(&self, tree: &WidgetTree) -> Option<String> {
3553 let state = tree.state.downcast_ref::<State>();
3554 let (start, end) = state.cursor().selection(&state.tracked_value)?;
3555 Some(state.tracked_value.select(start, end).to_string())
3556 }
3557
3558 fn select_all(&self, tree: &mut WidgetTree) {
3559 let state = tree.state.downcast_mut::<State>();
3560 state.select_all();
3561 }
3562
3563 fn is_editable(&self) -> bool {
3564 true
3565 }
3566
3567 fn has_text(&self, tree: &WidgetTree) -> bool {
3568 !tree.state.downcast_ref::<State>().tracked_value.is_empty()
3569 }
3570
3571 fn clipboard_has_text(&self, tree: &WidgetTree) -> bool {
3572 tree.state.downcast_ref::<State>().clipboard_has_text
3573 }
3574
3575 fn is_focused(&self, tree: &WidgetTree) -> bool {
3576 tree.state.downcast_ref::<State>().is_focused()
3577 }
3578
3579 fn context_menu_position(&self, tree: &WidgetTree) -> Option<iced_core::Point> {
3580 tree.state.downcast_ref::<State>().context_menu_position
3581 }
3582
3583 fn set_context_menu_position(&self, tree: &mut WidgetTree, pos: Option<iced_core::Point>) {
3584 tree.state.downcast_mut::<State>().context_menu_position = pos;
3585 }
3586
3587 fn delete_selection(&self, tree: &mut WidgetTree) -> Option<String> {
3588 let state = tree.state.downcast_mut::<State>();
3589 Some(state.delete_selection())
3590 }
3591
3592 fn paste_text(&self, tree: &mut WidgetTree, text: &str) -> Option<String> {
3593 let filtered: String = text.chars().filter(|c| !c.is_control()).collect();
3594 let state = tree.state.downcast_mut::<State>();
3595 Some(state.paste_text(&filtered))
3596 }
3597}