1use std::cell::RefCell;
2use std::ops::Range;
3use std::rc::Rc;
4
5use crate::prelude::*;
6
7use crate::text::{
8 Direction, EditableText, Movement, PreeditBackup, Selection, VerticalMovement, apply_movement,
9 enforce_text_bounds, ensure_visible, offset_for_delete_backwards, resolved_text_direction,
10};
11use accesskit::{ActionData, ActionRequest, TextDirection, TextPosition, TextSelection};
12use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
13use skia_safe::{ClipOp, Paint, PaintStyle, Rect};
14use unicode_segmentation::UnicodeSegmentation;
15
16pub enum TextEvent {
18 InsertText(String),
20 UpdatePreedit(String, Option<(usize, usize)>),
22 ClearPreedit,
24 Clear,
26 DeleteText(Movement),
28 MoveCursor(Movement, bool),
30 SelectAll,
32 SelectWord,
34 SelectParagraph,
36 StartEdit,
38 EndEdit,
40 Submit(bool),
42 Hit(f32, f32, bool),
44 Drag(f32, f32),
46 Scroll(f32, f32),
48 Copy,
50 Paste,
52 Cut,
54 SetPlaceholder(String),
56 Blur,
58 SetMaskVisible(bool),
60 ToggleMaskVisible,
62 ToggleCaret,
64}
65
66pub struct Textbox<R, T> {
73 value: R,
74 kind: TextboxKind,
75 edit: bool,
76 transform: Rc<RefCell<(f32, f32)>>,
77 on_edit: Option<Box<dyn Fn(&mut EventContext, String) + Send + Sync>>,
78 on_submit: Option<Box<dyn Fn(&mut EventContext, T, bool) + Send + Sync>>,
79 on_blur: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
80 on_cancel: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
81 validate: Option<Box<dyn Fn(&T) -> bool>>,
82 placeholder: Signal<String>,
83 show_placeholder: Signal<bool>,
84 show_caret: Signal<bool>,
85 mask_char: Signal<Option<char>>,
86 max_length: Signal<Option<usize>>,
87 can_copy: Signal<bool>,
88 can_paste: Signal<bool>,
89 mask_visible: bool,
90 real_text: String,
91 caret_timer: Timer,
92 selection: Selection,
93 preedit_backup: Option<PreeditBackup>,
94 text_overflow: Option<TextOverflow>,
95 edited_since_focus: bool,
96 edited_once: bool,
97}
98
99#[derive(Copy, Clone, PartialEq, Eq)]
101enum TextboxKind {
102 SingleLine,
103 MultiLineUnwrapped,
104 MultiLineWrapped,
105}
106
107impl<R, T> Textbox<R, T>
108where
109 R: Res<T> + 'static,
110 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
111{
112 pub fn new(cx: &mut Context, value: R) -> Handle<Self>
132 where
133 R: Clone,
134 {
135 Self::new_core(cx, value, TextboxKind::SingleLine)
136 }
137
138 pub fn new_multiline(cx: &mut Context, value: R, wrap: bool) -> Handle<Self>
162 where
163 R: Clone,
164 {
165 Self::new_core(
166 cx,
167 value,
168 if wrap { TextboxKind::MultiLineWrapped } else { TextboxKind::MultiLineUnwrapped },
169 )
170 }
171
172 fn new_core(cx: &mut Context, value: R, kind: TextboxKind) -> Handle<Self>
173 where
174 R: Clone,
175 {
176 let value_text = value.clone().to_signal(cx);
177 let caret_timer = cx.environment().caret_timer;
178 let initial_text = value.get_value(cx).to_string_local(cx);
179 let show_caret = Signal::new(false);
180 let mask_char = Signal::new(None);
181 let max_length = Signal::new(None);
182 let can_copy = Signal::new(true);
183 let can_paste = Signal::new(true);
184 let placeholder = Signal::new(String::from(""));
185 let show_placeholder = Signal::new(initial_text.is_empty());
186
187 Self {
188 value: value.clone(),
189 kind,
190 edit: false,
191 transform: Rc::new(RefCell::new((0.0, 0.0))),
192 on_edit: None,
193 on_submit: None,
194 on_blur: None,
195 on_cancel: None,
196 validate: None,
197 placeholder,
198 show_placeholder,
199 show_caret,
200 mask_char,
201 max_length,
202 can_copy,
203 can_paste,
204 mask_visible: false,
205 real_text: initial_text.clone(),
206 caret_timer,
207 selection: Selection::new(0, 0),
208 preedit_backup: None,
209 text_overflow: None,
210 edited_since_focus: false,
211 edited_once: false,
212 }
213 .build(cx, move |cx| {
214 cx.add_listener(move |textbox: &mut Self, cx, event| {
215 let flag: bool = textbox.edit;
216 event.map(|window_event, meta| match window_event {
217 WindowEvent::MouseDown(_) => {
218 if flag && meta.origin != cx.current() && cx.hovered() != cx.current() {
219 cx.emit(TextEvent::Blur);
220 }
221 }
222
223 _ => {}
224 });
225 });
226 })
227 .toggle_class("multiline", kind == TextboxKind::MultiLineWrapped)
228 .text_wrap(kind == TextboxKind::MultiLineWrapped)
229 .navigable(true)
230 .role(if kind == TextboxKind::SingleLine {
231 Role::TextInput
232 } else {
233 Role::MultilineTextInput
234 })
235 .text_value(value.clone())
236 .toggle_class("caret", show_caret)
237 .placeholder_shown(show_placeholder)
238 .bind(value_text, move |handle| {
239 handle.bind(placeholder, move |handle| {
240 let text = value_text.get();
241 let txt = text.to_string_local(&handle);
242 let mut display_text = String::new();
243 let handle = handle.modify(|textbox| {
244 textbox.real_text = txt.clone();
245 textbox.show_placeholder.set_if_changed(txt.is_empty());
246 display_text = textbox.display_text_from_real();
247 });
248 handle.text(display_text);
249 });
250 })
251 }
252
253 fn display_text_from_real(&self) -> String {
254 if self.show_placeholder.get() {
255 return self.placeholder.get().clone();
256 }
257
258 if self.mask_visible {
259 return self.real_text.clone();
260 }
261
262 let Some(mask) = self.mask_char.get() else {
263 return self.real_text.clone();
264 };
265
266 let mut masked = String::with_capacity(self.real_text.len());
267 for grapheme in self.real_text.graphemes(true) {
268 if grapheme == "\n" {
269 masked.push('\n');
270 } else {
271 masked.push(mask);
272 }
273 }
274
275 masked
276 }
277
278 fn sync_display_text(&self, cx: &mut EventContext) {
279 cx.style.text.insert(cx.current, self.display_text_from_real());
280 cx.style.needs_text_update(cx.current);
281 }
282
283 fn grapheme_index_to_byte(text: &str, idx: usize) -> usize {
284 if idx == 0 {
285 return 0;
286 }
287
288 for (i, (byte, _)) in text.grapheme_indices(true).enumerate() {
289 if i == idx {
290 return byte;
291 }
292 }
293
294 text.len()
295 }
296
297 fn byte_to_grapheme_index(text: &str, byte_offset: usize) -> usize {
298 let mut idx = 0;
299 for (byte, _) in text.grapheme_indices(true) {
300 if byte >= byte_offset {
301 break;
302 }
303 idx += 1;
304 }
305 idx
306 }
307
308 fn selection_display_to_real(
309 display_text: &str,
310 real_text: &str,
311 selection: Selection,
312 ) -> Selection {
313 let anchor_g = Self::byte_to_grapheme_index(display_text, selection.anchor);
314 let active_g = Self::byte_to_grapheme_index(display_text, selection.active);
315 let anchor = Self::grapheme_index_to_byte(real_text, anchor_g);
316 let active = Self::grapheme_index_to_byte(real_text, active_g);
317 Selection::new(anchor, active).with_h_pos(selection.h_pos)
318 }
319
320 fn remap_selection_for_display_change(&mut self, old_display: &str, new_display: &str) {
321 let anchor_g = Self::byte_to_grapheme_index(old_display, self.selection.anchor);
322 let active_g = Self::byte_to_grapheme_index(old_display, self.selection.active);
323 self.selection.anchor = Self::grapheme_index_to_byte(new_display, anchor_g);
324 self.selection.active = Self::grapheme_index_to_byte(new_display, active_g);
325 }
326
327 fn insert_text(&mut self, cx: &mut EventContext, txt: &str) {
328 if let Some(text) = cx.style.text.get_mut(cx.current) {
329 let old_display = text.clone();
330 if self.show_placeholder.get() && !txt.is_empty() {
331 text.clear();
332 self.show_placeholder.set(false);
333 }
334
335 let real_selection =
336 Self::selection_display_to_real(&old_display, &self.real_text, self.selection);
337
338 let clamped = self.clamp_insert_text(txt, real_selection.range());
339
340 text.edit(self.selection.range(), &clamped);
341 self.real_text.edit(real_selection.range(), &clamped);
342
343 let new_caret_grapheme =
346 Self::byte_to_grapheme_index(text, self.selection.min() + clamped.len());
347
348 self.show_placeholder.set(self.real_text.is_empty());
349 self.sync_display_text(cx);
350 if let Some(new_display) = cx.style.text.get(cx.current) {
351 let new_caret = Self::grapheme_index_to_byte(new_display, new_caret_grapheme);
352 self.selection = Selection::caret(new_caret);
353 }
354 cx.style.needs_access_update(cx.current);
355 }
356 }
357
358 fn update_preedit(
359 &mut self,
360 cx: &mut EventContext,
361 preedit_txt: &str,
362 cursor: Option<(usize, usize)>,
363 ) {
364 if preedit_txt.is_empty() || cursor.is_none() {
365 return;
366 }
367
368 if let Some(text) = cx.style.text.get_mut(cx.current) {
369 let old_display = text.clone();
370 if self.show_placeholder.get() {
371 text.clear();
372 self.show_placeholder.set(false);
373 }
374
375 if !self.selection.is_caret() {
376 let start = self.selection.min();
377 let end = self.selection.max();
378 let real_selection =
379 Self::selection_display_to_real(&old_display, &self.real_text, self.selection);
380
381 if end > start && end <= text.len() {
382 text.replace_range(start..end, "");
383 }
384 if real_selection.max() <= self.real_text.len() {
385 self.real_text.replace_range(real_selection.range(), "");
386 }
387 self.selection = Selection::caret(start);
388 }
389
390 let (original_selection, prev_preedit_text) = {
391 let preedit_backup = self
392 .preedit_backup
393 .get_or_insert_with(|| PreeditBackup::new(String::new(), self.selection));
394 (preedit_backup.original_selection, preedit_backup.prev_preedit.clone())
395 };
396
397 let new_caret_grapheme;
398
399 if prev_preedit_text == preedit_txt {
400 let caret = original_selection.min() + cursor.unwrap().0;
402 new_caret_grapheme = Self::byte_to_grapheme_index(text, caret);
403 } else {
404 let start = original_selection.min();
406 let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
407
408 if end > start && end <= text.len() {
410 text.replace_range(start..end, "");
411 }
412
413 let original_real_selection = Self::selection_display_to_real(
414 &old_display,
415 &self.real_text,
416 original_selection,
417 );
418 let real_start = original_real_selection.min();
419 let real_end =
420 real_start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
421 let clamped_preedit = self.clamp_insert_text(preedit_txt, real_start..real_end);
422 if real_end > real_start && real_end <= self.real_text.len() {
423 self.real_text.replace_range(real_start..real_end, "");
424 }
425 self.real_text.insert_str(real_start, &clamped_preedit);
426
427 text.insert_str(start, &clamped_preedit);
428
429 if let Some((cursor_index, _)) = cursor {
430 let new_caret =
431 original_selection.min() + cursor_index.min(clamped_preedit.len());
432 new_caret_grapheme = Self::byte_to_grapheme_index(text, new_caret);
433 } else {
434 let new_caret = original_selection.min() + clamped_preedit.chars().count();
436 new_caret_grapheme = Self::byte_to_grapheme_index(text, new_caret);
437 }
438
439 self.preedit_backup.as_mut().unwrap().set_prev_preedit(clamped_preedit);
440 }
441
442 self.show_placeholder.set(self.real_text.is_empty());
443 self.sync_display_text(cx);
444 if let Some(new_display) = cx.style.text.get(cx.current) {
445 let new_caret = Self::grapheme_index_to_byte(new_display, new_caret_grapheme);
446 self.selection = Selection::caret(new_caret);
447 }
448 }
449 }
450
451 fn clear_preedit(&mut self, cx: &mut EventContext) {
452 if let Some(text) = cx.style.text.get_mut(cx.current) {
453 if let Some(preedit_backup) = self.preedit_backup.as_ref() {
454 let old_display = text.clone();
455 let original_selection = preedit_backup.original_selection;
456 let prev_preedit_text = preedit_backup.prev_preedit.clone();
457
458 let start = original_selection.min();
459 let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
460
461 text.replace_range(start..end, "");
462
463 let original_real_selection = Self::selection_display_to_real(
464 &old_display,
465 &self.real_text,
466 original_selection,
467 );
468 let real_start = original_real_selection.min();
469 let real_end =
470 real_start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
471 if real_end <= self.real_text.len() {
472 self.real_text.replace_range(real_start..real_end, "");
473 }
474
475 self.selection = original_selection;
476
477 self.preedit_backup = None;
478 self.show_placeholder.set(self.real_text.is_empty());
479 self.sync_display_text(cx);
480 }
481 }
482 }
483
484 fn delete_text(&mut self, cx: &mut EventContext, movement: Movement) {
485 if self.show_placeholder.get() {
486 return;
487 }
488
489 if self.preedit_backup.is_some() {
490 return;
491 }
492
493 if self.selection.is_caret() {
494 if movement == Movement::Grapheme(Direction::Upstream) {
495 if self.selection.active == 0 {
496 return;
497 }
498 if let Some(text) = cx.style.text.get_mut(cx.current) {
499 let old_display = text.clone();
500 let del_offset = offset_for_delete_backwards(&self.selection, text);
501 let del_range = del_offset..self.selection.active;
502 let real_del_range = Self::selection_display_to_real(
503 &old_display,
504 &self.real_text,
505 Selection::new(del_range.start, del_range.end),
506 )
507 .range();
508
509 self.selection = Selection::caret(del_range.start);
510
511 text.edit(del_range, "");
512 self.real_text.edit(real_del_range, "");
513
514 self.sync_display_text(cx);
515 cx.style.needs_access_update(cx.current);
516 }
517 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
518 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
519 let old_display = text.clone();
520 let to_delete = apply_movement(movement, self.selection, text, paragraph, true);
521 let real_to_delete =
522 Self::selection_display_to_real(&old_display, &self.real_text, to_delete);
523 self.selection = to_delete;
524 let new_cursor_pos = self.selection.min();
525
526 text.edit(to_delete.range(), "");
527 self.real_text.edit(real_to_delete.range(), "");
528 self.selection = Selection::caret(new_cursor_pos);
529
530 self.sync_display_text(cx);
531 cx.style.needs_access_update(cx.current);
532 }
533 }
534 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
535 let old_display = text.clone();
536 let del_range = self.selection.range();
537 let real_del_range =
538 Self::selection_display_to_real(&old_display, &self.real_text, self.selection)
539 .range();
540
541 self.selection = Selection::caret(del_range.start);
542
543 text.edit(del_range, "");
544 self.real_text.edit(real_del_range, "");
545
546 self.sync_display_text(cx);
547 cx.style.needs_access_update(cx.current);
548 }
549
550 self.show_placeholder.set(self.real_text.is_empty());
551 }
552
553 fn reset_text(&mut self, cx: &mut EventContext) {
554 if let Some(text) = cx.style.text.get_mut(cx.current) {
555 text.clear();
556 self.real_text.clear();
557 self.selection = Selection::caret(0);
558 self.show_placeholder.set(true);
559 self.sync_display_text(cx);
560 cx.style.needs_access_update(cx.current);
561 }
562 }
563
564 fn move_cursor(&mut self, cx: &mut EventContext, movement: Movement, selection: bool) {
568 if let Some(text) = cx.style.text.get_mut(cx.current) {
569 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
570 let new_selection =
571 apply_movement(movement, self.selection, text, paragraph, selection);
572 self.selection = new_selection;
573 cx.needs_redraw();
574 cx.style.needs_access_update(cx.current);
575 }
576 }
577 }
578
579 fn select_all(&mut self, cx: &mut EventContext) {
580 if self.show_placeholder.get() {
581 return;
582 }
583 if let Some(text) = cx.style.text.get(cx.current) {
584 self.selection.anchor = 0;
585 self.selection.active = text.len();
586 cx.needs_redraw();
587 cx.style.needs_access_update(cx.current);
588 }
589 }
590
591 fn select_word(&mut self, cx: &mut EventContext) {
592 if self.show_placeholder.get() {
593 return;
594 }
595 self.move_cursor(cx, Movement::Word(Direction::Upstream), false);
596 self.move_cursor(cx, Movement::Word(Direction::Downstream), true);
597 }
598
599 fn select_paragraph(&mut self, cx: &mut EventContext) {
600 if self.show_placeholder.get() {
601 return;
602 }
603 self.move_cursor(cx, Movement::ParagraphStart, false);
604 self.move_cursor(cx, Movement::ParagraphEnd, true);
605 }
606
607 fn deselect(&mut self) {
608 self.selection = Selection::caret(self.selection.active);
609 }
610
611 fn coordinates_global_to_text(&self, cx: &EventContext, x: f32, y: f32) -> (f32, f32) {
615 let bounds = cx.bounds();
616
617 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
618 let padding_left = cx
619 .style
620 .padding_left
621 .get_resolved(cx.current, &cx.style.custom_units_props)
622 .unwrap_or_default();
623 let padding_top = cx
624 .style
625 .padding_top
626 .get_resolved(cx.current, &cx.style.custom_units_props)
627 .unwrap_or_default();
628 let padding_right = cx
629 .style
630 .padding_right
631 .get_resolved(cx.current, &cx.style.custom_units_props)
632 .unwrap_or_default();
633 let padding_bottom = cx
634 .style
635 .padding_bottom
636 .get_resolved(cx.current, &cx.style.custom_units_props)
637 .unwrap_or_default();
638
639 let logical_parent_width = cx.physical_to_logical(bounds.w);
640 let logical_parent_height = cx.physical_to_logical(bounds.h);
641
642 let mut padding_left =
643 padding_left.to_px(logical_parent_width, 0.0) * cx.scale_factor();
644 let mut padding_right =
645 padding_right.to_px(logical_parent_width, 0.0) * cx.scale_factor();
646 let padding_top = padding_top.to_px(logical_parent_height, 0.0) * cx.scale_factor();
647 let padding_bottom =
648 padding_bottom.to_px(logical_parent_height, 0.0) * cx.scale_factor();
649
650 if resolved_text_direction(cx.style, cx.current) == crate::style::Direction::RightToLeft
651 {
652 std::mem::swap(&mut padding_left, &mut padding_right);
653 }
654
655 let (mut top, _) = match cx.style.alignment.get(cx.current).copied().unwrap_or_default()
656 {
657 Alignment::TopLeft => (0.0, 0.0),
658 Alignment::TopCenter => (0.0, 0.5),
659 Alignment::TopRight => (0.0, 1.0),
660 Alignment::Left => (0.5, 0.0),
661 Alignment::Center => (0.5, 0.5),
662 Alignment::Right => (0.5, 1.0),
663 Alignment::BottomLeft => (1.0, 0.0),
664 Alignment::BottomCenter => (1.0, 0.5),
665 Alignment::BottomRight => (1.0, 1.0),
666 };
667
668 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
669
670 let x = x - bounds.x - padding_left;
671 let y = y - bounds.y - padding_top - top;
672
673 (x, y)
674 } else {
675 (x, y)
676 }
677 }
678
679 fn hit(&mut self, cx: &mut EventContext, x: f32, y: f32, selection: bool) {
681 if let Some(text) = cx.style.text.get(cx.current) {
682 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
683 let x = x - self.transform.borrow().0;
684 let y = y - self.transform.borrow().1;
685 let gp = paragraph
686 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
687 let num_graphemes = text.graphemes(true).count();
688 let pos = (gp.position as usize).min(num_graphemes);
689 let mut cursor = text.len();
690 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
691 if pos == i {
692 cursor = j;
693 break;
694 }
695 }
696
697 if selection {
698 self.selection.active = cursor;
699 } else {
700 self.selection = Selection::caret(cursor);
701 }
702
703 cx.needs_redraw();
704 cx.style.needs_access_update(cx.current);
705 }
706 }
707 }
708
709 fn drag(&mut self, cx: &mut EventContext, x: f32, y: f32) {
711 if let Some(text) = cx.style.text.get(cx.current) {
712 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
713 let x = x - self.transform.borrow().0;
714 let y = y - self.transform.borrow().1;
715 let gp = paragraph
716 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
717 let num_graphemes = text.graphemes(true).count();
718 let pos = (gp.position as usize).min(num_graphemes);
719
720 let mut cursor = text.len();
721 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
722 if pos == i {
723 cursor = j;
724 break;
725 }
726 }
727
728 self.selection.active = cursor;
729
730 cx.needs_redraw();
731 cx.style.needs_access_update(cx.current);
732 }
733 }
734 }
735
736 #[cfg(feature = "clipboard")]
740 fn clone_selected(&self, cx: &mut EventContext) -> Option<String> {
741 if let Some(text) = cx.style.text.get(cx.current) {
742 let real_selection =
743 Self::selection_display_to_real(text, &self.real_text, self.selection);
744 let substring = &self.real_text[real_selection.range()];
745 return Some(substring.to_string());
746 }
747
748 None
749 }
750
751 fn clone_text(&self, _cx: &mut EventContext) -> String {
752 if self.show_placeholder.get() {
753 return String::new();
754 }
755
756 self.real_text.clone()
757 }
758
759 fn clamp_insert_text(&self, txt: &str, replace_range: Range<usize>) -> String {
760 let Some(max_length) = self.max_length.get() else {
761 return txt.to_string();
762 };
763
764 let current_len = self.real_text.graphemes(true).count();
765 let replaced_len = self.real_text[replace_range].graphemes(true).count();
766 let preserved_len = current_len.saturating_sub(replaced_len);
767 let remaining = max_length.saturating_sub(preserved_len);
768
769 if remaining == 0 {
770 return String::new();
771 }
772
773 txt.graphemes(true).take(remaining).collect()
774 }
775
776 fn is_text_valid(&self, text: &str) -> bool {
777 if let Ok(value) = text.parse::<T>() {
778 if let Some(validate) = &self.validate { validate(&value) } else { true }
779 } else {
780 false
781 }
782 }
783
784 fn reset_caret_timer(&mut self, cx: &mut EventContext) {
785 cx.stop_timer(self.caret_timer);
786 if !cx.is_read_only() {
787 self.show_caret.set(true);
788 cx.start_timer(self.caret_timer);
789 }
790 }
791
792 fn reset_ime_position(&mut self, cx: &mut EventContext) {
793 cx.event_queue.push_back(
795 Event::new(WindowEvent::SetImeCursorArea(
796 (cx.bounds().x as u32, cx.bounds().y as u32),
797 ((cx.bounds().width()) as u32, cx.bounds().height() as u32),
798 ))
799 .target(cx.current),
800 );
801 }
802
803 fn draw_selection(&self, cx: &mut DrawContext, canvas: &Canvas) {
804 if !self.selection.is_caret() {
805 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
806 if let Some(text) = cx.style.text.get(cx.current) {
807 let min = text.current_grapheme_offset(self.selection.min());
808 let max = text.current_grapheme_offset(self.selection.max());
809
810 let cursor_rects = paragraph.get_rects_for_range(
811 min..max,
812 RectHeightStyle::Tight,
813 RectWidthStyle::Tight,
814 );
815
816 for cursor_rect in cursor_rects {
817 let bounds = cx.bounds();
818
819 let alignment = cx.alignment();
820
821 let (mut top, left) = match alignment {
822 Alignment::TopLeft => (0.0, 0.0),
823 Alignment::TopCenter => (0.0, 0.5),
824 Alignment::TopRight => (0.0, 1.0),
825 Alignment::Left => (0.5, 0.0),
826 Alignment::Center => (0.5, 0.5),
827 Alignment::Right => (0.5, 1.0),
828 Alignment::BottomLeft => (1.0, 0.0),
829 Alignment::BottomCenter => (1.0, 0.5),
830 Alignment::BottomRight => (1.0, 1.0),
831 };
832
833 let padding_top = match cx.padding_top() {
834 Units::Pixels(val) => val,
835 _ => 0.0,
836 };
837
838 let padding_bottom = match cx.padding_bottom() {
839 Units::Pixels(val) => val,
840 _ => 0.0,
841 };
842
843 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
844
845 let mut padding_left = match cx.padding_left() {
846 Units::Pixels(val) => val,
847 _ => 0.0,
848 };
849
850 let mut padding_right = match cx.padding_right() {
851 Units::Pixels(val) => val,
852 _ => 0.0,
853 };
854
855 if resolved_text_direction(cx.style, cx.current)
856 == crate::style::Direction::RightToLeft
857 {
858 std::mem::swap(&mut padding_left, &mut padding_right);
859 }
860
861 let x = bounds.x + padding_left + cursor_rect.rect.left + left;
862 let y = bounds.y + padding_top + cursor_rect.rect.top + top;
863
864 let x2 = x + (cursor_rect.rect.right - cursor_rect.rect.left);
865 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
866
867 let mut paint = Paint::default();
868 paint.set_anti_alias(true);
869 paint.set_style(PaintStyle::Fill);
870 paint.set_color(cx.selection_color());
871
872 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
873 }
874 }
875 }
876 }
877 }
878
879 pub fn draw_text_caret(&self, cx: &mut DrawContext, canvas: &Canvas) {
881 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
882 if let Some(text) = cx.style.text.get(cx.current) {
883 let bounds = cx.bounds();
884
885 let current = text.current_grapheme_offset(self.selection.active);
886
887 let grapheme_count = text.graphemes(true).count();
888 let (range_start, range_end, use_trailing_edge) = if current < grapheme_count {
889 (current, current + 1, false)
890 } else if current > 0 {
891 (current - 1, current, true)
893 } else {
894 (0, 1, false)
896 };
897
898 let rects = paragraph.get_rects_for_range(
899 range_start..range_end,
900 RectHeightStyle::Tight,
901 RectWidthStyle::Tight,
902 );
903
904 let Some(cursor_rect) = rects.first() else {
905 return;
906 };
907
908 let alignment = cx.alignment();
909
910 let (mut top, _) = match alignment {
911 Alignment::TopLeft => (0.0, 0.0),
912 Alignment::TopCenter => (0.0, 0.5),
913 Alignment::TopRight => (0.0, 1.0),
914 Alignment::Left => (0.5, 0.0),
915 Alignment::Center => (0.5, 0.5),
916 Alignment::Right => (0.5, 1.0),
917 Alignment::BottomLeft => (1.0, 0.0),
918 Alignment::BottomCenter => (1.0, 0.5),
919 Alignment::BottomRight => (1.0, 1.0),
920 };
921
922 let padding_top = match cx.padding_top() {
923 Units::Pixels(val) => val,
924 _ => 0.0,
925 };
926
927 let padding_bottom = match cx.padding_bottom() {
928 Units::Pixels(val) => val,
929 _ => 0.0,
930 };
931
932 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
933
934 let mut padding_left = match cx.padding_left() {
935 Units::Pixels(val) => val,
936 _ => 0.0,
937 };
938
939 let mut padding_right = match cx.padding_right() {
940 Units::Pixels(val) => val,
941 _ => 0.0,
942 };
943
944 if resolved_text_direction(cx.style, cx.current)
945 == crate::style::Direction::RightToLeft
946 {
947 std::mem::swap(&mut padding_left, &mut padding_right);
948 }
949
950 let caret_x =
951 if use_trailing_edge { cursor_rect.rect.right } else { cursor_rect.rect.left };
952
953 let x = (bounds.x + padding_left + caret_x).round();
954 let y = (bounds.y + padding_top + cursor_rect.rect.top + top).round();
955
956 let x2 = x + 1.0;
957 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
958
959 let mut paint = Paint::default();
960 paint.set_anti_alias(true);
961 paint.set_style(PaintStyle::Fill);
962 paint.set_color(cx.caret_color());
963
964 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
965
966 let mut transform = self.transform.borrow_mut();
967
968 let text_bounds = BoundingBox::from_min_max(
969 bounds.x + padding_left,
970 bounds.y + padding_top + top,
971 bounds.x + padding_left + paragraph.max_intrinsic_width(),
972 bounds.y + padding_top + top + paragraph.height(),
973 );
974
975 let mut bounds = bounds;
976
977 bounds =
978 bounds.shrink_sides(padding_left, padding_top, padding_right, padding_bottom);
979
980 let (tx, ty) =
981 enforce_text_bounds(&text_bounds, &bounds, (transform.0, transform.1));
982
983 let caret_box = BoundingBox::from_min_max(x, y, x2, y2);
984
985 let (new_tx, new_ty) = ensure_visible(&caret_box, &bounds, (tx, ty));
986
987 if new_tx != transform.0 || new_ty != transform.1 {
988 *transform = (new_tx, new_ty);
989 cx.needs_redraw();
990 }
991 }
992 }
993 }
994}
995
996impl<R, T> Handle<'_, Textbox<R, T>>
997where
998 R: Res<T> + 'static,
999 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1000{
1001 pub fn on_edit<F>(self, callback: F) -> Self
1005 where
1006 F: 'static + Fn(&mut EventContext, String) + Send + Sync,
1007 {
1008 self.modify(|textbox| textbox.on_edit = Some(Box::new(callback)))
1009 }
1010
1011 pub fn on_submit<F>(self, callback: F) -> Self
1016 where
1017 F: 'static + Fn(&mut EventContext, T, bool) + Send + Sync,
1018 {
1019 self.modify(|textbox| textbox.on_submit = Some(Box::new(callback)))
1020 }
1021
1022 pub fn on_blur<F>(self, callback: F) -> Self
1024 where
1025 F: 'static + Fn(&mut EventContext) + Send + Sync,
1026 {
1027 self.modify(|textbox| textbox.on_blur = Some(Box::new(callback)))
1028 }
1029
1030 pub fn on_cancel<F>(self, callback: F) -> Self
1032 where
1033 F: 'static + Fn(&mut EventContext) + Send + Sync,
1034 {
1035 self.modify(|textbox| textbox.on_cancel = Some(Box::new(callback)))
1036 }
1037
1038 pub fn validate<F>(self, is_valid: F) -> Self
1042 where
1043 F: 'static + Fn(&T) -> bool + Send + Sync,
1044 {
1045 self.modify(|textbox| textbox.validate = Some(Box::new(is_valid)))
1046 }
1047
1048 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
1050 self,
1051 text: impl Res<P> + 'static,
1052 ) -> Self {
1053 let text = text.to_signal(self.cx);
1054 self.bind(text, move |mut handle| {
1055 let text = text.get();
1056 let txt = text.to_string_local(&handle);
1057 let entity = handle.entity();
1058 handle = handle.modify(|textbox| textbox.placeholder.set(txt));
1059 handle.context().style.needs_access_update(entity);
1060 })
1061 }
1062
1063 pub fn mask_char<U: Into<Option<char>> + Clone + 'static>(
1067 self,
1068 mask: impl Res<U> + 'static,
1069 ) -> Self {
1070 let mask = mask.to_signal(self.cx);
1071 self.bind(mask, move |mut handle| {
1072 let entity = handle.entity();
1073 let new_mask = mask.get().into();
1074 let mut display_text = String::new();
1075 handle = handle.modify(|textbox| {
1076 let old_display = textbox.display_text_from_real();
1077 textbox.mask_char.set_if_changed(new_mask);
1078 let new_display = textbox.display_text_from_real();
1079 textbox.remap_selection_for_display_change(&old_display, &new_display);
1080 display_text = new_display;
1081 });
1082 handle = handle.text(display_text);
1083 handle.context().style.needs_text_update(entity);
1084 handle.context().needs_redraw(entity);
1085 })
1086 }
1087
1088 pub fn mask_visible<U: Into<bool> + Clone + 'static>(
1090 self,
1091 visible: impl Res<U> + 'static,
1092 ) -> Self {
1093 let visible = visible.to_signal(self.cx);
1094 self.bind(visible, move |mut handle| {
1095 let entity = handle.entity();
1096 let new_visible = visible.get().into();
1097 let mut display_text = String::new();
1098 let mut changed = false;
1099
1100 handle = handle.modify(|textbox| {
1101 if textbox.mask_visible != new_visible {
1102 let old_display = textbox.display_text_from_real();
1103 textbox.mask_visible = new_visible;
1104 let new_display = textbox.display_text_from_real();
1105 textbox.remap_selection_for_display_change(&old_display, &new_display);
1106 display_text = new_display;
1107 changed = true;
1108 }
1109 });
1110
1111 if changed {
1112 handle = handle.text(display_text);
1113 handle.context().style.needs_text_update(entity);
1114 handle.context().needs_redraw(entity);
1115 }
1116 })
1117 }
1118
1119 pub fn max_length<U: Into<Option<usize>> + Clone + 'static>(
1123 self,
1124 max_length: impl Res<U> + 'static,
1125 ) -> Self {
1126 let max_length = max_length.to_signal(self.cx);
1127 self.bind(max_length, move |handle| {
1128 let value = max_length.get().into();
1129 handle.modify(|textbox| textbox.max_length.set_if_changed(value));
1130 })
1131 }
1132
1133 pub fn can_copy<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1135 let state = state.to_signal(self.cx);
1136 self.bind(state, move |handle| {
1137 let value = state.get().into();
1138 handle.modify(|textbox| textbox.can_copy.set_if_changed(value));
1139 })
1140 }
1141
1142 pub fn can_paste<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1144 let state = state.to_signal(self.cx);
1145 self.bind(state, move |handle| {
1146 let value = state.get().into();
1147 handle.modify(|textbox| textbox.can_paste.set_if_changed(value));
1148 })
1149 }
1150}
1151
1152fn byte_offset_to_char_index(character_lengths: &[u8], byte_offset: usize) -> usize {
1155 let mut cumulative = 0;
1156 for (i, &len) in character_lengths.iter().enumerate() {
1157 cumulative += len as usize;
1158 if byte_offset < cumulative {
1159 return i;
1160 }
1161 }
1162 character_lengths.len()
1163}
1164
1165impl<R, T> View for Textbox<R, T>
1166where
1167 R: Res<T> + 'static,
1168 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1169{
1170 fn element(&self) -> Option<&'static str> {
1171 Some("textbox")
1172 }
1173
1174 fn accessibility(&self, cx: &mut AccessContext, node: &mut AccessNode) {
1175 if !self.placeholder.get().is_empty() {
1176 node.set_placeholder(self.placeholder.get().clone());
1177 }
1178
1179 let node_id = node.node_id();
1180
1181 let selection = self.selection;
1182
1183 let mut selection_active_line = None;
1184 let mut selection_anchor_line = None;
1185 let mut selection_active_cursor = 0;
1186 let mut selection_anchor_cursor = 0;
1187 let mut first_line_node_id = None;
1188
1189 let text = if self.show_placeholder.get() {
1190 ""
1191 } else {
1192 cx.style.text.get(cx.current).map(|t| t.as_str()).unwrap_or("")
1193 };
1194 let text_len = text.len();
1198
1199 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
1200 let text_direction = if resolved_text_direction(cx.style, cx.current)
1201 == crate::style::Direction::RightToLeft
1202 {
1203 TextDirection::RightToLeft
1204 } else {
1205 TextDirection::LeftToRight
1206 };
1207
1208 let line_metrics = paragraph.get_line_metrics();
1209 for line in line_metrics.iter() {
1210 if line.start_index >= text_len && text_len > 0 {
1212 continue;
1213 }
1214
1215 let mut line_node = AccessNode::new_from_parent(node_id, line.line_number);
1217 line_node.set_role(Role::TextRun);
1218 line_node.set_text_direction(text_direction);
1219 line_node.set_bounds(BoundingBox {
1220 x: line.left as f32,
1221 y: (line.baseline - line.ascent) as f32,
1222 w: line.width as f32,
1223 h: line.height as f32,
1224 });
1225
1226 let glyph_end = line.end_index.min(text_len);
1228 if line.start_index > glyph_end {
1229 continue;
1230 }
1231 let estimated_chars = glyph_end.saturating_sub(line.start_index);
1232 let mut character_lengths: Vec<u8> = Vec::with_capacity(estimated_chars);
1233 let mut character_positions: Vec<f32> = Vec::with_capacity(estimated_chars);
1234 let mut character_widths: Vec<f32> = Vec::with_capacity(estimated_chars);
1235 let mut glyph_pos = line.start_index;
1236
1237 while glyph_pos < glyph_end {
1238 if let Some(cluster_info) = paragraph.get_glyph_cluster_at(glyph_pos) {
1239 let length = cluster_info.text_range.end - cluster_info.text_range.start;
1240 if length == 0 {
1241 break;
1242 }
1243
1244 character_lengths.push(length as u8);
1245 character_positions.push(cluster_info.bounds.left());
1246 character_widths.push(cluster_info.bounds.width());
1247
1248 glyph_pos += length;
1249 } else {
1250 break;
1251 }
1252 }
1253
1254 let mut line_end = if line.hard_break {
1256 line.end_including_newline.min(text_len)
1257 } else {
1258 glyph_end
1259 };
1260 if line_end < line.start_index {
1261 line_end = line.start_index;
1262 }
1263 let line_text = text.get(line.start_index..line_end).unwrap_or("").to_owned();
1264
1265 if line.hard_break && line.end_including_newline <= text_len {
1266 character_lengths.push(1);
1267 character_positions.push(line.width as f32);
1268 character_widths.push(0.0);
1269 }
1270
1271 let mut word_starts = Vec::new();
1272 let mut previous_is_alphanumeric = text
1273 .get(..line.start_index)
1274 .and_then(|prefix| prefix.graphemes(true).next_back())
1275 .and_then(|grapheme| grapheme.chars().next())
1276 .is_some_and(|ch| ch.is_alphanumeric());
1277
1278 for (character_index, grapheme) in line_text.graphemes(true).enumerate() {
1279 let current_is_alphanumeric =
1280 grapheme.chars().next().is_some_and(|ch| ch.is_alphanumeric());
1281
1282 if current_is_alphanumeric
1283 && !previous_is_alphanumeric
1284 && let Ok(character_index) = u8::try_from(character_index)
1285 {
1286 word_starts.push(character_index);
1287 }
1288
1289 previous_is_alphanumeric = current_is_alphanumeric;
1290 }
1291
1292 if first_line_node_id.is_none() {
1293 first_line_node_id = Some(line_node.node_id());
1294 }
1295
1296 if selection.active >= line.start_index && selection.active <= line_end {
1298 selection_active_line = Some(line_node.node_id());
1299 selection_active_cursor = byte_offset_to_char_index(
1300 &character_lengths,
1301 selection.active - line.start_index,
1302 );
1303 }
1304
1305 if selection.anchor >= line.start_index && selection.anchor <= line_end {
1307 selection_anchor_line = Some(line_node.node_id());
1308 selection_anchor_cursor = byte_offset_to_char_index(
1309 &character_lengths,
1310 selection.anchor - line.start_index,
1311 );
1312 }
1313
1314 line_node.set_value(line_text.into_boxed_str());
1315 line_node.set_character_lengths(character_lengths.into_boxed_slice());
1316 line_node.set_character_positions(character_positions.into_boxed_slice());
1317 line_node.set_character_widths(character_widths.into_boxed_slice());
1318 line_node.set_word_starts(word_starts.into_boxed_slice());
1319
1320 node.add_child(line_node);
1321 }
1322 }
1323
1324 if let Some(fallback) = first_line_node_id {
1325 node.set_text_selection(TextSelection {
1326 anchor: TextPosition {
1327 node: selection_anchor_line.unwrap_or(fallback),
1328 character_index: selection_anchor_cursor,
1329 },
1330 focus: TextPosition {
1331 node: selection_active_line.unwrap_or(fallback),
1332 character_index: selection_active_cursor,
1333 },
1334 });
1335 }
1336 }
1337
1338 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
1339 event.map(|window_event, meta| match window_event {
1341 WindowEvent::MouseDown(MouseButton::Left) => {
1342 if meta.origin == cx.current {
1343 return;
1344 }
1345
1346 if cx.is_over() {
1347 if !cx.is_disabled() {
1348 cx.focus_with_visibility(true);
1349 cx.capture();
1350 cx.lock_cursor_icon();
1351
1352 if !self.edit {
1353 cx.emit(TextEvent::StartEdit);
1354 }
1355 self.reset_caret_timer(cx);
1356 cx.emit(TextEvent::Hit(
1357 cx.mouse.cursor_x,
1358 cx.mouse.cursor_y,
1359 cx.modifiers.shift(),
1360 ));
1361 }
1362 } else {
1363 cx.release();
1364
1365 cx.event_queue.push_back(
1367 Event::new(WindowEvent::MouseDown(MouseButton::Left)).target(cx.hovered()),
1368 );
1369 cx.event_queue.push_back(
1370 Event::new(WindowEvent::PressDown { mouse: true }).target(cx.hovered()),
1371 );
1372 }
1373 }
1374
1375 WindowEvent::FocusIn => {
1376 if cx.mouse.left.pressed != cx.current()
1377 || cx.mouse.left.state == MouseButtonState::Released
1378 {
1379 cx.emit(TextEvent::StartEdit);
1380 }
1381 }
1382
1383 WindowEvent::FocusOut => {
1384 cx.emit(TextEvent::EndEdit);
1385 }
1386
1387 WindowEvent::MouseDoubleClick(MouseButton::Left) => {
1388 cx.emit(TextEvent::SelectWord);
1389 }
1390
1391 WindowEvent::MouseTripleClick(MouseButton::Left) => {
1392 cx.emit(TextEvent::SelectParagraph);
1393 }
1394
1395 WindowEvent::MouseUp(MouseButton::Left) => {
1396 self.reset_caret_timer(cx);
1397 cx.unlock_cursor_icon();
1398 cx.release();
1399 }
1400
1401 WindowEvent::MouseMove(x, y) => {
1402 if cx.mouse.left.state == MouseButtonState::Pressed
1403 && cx.mouse.left.pressed == cx.current
1404 {
1405 if self.edit {
1406 self.reset_caret_timer(cx);
1407 }
1408 if cx.mouse.left.pos_down.0 != *x || cx.mouse.left.pos_down.1 != *y {
1409 cx.emit(TextEvent::Drag(*x, *y));
1410 }
1411 }
1412 }
1413
1414 WindowEvent::MouseScroll(x, y) => {
1415 cx.emit(TextEvent::Scroll(*x, *y));
1416 }
1417
1418 WindowEvent::CharInput(c) => {
1419 if *c != '\u{1b}' && *c != '\u{8}' && *c != '\u{9}' && *c != '\u{7f}' && *c != '\u{0d}' && !cx.modifiers.ctrl() &&
1425 !cx.modifiers.logo() &&
1426 self.edit &&
1427 !cx.is_read_only()
1428 {
1429 self.reset_caret_timer(cx);
1430 cx.emit(TextEvent::InsertText(String::from(*c)));
1431 }
1432 }
1433
1434 WindowEvent::ImeCommit(text) => {
1435 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1436 self.reset_caret_timer(cx);
1437 cx.emit(TextEvent::ClearPreedit);
1438 cx.emit(TextEvent::InsertText(text.to_string()));
1439
1440 self.reset_ime_position(cx);
1441 }
1442 }
1443
1444 WindowEvent::ImePreedit(text, cursor) => {
1445 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1446 self.reset_caret_timer(cx);
1447 cx.emit(TextEvent::UpdatePreedit(text.to_string(), *cursor));
1448 }
1449 }
1450
1451 WindowEvent::KeyDown(code, _) => match code {
1452 Code::Enter => {
1453 if matches!(self.kind, TextboxKind::SingleLine) {
1454 cx.emit(TextEvent::Submit(true));
1455 } else if !cx.is_read_only() {
1456 self.reset_caret_timer(cx);
1457 cx.emit(TextEvent::InsertText("\n".to_owned()));
1458 }
1459 }
1460
1461 Code::ArrowLeft => {
1468 self.reset_caret_timer(cx);
1469 #[cfg(target_os = "macos")]
1473 let movement = if cx.modifiers.logo() {
1474 Movement::LineStart
1475 } else if cx.modifiers.alt() {
1476 Movement::Word(Direction::Left)
1477 } else {
1478 Movement::Grapheme(Direction::Left)
1479 };
1480 #[cfg(not(target_os = "macos"))]
1481 let movement = if cx.modifiers.ctrl() {
1482 Movement::Word(Direction::Left)
1483 } else {
1484 Movement::Grapheme(Direction::Left)
1485 };
1486
1487 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1488 }
1489
1490 Code::ArrowRight => {
1491 self.reset_caret_timer(cx);
1492
1493 #[cfg(target_os = "macos")]
1494 let movement = if cx.modifiers.logo() {
1495 Movement::LineEnd
1496 } else if cx.modifiers.alt() {
1497 Movement::Word(Direction::Right)
1498 } else {
1499 Movement::Grapheme(Direction::Right)
1500 };
1501 #[cfg(not(target_os = "macos"))]
1502 let movement = if cx.modifiers.ctrl() {
1503 Movement::Word(Direction::Right)
1504 } else {
1505 Movement::Grapheme(Direction::Right)
1506 };
1507
1508 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1509 }
1510
1511 Code::ArrowUp => {
1512 self.reset_caret_timer(cx);
1513 if self.kind != TextboxKind::SingleLine {
1514 cx.emit(TextEvent::MoveCursor(
1515 Movement::Vertical(VerticalMovement::LineUp),
1516 cx.modifiers.shift(),
1517 ));
1518 }
1519 }
1520
1521 Code::ArrowDown => {
1522 self.reset_caret_timer(cx);
1523 if self.kind != TextboxKind::SingleLine {
1524 cx.emit(TextEvent::MoveCursor(
1525 Movement::Vertical(VerticalMovement::LineDown),
1526 cx.modifiers.shift(),
1527 ));
1528 }
1529 }
1530
1531 Code::Backspace => {
1532 self.reset_caret_timer(cx);
1533 if !cx.is_read_only() {
1534 #[cfg(target_os = "macos")]
1535 let movement = if cx.modifiers.logo() {
1536 Movement::LineStart
1540 } else if cx.modifiers.alt() {
1541 Movement::Word(Direction::Upstream)
1542 } else {
1543 Movement::Grapheme(Direction::Upstream)
1544 };
1545 #[cfg(not(target_os = "macos"))]
1546 let movement = if cx.modifiers.ctrl() {
1547 Movement::Word(Direction::Upstream)
1548 } else {
1549 Movement::Grapheme(Direction::Upstream)
1550 };
1551
1552 cx.emit(TextEvent::DeleteText(movement));
1553 }
1554 }
1555
1556 Code::Delete => {
1557 self.reset_caret_timer(cx);
1558 if !cx.is_read_only() {
1559 #[cfg(target_os = "macos")]
1560 let movement = if cx.modifiers.alt() {
1561 Movement::Word(Direction::Downstream)
1562 } else {
1563 Movement::Grapheme(Direction::Downstream)
1564 };
1565 #[cfg(not(target_os = "macos"))]
1566 let movement = if cx.modifiers.ctrl() {
1567 Movement::Word(Direction::Downstream)
1568 } else {
1569 Movement::Grapheme(Direction::Downstream)
1570 };
1571
1572 cx.emit(TextEvent::DeleteText(movement));
1573 }
1574 }
1575
1576 Code::Escape => {
1577 if let Some(callback) = &self.on_cancel {
1578 (callback)(cx);
1579 } else {
1580 cx.emit(TextEvent::EndEdit);
1581 }
1582 }
1583
1584 Code::Home => {
1585 self.reset_caret_timer(cx);
1586 cx.emit(TextEvent::MoveCursor(Movement::LineStart, cx.modifiers.shift()));
1587 }
1588
1589 Code::End => {
1590 self.reset_caret_timer(cx);
1591 cx.emit(TextEvent::MoveCursor(Movement::LineEnd, cx.modifiers.shift()));
1592 }
1593
1594 Code::PageUp | Code::PageDown => {
1595 self.reset_caret_timer(cx);
1596 let direction = if *code == Code::PageUp {
1597 Direction::Upstream
1598 } else {
1599 Direction::Downstream
1600 };
1601 cx.emit(TextEvent::MoveCursor(
1602 if cx.modifiers.ctrl() {
1603 Movement::Body(direction)
1604 } else {
1605 Movement::Page(direction)
1606 },
1607 cx.modifiers.shift(),
1608 ));
1609 }
1610
1611 Code::KeyA => {
1612 #[cfg(target_os = "macos")]
1613 let modifier = Modifiers::SUPER;
1614 #[cfg(not(target_os = "macos"))]
1615 let modifier = Modifiers::CTRL;
1616
1617 if cx.modifiers == &modifier {
1618 cx.emit(TextEvent::SelectAll);
1619 }
1620 }
1621
1622 Code::KeyC => {
1623 #[cfg(target_os = "macos")]
1624 let modifier = Modifiers::SUPER;
1625 #[cfg(not(target_os = "macos"))]
1626 let modifier = Modifiers::CTRL;
1627
1628 if cx.modifiers == &modifier && self.can_copy.get() {
1629 cx.emit(TextEvent::Copy);
1630 }
1631 }
1632
1633 Code::KeyV => {
1634 #[cfg(target_os = "macos")]
1635 let modifier = Modifiers::SUPER;
1636 #[cfg(not(target_os = "macos"))]
1637 let modifier = Modifiers::CTRL;
1638
1639 if cx.modifiers == &modifier && self.can_paste.get() {
1640 cx.emit(TextEvent::Paste);
1641 }
1642 }
1643
1644 Code::KeyX => {
1645 #[cfg(target_os = "macos")]
1646 let modifier = Modifiers::SUPER;
1647 #[cfg(not(target_os = "macos"))]
1648 let modifier = Modifiers::CTRL;
1649
1650 if cx.modifiers == &modifier && !cx.is_read_only() {
1651 cx.emit(TextEvent::Cut);
1652 }
1653 }
1654
1655 _ => {}
1656 },
1657
1658 WindowEvent::ActionRequest(ActionRequest {
1659 action: accesskit::Action::SetTextSelection,
1660 target_tree: _,
1661 target_node: _,
1662 data: Some(ActionData::SetTextSelection(_selection)),
1663 }) => {
1664 }
1666
1667 _ => {}
1668 });
1669
1670 event.map(|text_event, _| match text_event {
1672 TextEvent::InsertText(text) => {
1673 if self.preedit_backup.is_some() {
1674 return;
1675 }
1676
1677 self.edited_since_focus = true;
1678 self.edited_once = true;
1679
1680 if self.show_placeholder.get() {
1681 self.reset_text(cx);
1682 }
1683
1684 self.insert_text(cx, text);
1685
1686 let text = self.clone_text(cx);
1687
1688 cx.set_valid(self.is_text_valid(&text));
1689
1690 if self.edit {
1691 if let Some(callback) = &self.on_edit {
1692 (callback)(cx, text);
1693 }
1694 }
1695 }
1696
1697 TextEvent::UpdatePreedit(preedit, cursor) => {
1698 self.update_preedit(cx, preedit, *cursor);
1699 }
1700
1701 TextEvent::ClearPreedit => {
1702 self.clear_preedit(cx);
1703 }
1704
1705 TextEvent::Clear => {
1706 self.reset_text(cx);
1707 cx.needs_relayout();
1709 cx.needs_redraw();
1710 }
1711
1712 TextEvent::DeleteText(movement) => {
1713 if self.edit {
1714 self.edited_since_focus = true;
1715 self.edited_once = true;
1716 self.delete_text(cx, *movement);
1717
1718 let text = self.clone_text(cx);
1719
1720 cx.set_valid(self.is_text_valid(&text));
1721
1722 if let Some(callback) = &self.on_edit {
1723 (callback)(cx, text);
1724 }
1725 }
1726 }
1727
1728 TextEvent::MoveCursor(movement, selection) => {
1729 if self.edit && !self.show_placeholder.get() && self.preedit_backup.is_none() {
1730 self.move_cursor(cx, *movement, *selection);
1731 }
1732 }
1733
1734 TextEvent::SetPlaceholder(text) => {
1735 self.placeholder.set(text.clone());
1736 cx.style.needs_access_update(cx.current);
1737 }
1738
1739 TextEvent::StartEdit => {
1740 if !cx.is_disabled() && !self.edit {
1741 self.edit = true;
1742 self.edited_since_focus = false;
1743 cx.focus_with_visibility(true);
1744 cx.capture();
1745 self.reset_caret_timer(cx);
1746 self.reset_ime_position(cx);
1747
1748 self.text_overflow = cx.style.text_overflow.get_inline(cx.current).copied();
1749 cx.style.text_overflow.remove(cx.current);
1750
1751 let text = self.value.get_value(cx);
1752 let text = text.to_string_local(cx);
1753 self.real_text = text.clone();
1754
1755 if text.is_empty() {
1756 self.show_placeholder.set(true);
1757 self.selection = Selection::caret(0);
1758 cx.style.needs_access_update(cx.current);
1759 } else {
1760 self.show_placeholder.set(false);
1761 self.select_all(cx);
1762 }
1763
1764 if self.edited_once || !text.is_empty() {
1767 cx.set_valid(self.is_text_valid(&text));
1768 } else {
1769 cx.set_valid(true);
1770 }
1771 }
1772
1773 self.sync_display_text(cx);
1774 }
1775
1776 TextEvent::EndEdit => {
1777 self.deselect();
1778 self.edit = false;
1779 cx.release();
1780 cx.stop_timer(self.caret_timer);
1781
1782 let text = self.clone_text(cx);
1783 self.show_placeholder.set(text.is_empty());
1784
1785 if let Some(text_overflow) = self.text_overflow {
1786 cx.style.text_overflow.insert(cx.current, text_overflow);
1787 } else {
1788 cx.style.text_overflow.remove(cx.current);
1789 }
1790
1791 self.select_all(cx);
1792
1793 if self.edited_since_focus {
1794 cx.set_valid(self.is_text_valid(&text));
1795 }
1796
1797 let mut transform = self.transform.borrow_mut();
1799 *transform = (0.0, 0.0);
1800
1801 self.selection = Selection::caret(0);
1803
1804 self.sync_display_text(cx);
1805 cx.style.needs_access_update(cx.current);
1806 }
1807
1808 TextEvent::Blur => {
1809 if cx.focused() == cx.current() {
1813 cx.focus_with_visibility(false);
1814 }
1815
1816 if let Some(callback) = &self.on_blur {
1817 (callback)(cx);
1818 } else {
1819 cx.emit(TextEvent::Submit(false));
1820 cx.emit(TextEvent::EndEdit);
1821 }
1822 }
1823
1824 TextEvent::SetMaskVisible(visible) => {
1825 if self.mask_visible != *visible {
1826 let old_display = self.display_text_from_real();
1827 self.mask_visible = *visible;
1828 let new_display = self.display_text_from_real();
1829 self.remap_selection_for_display_change(&old_display, &new_display);
1830 self.sync_display_text(cx);
1831 cx.needs_redraw();
1832 }
1833 }
1834
1835 TextEvent::ToggleMaskVisible => {
1836 let old_display = self.display_text_from_real();
1837 self.mask_visible = !self.mask_visible;
1838 let new_display = self.display_text_from_real();
1839 self.remap_selection_for_display_change(&old_display, &new_display);
1840 self.sync_display_text(cx);
1841 cx.needs_redraw();
1842 }
1843
1844 TextEvent::Submit(reason) => {
1845 if let Some(callback) = &self.on_submit {
1846 let text = self.clone_text(cx);
1847 let is_valid = self.is_text_valid(&text);
1848 cx.set_valid(is_valid);
1849 if is_valid && let Ok(value) = text.parse::<T>() {
1850 (callback)(cx, value, *reason);
1851 }
1852 }
1853 }
1854
1855 TextEvent::SelectAll => {
1856 self.select_all(cx);
1857 }
1858
1859 TextEvent::SelectWord => {
1860 self.select_word(cx);
1861 }
1862
1863 TextEvent::SelectParagraph => {
1864 self.select_paragraph(cx);
1865 }
1866
1867 TextEvent::Hit(posx, posy, selection) => {
1868 if !self.show_placeholder.get() {
1869 self.hit(cx, *posx, *posy, *selection);
1870 }
1871 }
1872
1873 TextEvent::Drag(posx, posy) => {
1874 if !self.show_placeholder.get() {
1875 self.drag(cx, *posx, *posy);
1876 }
1877 }
1878
1879 TextEvent::Scroll(_x, _y) => {
1880 }
1882
1883 TextEvent::Copy =>
1884 {
1885 #[cfg(feature = "clipboard")]
1886 if self.edit && self.can_copy.get() {
1887 if let Some(selected_text) = self.clone_selected(cx) {
1888 if !selected_text.is_empty() {
1889 cx.set_clipboard(selected_text)
1890 .expect("Failed to add text to clipboard");
1891 }
1892 }
1893 }
1894 }
1895
1896 TextEvent::Paste =>
1897 {
1898 #[cfg(feature = "clipboard")]
1899 if self.edit && self.can_paste.get() {
1900 if let Ok(text) = cx.get_clipboard() {
1901 cx.emit(TextEvent::InsertText(text));
1902 }
1903 }
1904 }
1905
1906 TextEvent::Cut =>
1907 {
1908 #[cfg(feature = "clipboard")]
1909 if self.edit {
1910 if let Some(selected_text) = self.clone_selected(cx) {
1911 if !selected_text.is_empty() {
1912 self.edited_since_focus = true;
1913 self.edited_once = true;
1914 cx.set_clipboard(selected_text)
1915 .expect("Failed to add text to clipboard");
1916 self.delete_text(cx, Movement::Grapheme(Direction::Upstream));
1917
1918 let text = self.clone_text(cx);
1919
1920 cx.set_valid(self.is_text_valid(&text));
1921
1922 if let Some(callback) = &self.on_edit {
1923 (callback)(cx, text);
1924 }
1925 }
1926 }
1927 }
1928 }
1929
1930 TextEvent::ToggleCaret => {
1931 self.show_caret ^= true;
1932 }
1933 });
1934 }
1935
1936 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
1938 cx.draw_shadows(canvas);
1939 cx.draw_background(canvas);
1940 cx.draw_border(canvas);
1941 cx.draw_outline(canvas);
1942
1943 canvas.save();
1946 let path = cx.path();
1947 canvas.clip_path(&path, ClipOp::Intersect, true);
1948
1949 let bounds = cx.bounds();
1950 let padding_left = match cx.padding_left() {
1951 Units::Pixels(val) => val,
1952 _ => 0.0,
1953 };
1954 let padding_right = match cx.padding_right() {
1955 Units::Pixels(val) => val,
1956 _ => 0.0,
1957 };
1958 let padding_top = match cx.padding_top() {
1959 Units::Pixels(val) => val,
1960 _ => 0.0,
1961 };
1962 let padding_bottom = match cx.padding_bottom() {
1963 Units::Pixels(val) => val,
1964 _ => 0.0,
1965 };
1966
1967 let content_left = bounds.x + padding_left;
1968 let content_top = bounds.y + padding_top;
1969 let content_right = (bounds.x + bounds.w - padding_right).max(content_left);
1970 let content_bottom = (bounds.y + bounds.h - padding_bottom).max(content_top);
1971 canvas.clip_rect(
1972 Rect::new(content_left, content_top, content_right, content_bottom),
1973 ClipOp::Intersect,
1974 true,
1975 );
1976
1977 let transform = *self.transform.borrow();
1978 canvas.translate((transform.0, transform.1));
1979 cx.draw_text(canvas);
1980
1981 if self.edit {
1982 self.draw_selection(cx, canvas);
1983 self.draw_text_caret(cx, canvas);
1984 }
1985
1986 canvas.restore();
1987 }
1988}