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, Point, 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 (mut x, mut y) = (x, y);
684
685 if let Some(inv_transform) = cx.transform().invert() {
686 let point = inv_transform.map_point(Point::new(x, y));
687 x = point.x;
688 y = point.y;
689 }
690
691 x -= self.transform.borrow().0;
692 y -= self.transform.borrow().1;
693
694 let gp = paragraph
695 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
696 let num_graphemes = text.graphemes(true).count();
697 let pos = (gp.position as usize).min(num_graphemes);
698 let mut cursor = text.len();
699 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
700 if pos == i {
701 cursor = j;
702 break;
703 }
704 }
705
706 if selection {
707 self.selection.active = cursor;
708 } else {
709 self.selection = Selection::caret(cursor);
710 }
711
712 cx.needs_redraw();
713 cx.style.needs_access_update(cx.current);
714 }
715 }
716 }
717
718 fn drag(&mut self, cx: &mut EventContext, x: f32, y: f32) {
720 if let Some(text) = cx.style.text.get(cx.current) {
721 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
722 let (mut x, mut y) = (x, y);
723
724 if let Some(inv_transform) = cx.transform().invert() {
725 let point = inv_transform.map_point(Point::new(x, y));
726 x = point.x;
727 y = point.y;
728 }
729
730 x -= self.transform.borrow().0;
731 y -= self.transform.borrow().1;
732
733 let gp = paragraph
734 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
735 let num_graphemes = text.graphemes(true).count();
736 let pos = (gp.position as usize).min(num_graphemes);
737
738 let mut cursor = text.len();
739 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
740 if pos == i {
741 cursor = j;
742 break;
743 }
744 }
745
746 self.selection.active = cursor;
747
748 cx.needs_redraw();
749 cx.style.needs_access_update(cx.current);
750 }
751 }
752 }
753
754 #[cfg(feature = "clipboard")]
758 fn clone_selected(&self, cx: &mut EventContext) -> Option<String> {
759 if let Some(text) = cx.style.text.get(cx.current) {
760 let real_selection =
761 Self::selection_display_to_real(text, &self.real_text, self.selection);
762 let substring = &self.real_text[real_selection.range()];
763 return Some(substring.to_string());
764 }
765
766 None
767 }
768
769 fn clone_text(&self, _cx: &mut EventContext) -> String {
770 if self.show_placeholder.get() {
771 return String::new();
772 }
773
774 self.real_text.clone()
775 }
776
777 fn clamp_insert_text(&self, txt: &str, replace_range: Range<usize>) -> String {
778 let Some(max_length) = self.max_length.get() else {
779 return txt.to_string();
780 };
781
782 let current_len = self.real_text.graphemes(true).count();
783 let replaced_len = self.real_text[replace_range].graphemes(true).count();
784 let preserved_len = current_len.saturating_sub(replaced_len);
785 let remaining = max_length.saturating_sub(preserved_len);
786
787 if remaining == 0 {
788 return String::new();
789 }
790
791 txt.graphemes(true).take(remaining).collect()
792 }
793
794 fn is_text_valid(&self, text: &str) -> bool {
795 if let Ok(value) = text.parse::<T>() {
796 if let Some(validate) = &self.validate { validate(&value) } else { true }
797 } else {
798 false
799 }
800 }
801
802 fn reset_caret_timer(&mut self, cx: &mut EventContext) {
803 cx.stop_timer(self.caret_timer);
804 if !cx.is_read_only() {
805 self.show_caret.set(true);
806 cx.start_timer(self.caret_timer);
807 }
808 }
809
810 fn reset_ime_position(&mut self, cx: &mut EventContext) {
811 cx.event_queue.push_back(
813 Event::new(WindowEvent::SetImeCursorArea(
814 (cx.bounds().x as u32, cx.bounds().y as u32),
815 ((cx.bounds().width()) as u32, cx.bounds().height() as u32),
816 ))
817 .target(cx.current),
818 );
819 }
820
821 fn draw_selection(&self, cx: &mut DrawContext, canvas: &Canvas) {
822 if !self.selection.is_caret() {
823 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
824 if let Some(text) = cx.style.text.get(cx.current) {
825 let min = text.current_grapheme_offset(self.selection.min());
826 let max = text.current_grapheme_offset(self.selection.max());
827
828 let cursor_rects = paragraph.get_rects_for_range(
829 min..max,
830 RectHeightStyle::Tight,
831 RectWidthStyle::Tight,
832 );
833
834 for cursor_rect in cursor_rects {
835 let bounds = cx.bounds();
836
837 let alignment = cx.alignment();
838
839 let (mut top, left) = match alignment {
840 Alignment::TopLeft => (0.0, 0.0),
841 Alignment::TopCenter => (0.0, 0.5),
842 Alignment::TopRight => (0.0, 1.0),
843 Alignment::Left => (0.5, 0.0),
844 Alignment::Center => (0.5, 0.5),
845 Alignment::Right => (0.5, 1.0),
846 Alignment::BottomLeft => (1.0, 0.0),
847 Alignment::BottomCenter => (1.0, 0.5),
848 Alignment::BottomRight => (1.0, 1.0),
849 };
850
851 let padding_top = match cx.padding_top() {
852 Units::Pixels(val) => val,
853 _ => 0.0,
854 };
855
856 let padding_bottom = match cx.padding_bottom() {
857 Units::Pixels(val) => val,
858 _ => 0.0,
859 };
860
861 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
862
863 let mut padding_left = match cx.padding_left() {
864 Units::Pixels(val) => val,
865 _ => 0.0,
866 };
867
868 let mut padding_right = match cx.padding_right() {
869 Units::Pixels(val) => val,
870 _ => 0.0,
871 };
872
873 if resolved_text_direction(cx.style, cx.current)
874 == crate::style::Direction::RightToLeft
875 {
876 std::mem::swap(&mut padding_left, &mut padding_right);
877 }
878
879 let x = bounds.x + padding_left + cursor_rect.rect.left + left;
880 let y = bounds.y + padding_top + cursor_rect.rect.top + top;
881
882 let x2 = x + (cursor_rect.rect.right - cursor_rect.rect.left);
883 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
884
885 let mut paint = Paint::default();
886 paint.set_anti_alias(true);
887 paint.set_style(PaintStyle::Fill);
888 paint.set_color(cx.selection_color());
889
890 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
891 }
892 }
893 }
894 }
895 }
896
897 pub fn draw_text_caret(&self, cx: &mut DrawContext, canvas: &Canvas) {
899 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
900 if let Some(text) = cx.style.text.get(cx.current) {
901 let bounds = cx.bounds();
902
903 let current = text.current_grapheme_offset(self.selection.active);
904
905 let grapheme_count = text.graphemes(true).count();
906 let (range_start, range_end, use_trailing_edge) = if current < grapheme_count {
907 (current, current + 1, false)
908 } else if current > 0 {
909 (current - 1, current, true)
911 } else {
912 (0, 1, false)
914 };
915
916 let rects = paragraph.get_rects_for_range(
917 range_start..range_end,
918 RectHeightStyle::Tight,
919 RectWidthStyle::Tight,
920 );
921
922 let Some(cursor_rect) = rects.first() else {
923 return;
924 };
925
926 let alignment = cx.alignment();
927
928 let (mut top, _) = match alignment {
929 Alignment::TopLeft => (0.0, 0.0),
930 Alignment::TopCenter => (0.0, 0.5),
931 Alignment::TopRight => (0.0, 1.0),
932 Alignment::Left => (0.5, 0.0),
933 Alignment::Center => (0.5, 0.5),
934 Alignment::Right => (0.5, 1.0),
935 Alignment::BottomLeft => (1.0, 0.0),
936 Alignment::BottomCenter => (1.0, 0.5),
937 Alignment::BottomRight => (1.0, 1.0),
938 };
939
940 let padding_top = match cx.padding_top() {
941 Units::Pixels(val) => val,
942 _ => 0.0,
943 };
944
945 let padding_bottom = match cx.padding_bottom() {
946 Units::Pixels(val) => val,
947 _ => 0.0,
948 };
949
950 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
951
952 let mut padding_left = match cx.padding_left() {
953 Units::Pixels(val) => val,
954 _ => 0.0,
955 };
956
957 let mut padding_right = match cx.padding_right() {
958 Units::Pixels(val) => val,
959 _ => 0.0,
960 };
961
962 if resolved_text_direction(cx.style, cx.current)
963 == crate::style::Direction::RightToLeft
964 {
965 std::mem::swap(&mut padding_left, &mut padding_right);
966 }
967
968 let caret_x =
969 if use_trailing_edge { cursor_rect.rect.right } else { cursor_rect.rect.left };
970
971 let x = (bounds.x + padding_left + caret_x).round();
972 let y = (bounds.y + padding_top + cursor_rect.rect.top + top).round();
973
974 let x2 = x + 1.0;
975 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
976
977 let mut paint = Paint::default();
978 paint.set_anti_alias(true);
979 paint.set_style(PaintStyle::Fill);
980 paint.set_color(cx.caret_color());
981
982 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
983
984 let mut transform = self.transform.borrow_mut();
985
986 let text_bounds = BoundingBox::from_min_max(
987 bounds.x + padding_left,
988 bounds.y + padding_top + top,
989 bounds.x + padding_left + paragraph.max_intrinsic_width(),
990 bounds.y + padding_top + top + paragraph.height(),
991 );
992
993 let mut bounds = bounds;
994
995 bounds =
996 bounds.shrink_sides(padding_left, padding_top, padding_right, padding_bottom);
997
998 let (tx, ty) =
999 enforce_text_bounds(&text_bounds, &bounds, (transform.0, transform.1));
1000
1001 let caret_box = BoundingBox::from_min_max(x, y, x2, y2);
1002
1003 let (new_tx, new_ty) = ensure_visible(&caret_box, &bounds, (tx, ty));
1004
1005 if new_tx != transform.0 || new_ty != transform.1 {
1006 *transform = (new_tx, new_ty);
1007 cx.needs_redraw();
1008 }
1009 }
1010 }
1011 }
1012}
1013
1014impl<R, T> Handle<'_, Textbox<R, T>>
1015where
1016 R: Res<T> + 'static,
1017 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1018{
1019 pub fn on_edit<F>(self, callback: F) -> Self
1023 where
1024 F: 'static + Fn(&mut EventContext, String) + Send + Sync,
1025 {
1026 self.modify(|textbox| textbox.on_edit = Some(Box::new(callback)))
1027 }
1028
1029 pub fn on_submit<F>(self, callback: F) -> Self
1034 where
1035 F: 'static + Fn(&mut EventContext, T, bool) + Send + Sync,
1036 {
1037 self.modify(|textbox| textbox.on_submit = Some(Box::new(callback)))
1038 }
1039
1040 pub fn on_blur<F>(self, callback: F) -> Self
1042 where
1043 F: 'static + Fn(&mut EventContext) + Send + Sync,
1044 {
1045 self.modify(|textbox| textbox.on_blur = Some(Box::new(callback)))
1046 }
1047
1048 pub fn on_cancel<F>(self, callback: F) -> Self
1050 where
1051 F: 'static + Fn(&mut EventContext) + Send + Sync,
1052 {
1053 self.modify(|textbox| textbox.on_cancel = Some(Box::new(callback)))
1054 }
1055
1056 pub fn validate<F>(self, is_valid: F) -> Self
1060 where
1061 F: 'static + Fn(&T) -> bool + Send + Sync,
1062 {
1063 self.modify(|textbox| textbox.validate = Some(Box::new(is_valid)))
1064 }
1065
1066 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
1068 self,
1069 text: impl Res<P> + 'static,
1070 ) -> Self {
1071 let text = text.to_signal(self.cx);
1072 self.bind(text, move |mut handle| {
1073 let text = text.get();
1074 let txt = text.to_string_local(&handle);
1075 let entity = handle.entity();
1076 handle = handle.modify(|textbox| textbox.placeholder.set(txt));
1077 handle.context().style.needs_access_update(entity);
1078 })
1079 }
1080
1081 pub fn mask_char<U: Into<Option<char>> + Clone + 'static>(
1085 self,
1086 mask: impl Res<U> + 'static,
1087 ) -> Self {
1088 let mask = mask.to_signal(self.cx);
1089 self.bind(mask, move |mut handle| {
1090 let entity = handle.entity();
1091 let new_mask = mask.get().into();
1092 let mut display_text = String::new();
1093 handle = handle.modify(|textbox| {
1094 let old_display = textbox.display_text_from_real();
1095 textbox.mask_char.set_if_changed(new_mask);
1096 let new_display = textbox.display_text_from_real();
1097 textbox.remap_selection_for_display_change(&old_display, &new_display);
1098 display_text = new_display;
1099 });
1100 handle = handle.text(display_text);
1101 handle.context().style.needs_text_update(entity);
1102 handle.context().needs_redraw(entity);
1103 })
1104 }
1105
1106 pub fn mask_visible<U: Into<bool> + Clone + 'static>(
1108 self,
1109 visible: impl Res<U> + 'static,
1110 ) -> Self {
1111 let visible = visible.to_signal(self.cx);
1112 self.bind(visible, move |mut handle| {
1113 let entity = handle.entity();
1114 let new_visible = visible.get().into();
1115 let mut display_text = String::new();
1116 let mut changed = false;
1117
1118 handle = handle.modify(|textbox| {
1119 if textbox.mask_visible != new_visible {
1120 let old_display = textbox.display_text_from_real();
1121 textbox.mask_visible = new_visible;
1122 let new_display = textbox.display_text_from_real();
1123 textbox.remap_selection_for_display_change(&old_display, &new_display);
1124 display_text = new_display;
1125 changed = true;
1126 }
1127 });
1128
1129 if changed {
1130 handle = handle.text(display_text);
1131 handle.context().style.needs_text_update(entity);
1132 handle.context().needs_redraw(entity);
1133 }
1134 })
1135 }
1136
1137 pub fn max_length<U: Into<Option<usize>> + Clone + 'static>(
1141 self,
1142 max_length: impl Res<U> + 'static,
1143 ) -> Self {
1144 let max_length = max_length.to_signal(self.cx);
1145 self.bind(max_length, move |handle| {
1146 let value = max_length.get().into();
1147 handle.modify(|textbox| textbox.max_length.set_if_changed(value));
1148 })
1149 }
1150
1151 pub fn can_copy<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1153 let state = state.to_signal(self.cx);
1154 self.bind(state, move |handle| {
1155 let value = state.get().into();
1156 handle.modify(|textbox| textbox.can_copy.set_if_changed(value));
1157 })
1158 }
1159
1160 pub fn can_paste<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1162 let state = state.to_signal(self.cx);
1163 self.bind(state, move |handle| {
1164 let value = state.get().into();
1165 handle.modify(|textbox| textbox.can_paste.set_if_changed(value));
1166 })
1167 }
1168}
1169
1170fn byte_offset_to_char_index(character_lengths: &[u8], byte_offset: usize) -> usize {
1173 let mut cumulative = 0;
1174 for (i, &len) in character_lengths.iter().enumerate() {
1175 cumulative += len as usize;
1176 if byte_offset < cumulative {
1177 return i;
1178 }
1179 }
1180 character_lengths.len()
1181}
1182
1183impl<R, T> View for Textbox<R, T>
1184where
1185 R: Res<T> + 'static,
1186 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1187{
1188 fn element(&self) -> Option<&'static str> {
1189 Some("textbox")
1190 }
1191
1192 fn accessibility(&self, cx: &mut AccessContext, node: &mut AccessNode) {
1193 if !self.placeholder.get().is_empty() {
1194 node.set_placeholder(self.placeholder.get().clone());
1195 }
1196
1197 let node_id = node.node_id();
1198
1199 let selection = self.selection;
1200
1201 let mut selection_active_line = None;
1202 let mut selection_anchor_line = None;
1203 let mut selection_active_cursor = 0;
1204 let mut selection_anchor_cursor = 0;
1205 let mut first_line_node_id = None;
1206
1207 let text = if self.show_placeholder.get() {
1208 ""
1209 } else {
1210 cx.style.text.get(cx.current).map(|t| t.as_str()).unwrap_or("")
1211 };
1212 let text_len = text.len();
1216
1217 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
1218 let text_direction = if resolved_text_direction(cx.style, cx.current)
1219 == crate::style::Direction::RightToLeft
1220 {
1221 TextDirection::RightToLeft
1222 } else {
1223 TextDirection::LeftToRight
1224 };
1225
1226 let line_metrics = paragraph.get_line_metrics();
1227 for line in line_metrics.iter() {
1228 if line.start_index >= text_len && text_len > 0 {
1230 continue;
1231 }
1232
1233 let mut line_node = AccessNode::new_from_parent(node_id, line.line_number);
1235 line_node.set_role(Role::TextRun);
1236 line_node.set_text_direction(text_direction);
1237 line_node.set_bounds(BoundingBox {
1238 x: line.left as f32,
1239 y: (line.baseline - line.ascent) as f32,
1240 w: line.width as f32,
1241 h: line.height as f32,
1242 });
1243
1244 let glyph_end = line.end_index.min(text_len);
1246 if line.start_index > glyph_end {
1247 continue;
1248 }
1249 let estimated_chars = glyph_end.saturating_sub(line.start_index);
1250 let mut character_lengths: Vec<u8> = Vec::with_capacity(estimated_chars);
1251 let mut character_positions: Vec<f32> = Vec::with_capacity(estimated_chars);
1252 let mut character_widths: Vec<f32> = Vec::with_capacity(estimated_chars);
1253 let mut glyph_pos = line.start_index;
1254
1255 while glyph_pos < glyph_end {
1256 if let Some(cluster_info) = paragraph.get_glyph_cluster_at(glyph_pos) {
1257 let length = cluster_info.text_range.end - cluster_info.text_range.start;
1258 if length == 0 {
1259 break;
1260 }
1261
1262 character_lengths.push(length as u8);
1263 character_positions.push(cluster_info.bounds.left());
1264 character_widths.push(cluster_info.bounds.width());
1265
1266 glyph_pos += length;
1267 } else {
1268 break;
1269 }
1270 }
1271
1272 let mut line_end = if line.hard_break {
1274 line.end_including_newline.min(text_len)
1275 } else {
1276 glyph_end
1277 };
1278 if line_end < line.start_index {
1279 line_end = line.start_index;
1280 }
1281 let line_text = text.get(line.start_index..line_end).unwrap_or("").to_owned();
1282
1283 if line.hard_break && line.end_including_newline <= text_len {
1284 character_lengths.push(1);
1285 character_positions.push(line.width as f32);
1286 character_widths.push(0.0);
1287 }
1288
1289 let mut word_starts = Vec::new();
1290 let mut previous_is_alphanumeric = text
1291 .get(..line.start_index)
1292 .and_then(|prefix| prefix.graphemes(true).next_back())
1293 .and_then(|grapheme| grapheme.chars().next())
1294 .is_some_and(|ch| ch.is_alphanumeric());
1295
1296 for (character_index, grapheme) in line_text.graphemes(true).enumerate() {
1297 let current_is_alphanumeric =
1298 grapheme.chars().next().is_some_and(|ch| ch.is_alphanumeric());
1299
1300 if current_is_alphanumeric
1301 && !previous_is_alphanumeric
1302 && let Ok(character_index) = u8::try_from(character_index)
1303 {
1304 word_starts.push(character_index);
1305 }
1306
1307 previous_is_alphanumeric = current_is_alphanumeric;
1308 }
1309
1310 if first_line_node_id.is_none() {
1311 first_line_node_id = Some(line_node.node_id());
1312 }
1313
1314 if selection.active >= line.start_index && selection.active <= line_end {
1316 selection_active_line = Some(line_node.node_id());
1317 selection_active_cursor = byte_offset_to_char_index(
1318 &character_lengths,
1319 selection.active - line.start_index,
1320 );
1321 }
1322
1323 if selection.anchor >= line.start_index && selection.anchor <= line_end {
1325 selection_anchor_line = Some(line_node.node_id());
1326 selection_anchor_cursor = byte_offset_to_char_index(
1327 &character_lengths,
1328 selection.anchor - line.start_index,
1329 );
1330 }
1331
1332 line_node.set_value(line_text.into_boxed_str());
1333 line_node.set_character_lengths(character_lengths.into_boxed_slice());
1334 line_node.set_character_positions(character_positions.into_boxed_slice());
1335 line_node.set_character_widths(character_widths.into_boxed_slice());
1336 line_node.set_word_starts(word_starts.into_boxed_slice());
1337
1338 node.add_child(line_node);
1339 }
1340 }
1341
1342 if let Some(fallback) = first_line_node_id {
1343 node.set_text_selection(TextSelection {
1344 anchor: TextPosition {
1345 node: selection_anchor_line.unwrap_or(fallback),
1346 character_index: selection_anchor_cursor,
1347 },
1348 focus: TextPosition {
1349 node: selection_active_line.unwrap_or(fallback),
1350 character_index: selection_active_cursor,
1351 },
1352 });
1353 }
1354 }
1355
1356 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
1357 event.map(|window_event, meta| match window_event {
1359 WindowEvent::MouseDown(MouseButton::Left) => {
1360 if meta.origin == cx.current {
1361 return;
1362 }
1363
1364 if cx.is_over() {
1365 if !cx.is_disabled() {
1366 cx.focus_with_visibility(true);
1367 cx.capture();
1368 cx.lock_cursor_icon();
1369
1370 if !self.edit {
1371 cx.emit(TextEvent::StartEdit);
1372 }
1373 self.reset_caret_timer(cx);
1374 cx.emit(TextEvent::Hit(
1375 cx.mouse.cursor_x,
1376 cx.mouse.cursor_y,
1377 cx.modifiers.shift(),
1378 ));
1379 }
1380 } else {
1381 cx.release();
1382
1383 cx.event_queue.push_back(
1385 Event::new(WindowEvent::MouseDown(MouseButton::Left)).target(cx.hovered()),
1386 );
1387 cx.event_queue.push_back(
1388 Event::new(WindowEvent::PressDown { mouse: true }).target(cx.hovered()),
1389 );
1390 }
1391 }
1392
1393 WindowEvent::FocusIn => {
1394 if cx.mouse.left.pressed != cx.current()
1395 || cx.mouse.left.state == MouseButtonState::Released
1396 {
1397 cx.emit(TextEvent::StartEdit);
1398 }
1399 }
1400
1401 WindowEvent::FocusOut => {
1402 cx.emit(TextEvent::EndEdit);
1403 }
1404
1405 WindowEvent::MouseDoubleClick(MouseButton::Left) => {
1406 cx.emit(TextEvent::SelectWord);
1407 }
1408
1409 WindowEvent::MouseTripleClick(MouseButton::Left) => {
1410 cx.emit(TextEvent::SelectParagraph);
1411 }
1412
1413 WindowEvent::MouseUp(MouseButton::Left) => {
1414 self.reset_caret_timer(cx);
1415 cx.unlock_cursor_icon();
1416 cx.release();
1417 }
1418
1419 WindowEvent::MouseMove(x, y) => {
1420 if cx.mouse.left.state == MouseButtonState::Pressed
1421 && cx.mouse.left.pressed == cx.current
1422 {
1423 if self.edit {
1424 self.reset_caret_timer(cx);
1425 }
1426 if cx.mouse.left.pos_down.0 != *x || cx.mouse.left.pos_down.1 != *y {
1427 cx.emit(TextEvent::Drag(*x, *y));
1428 }
1429 }
1430 }
1431
1432 WindowEvent::MouseScroll(x, y) => {
1433 cx.emit(TextEvent::Scroll(*x, *y));
1434 }
1435
1436 WindowEvent::CharInput(c) => {
1437 if *c != '\u{1b}' && *c != '\u{8}' && *c != '\u{9}' && *c != '\u{7f}' && *c != '\u{0d}' && !cx.modifiers.ctrl() &&
1443 !cx.modifiers.logo() &&
1444 self.edit &&
1445 !cx.is_read_only()
1446 {
1447 self.reset_caret_timer(cx);
1448 cx.emit(TextEvent::InsertText(String::from(*c)));
1449 }
1450 }
1451
1452 WindowEvent::ImeCommit(text) => {
1453 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1454 self.reset_caret_timer(cx);
1455 cx.emit(TextEvent::ClearPreedit);
1456 cx.emit(TextEvent::InsertText(text.to_string()));
1457
1458 self.reset_ime_position(cx);
1459 }
1460 }
1461
1462 WindowEvent::ImePreedit(text, cursor) => {
1463 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1464 self.reset_caret_timer(cx);
1465 cx.emit(TextEvent::UpdatePreedit(text.to_string(), *cursor));
1466 }
1467 }
1468
1469 WindowEvent::KeyDown(code, _) => match code {
1470 Code::Enter => {
1471 if matches!(self.kind, TextboxKind::SingleLine) {
1472 cx.emit(TextEvent::Submit(true));
1473 } else if !cx.is_read_only() {
1474 self.reset_caret_timer(cx);
1475 cx.emit(TextEvent::InsertText("\n".to_owned()));
1476 }
1477 }
1478
1479 Code::ArrowLeft => {
1486 self.reset_caret_timer(cx);
1487 #[cfg(target_os = "macos")]
1491 let movement = if cx.modifiers.logo() {
1492 Movement::LineStart
1493 } else if cx.modifiers.alt() {
1494 Movement::Word(Direction::Left)
1495 } else {
1496 Movement::Grapheme(Direction::Left)
1497 };
1498 #[cfg(not(target_os = "macos"))]
1499 let movement = if cx.modifiers.ctrl() {
1500 Movement::Word(Direction::Left)
1501 } else {
1502 Movement::Grapheme(Direction::Left)
1503 };
1504
1505 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1506 }
1507
1508 Code::ArrowRight => {
1509 self.reset_caret_timer(cx);
1510
1511 #[cfg(target_os = "macos")]
1512 let movement = if cx.modifiers.logo() {
1513 Movement::LineEnd
1514 } else if cx.modifiers.alt() {
1515 Movement::Word(Direction::Right)
1516 } else {
1517 Movement::Grapheme(Direction::Right)
1518 };
1519 #[cfg(not(target_os = "macos"))]
1520 let movement = if cx.modifiers.ctrl() {
1521 Movement::Word(Direction::Right)
1522 } else {
1523 Movement::Grapheme(Direction::Right)
1524 };
1525
1526 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1527 }
1528
1529 Code::ArrowUp => {
1530 self.reset_caret_timer(cx);
1531 if self.kind != TextboxKind::SingleLine {
1532 cx.emit(TextEvent::MoveCursor(
1533 Movement::Vertical(VerticalMovement::LineUp),
1534 cx.modifiers.shift(),
1535 ));
1536 }
1537 }
1538
1539 Code::ArrowDown => {
1540 self.reset_caret_timer(cx);
1541 if self.kind != TextboxKind::SingleLine {
1542 cx.emit(TextEvent::MoveCursor(
1543 Movement::Vertical(VerticalMovement::LineDown),
1544 cx.modifiers.shift(),
1545 ));
1546 }
1547 }
1548
1549 Code::Backspace => {
1550 self.reset_caret_timer(cx);
1551 if !cx.is_read_only() {
1552 #[cfg(target_os = "macos")]
1553 let movement = if cx.modifiers.logo() {
1554 Movement::LineStart
1558 } else if cx.modifiers.alt() {
1559 Movement::Word(Direction::Upstream)
1560 } else {
1561 Movement::Grapheme(Direction::Upstream)
1562 };
1563 #[cfg(not(target_os = "macos"))]
1564 let movement = if cx.modifiers.ctrl() {
1565 Movement::Word(Direction::Upstream)
1566 } else {
1567 Movement::Grapheme(Direction::Upstream)
1568 };
1569
1570 cx.emit(TextEvent::DeleteText(movement));
1571 }
1572 }
1573
1574 Code::Delete => {
1575 self.reset_caret_timer(cx);
1576 if !cx.is_read_only() {
1577 #[cfg(target_os = "macos")]
1578 let movement = if cx.modifiers.alt() {
1579 Movement::Word(Direction::Downstream)
1580 } else {
1581 Movement::Grapheme(Direction::Downstream)
1582 };
1583 #[cfg(not(target_os = "macos"))]
1584 let movement = if cx.modifiers.ctrl() {
1585 Movement::Word(Direction::Downstream)
1586 } else {
1587 Movement::Grapheme(Direction::Downstream)
1588 };
1589
1590 cx.emit(TextEvent::DeleteText(movement));
1591 }
1592 }
1593
1594 Code::Escape => {
1595 if let Some(callback) = &self.on_cancel {
1596 (callback)(cx);
1597 } else {
1598 cx.emit(TextEvent::EndEdit);
1599 }
1600 }
1601
1602 Code::Home => {
1603 self.reset_caret_timer(cx);
1604 cx.emit(TextEvent::MoveCursor(Movement::LineStart, cx.modifiers.shift()));
1605 }
1606
1607 Code::End => {
1608 self.reset_caret_timer(cx);
1609 cx.emit(TextEvent::MoveCursor(Movement::LineEnd, cx.modifiers.shift()));
1610 }
1611
1612 Code::PageUp | Code::PageDown => {
1613 self.reset_caret_timer(cx);
1614 let direction = if *code == Code::PageUp {
1615 Direction::Upstream
1616 } else {
1617 Direction::Downstream
1618 };
1619 cx.emit(TextEvent::MoveCursor(
1620 if cx.modifiers.ctrl() {
1621 Movement::Body(direction)
1622 } else {
1623 Movement::Page(direction)
1624 },
1625 cx.modifiers.shift(),
1626 ));
1627 }
1628
1629 Code::KeyA => {
1630 #[cfg(target_os = "macos")]
1631 let modifier = Modifiers::SUPER;
1632 #[cfg(not(target_os = "macos"))]
1633 let modifier = Modifiers::CTRL;
1634
1635 if cx.modifiers == &modifier {
1636 cx.emit(TextEvent::SelectAll);
1637 }
1638 }
1639
1640 Code::KeyC => {
1641 #[cfg(target_os = "macos")]
1642 let modifier = Modifiers::SUPER;
1643 #[cfg(not(target_os = "macos"))]
1644 let modifier = Modifiers::CTRL;
1645
1646 if cx.modifiers == &modifier && self.can_copy.get() {
1647 cx.emit(TextEvent::Copy);
1648 }
1649 }
1650
1651 Code::KeyV => {
1652 #[cfg(target_os = "macos")]
1653 let modifier = Modifiers::SUPER;
1654 #[cfg(not(target_os = "macos"))]
1655 let modifier = Modifiers::CTRL;
1656
1657 if cx.modifiers == &modifier && self.can_paste.get() {
1658 cx.emit(TextEvent::Paste);
1659 }
1660 }
1661
1662 Code::KeyX => {
1663 #[cfg(target_os = "macos")]
1664 let modifier = Modifiers::SUPER;
1665 #[cfg(not(target_os = "macos"))]
1666 let modifier = Modifiers::CTRL;
1667
1668 if cx.modifiers == &modifier && !cx.is_read_only() {
1669 cx.emit(TextEvent::Cut);
1670 }
1671 }
1672
1673 _ => {}
1674 },
1675
1676 WindowEvent::ActionRequest(ActionRequest {
1677 action: accesskit::Action::SetTextSelection,
1678 target_tree: _,
1679 target_node: _,
1680 data: Some(ActionData::SetTextSelection(_selection)),
1681 }) => {
1682 }
1684
1685 _ => {}
1686 });
1687
1688 event.map(|text_event, _| match text_event {
1690 TextEvent::InsertText(text) => {
1691 if self.preedit_backup.is_some() {
1692 return;
1693 }
1694
1695 self.edited_since_focus = true;
1696 self.edited_once = true;
1697
1698 if self.show_placeholder.get() {
1699 self.reset_text(cx);
1700 }
1701
1702 self.insert_text(cx, text);
1703
1704 let text = self.clone_text(cx);
1705
1706 cx.set_valid(self.is_text_valid(&text));
1707
1708 if self.edit {
1709 if let Some(callback) = &self.on_edit {
1710 (callback)(cx, text);
1711 }
1712 }
1713 }
1714
1715 TextEvent::UpdatePreedit(preedit, cursor) => {
1716 self.update_preedit(cx, preedit, *cursor);
1717 }
1718
1719 TextEvent::ClearPreedit => {
1720 self.clear_preedit(cx);
1721 }
1722
1723 TextEvent::Clear => {
1724 self.reset_text(cx);
1725 cx.needs_relayout();
1727 cx.needs_redraw();
1728 }
1729
1730 TextEvent::DeleteText(movement) => {
1731 if self.edit {
1732 self.edited_since_focus = true;
1733 self.edited_once = true;
1734 self.delete_text(cx, *movement);
1735
1736 let text = self.clone_text(cx);
1737
1738 cx.set_valid(self.is_text_valid(&text));
1739
1740 if let Some(callback) = &self.on_edit {
1741 (callback)(cx, text);
1742 }
1743 }
1744 }
1745
1746 TextEvent::MoveCursor(movement, selection) => {
1747 if self.edit && !self.show_placeholder.get() && self.preedit_backup.is_none() {
1748 self.move_cursor(cx, *movement, *selection);
1749 }
1750 }
1751
1752 TextEvent::SetPlaceholder(text) => {
1753 self.placeholder.set(text.clone());
1754 cx.style.needs_access_update(cx.current);
1755 }
1756
1757 TextEvent::StartEdit => {
1758 if !cx.is_disabled() && !self.edit {
1759 self.edit = true;
1760 self.edited_since_focus = false;
1761 cx.focus_with_visibility(true);
1762 cx.capture();
1763 self.reset_caret_timer(cx);
1764 self.reset_ime_position(cx);
1765
1766 self.text_overflow = cx.style.text_overflow.get_inline(cx.current).copied();
1767 cx.style.text_overflow.remove(cx.current);
1768
1769 let text = self.value.get_value(cx);
1770 let text = text.to_string_local(cx);
1771 self.real_text = text.clone();
1772
1773 if text.is_empty() {
1774 self.show_placeholder.set(true);
1775 self.selection = Selection::caret(0);
1776 cx.style.needs_access_update(cx.current);
1777 } else {
1778 self.show_placeholder.set(false);
1779 self.select_all(cx);
1780 }
1781
1782 if self.edited_once || !text.is_empty() {
1785 cx.set_valid(self.is_text_valid(&text));
1786 } else {
1787 cx.set_valid(true);
1788 }
1789 }
1790
1791 self.sync_display_text(cx);
1792 }
1793
1794 TextEvent::EndEdit => {
1795 self.deselect();
1796 self.edit = false;
1797 cx.release();
1798 cx.stop_timer(self.caret_timer);
1799
1800 let text = self.clone_text(cx);
1801 self.show_placeholder.set(text.is_empty());
1802
1803 if let Some(text_overflow) = self.text_overflow {
1804 cx.style.text_overflow.insert(cx.current, text_overflow);
1805 } else {
1806 cx.style.text_overflow.remove(cx.current);
1807 }
1808
1809 self.select_all(cx);
1810
1811 if self.edited_since_focus {
1812 cx.set_valid(self.is_text_valid(&text));
1813 }
1814
1815 let mut transform = self.transform.borrow_mut();
1817 *transform = (0.0, 0.0);
1818
1819 self.selection = Selection::caret(0);
1821
1822 self.sync_display_text(cx);
1823 cx.style.needs_access_update(cx.current);
1824 }
1825
1826 TextEvent::Blur => {
1827 if cx.focused() == cx.current() {
1831 cx.focus_with_visibility(false);
1832 }
1833
1834 if let Some(callback) = &self.on_blur {
1835 (callback)(cx);
1836 } else {
1837 cx.emit(TextEvent::Submit(false));
1838 cx.emit(TextEvent::EndEdit);
1839 }
1840 }
1841
1842 TextEvent::SetMaskVisible(visible) => {
1843 if self.mask_visible != *visible {
1844 let old_display = self.display_text_from_real();
1845 self.mask_visible = *visible;
1846 let new_display = self.display_text_from_real();
1847 self.remap_selection_for_display_change(&old_display, &new_display);
1848 self.sync_display_text(cx);
1849 cx.needs_redraw();
1850 }
1851 }
1852
1853 TextEvent::ToggleMaskVisible => {
1854 let old_display = self.display_text_from_real();
1855 self.mask_visible = !self.mask_visible;
1856 let new_display = self.display_text_from_real();
1857 self.remap_selection_for_display_change(&old_display, &new_display);
1858 self.sync_display_text(cx);
1859 cx.needs_redraw();
1860 }
1861
1862 TextEvent::Submit(reason) => {
1863 if let Some(callback) = &self.on_submit {
1864 let text = self.clone_text(cx);
1865 let is_valid = self.is_text_valid(&text);
1866 cx.set_valid(is_valid);
1867 if is_valid && let Ok(value) = text.parse::<T>() {
1868 (callback)(cx, value, *reason);
1869 }
1870 }
1871 }
1872
1873 TextEvent::SelectAll => {
1874 self.select_all(cx);
1875 }
1876
1877 TextEvent::SelectWord => {
1878 self.select_word(cx);
1879 }
1880
1881 TextEvent::SelectParagraph => {
1882 self.select_paragraph(cx);
1883 }
1884
1885 TextEvent::Hit(posx, posy, selection) => {
1886 if !self.show_placeholder.get() {
1887 self.hit(cx, *posx, *posy, *selection);
1888 }
1889 }
1890
1891 TextEvent::Drag(posx, posy) => {
1892 if !self.show_placeholder.get() {
1893 self.drag(cx, *posx, *posy);
1894 }
1895 }
1896
1897 TextEvent::Scroll(_x, _y) => {
1898 }
1900
1901 TextEvent::Copy =>
1902 {
1903 #[cfg(feature = "clipboard")]
1904 if self.edit && self.can_copy.get() {
1905 if let Some(selected_text) = self.clone_selected(cx) {
1906 if !selected_text.is_empty() {
1907 cx.set_clipboard(selected_text)
1908 .expect("Failed to add text to clipboard");
1909 }
1910 }
1911 }
1912 }
1913
1914 TextEvent::Paste =>
1915 {
1916 #[cfg(feature = "clipboard")]
1917 if self.edit && self.can_paste.get() {
1918 if let Ok(text) = cx.get_clipboard() {
1919 cx.emit(TextEvent::InsertText(text));
1920 }
1921 }
1922 }
1923
1924 TextEvent::Cut =>
1925 {
1926 #[cfg(feature = "clipboard")]
1927 if self.edit {
1928 if let Some(selected_text) = self.clone_selected(cx) {
1929 if !selected_text.is_empty() {
1930 self.edited_since_focus = true;
1931 self.edited_once = true;
1932 cx.set_clipboard(selected_text)
1933 .expect("Failed to add text to clipboard");
1934 self.delete_text(cx, Movement::Grapheme(Direction::Upstream));
1935
1936 let text = self.clone_text(cx);
1937
1938 cx.set_valid(self.is_text_valid(&text));
1939
1940 if let Some(callback) = &self.on_edit {
1941 (callback)(cx, text);
1942 }
1943 }
1944 }
1945 }
1946 }
1947
1948 TextEvent::ToggleCaret => {
1949 self.show_caret ^= true;
1950 }
1951 });
1952 }
1953
1954 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
1956 cx.draw_shadows(canvas);
1957 cx.draw_background(canvas);
1958 cx.draw_border(canvas);
1959 cx.draw_outline(canvas);
1960
1961 canvas.save();
1964 let path = cx.path();
1965 canvas.clip_path(&path, ClipOp::Intersect, true);
1966
1967 let bounds = cx.bounds();
1968 let padding_left = match cx.padding_left() {
1969 Units::Pixels(val) => val,
1970 _ => 0.0,
1971 };
1972 let padding_right = match cx.padding_right() {
1973 Units::Pixels(val) => val,
1974 _ => 0.0,
1975 };
1976 let padding_top = match cx.padding_top() {
1977 Units::Pixels(val) => val,
1978 _ => 0.0,
1979 };
1980 let padding_bottom = match cx.padding_bottom() {
1981 Units::Pixels(val) => val,
1982 _ => 0.0,
1983 };
1984
1985 let content_left = bounds.x + padding_left;
1986 let content_top = bounds.y + padding_top;
1987 let content_right = (bounds.x + bounds.w - padding_right).max(content_left);
1988 let content_bottom = (bounds.y + bounds.h - padding_bottom).max(content_top);
1989 canvas.clip_rect(
1990 Rect::new(content_left, content_top, content_right, content_bottom),
1991 ClipOp::Intersect,
1992 true,
1993 );
1994
1995 let transform = *self.transform.borrow();
1996 canvas.translate((transform.0, transform.1));
1997 cx.draw_text(canvas);
1998
1999 if self.edit {
2000 self.draw_selection(cx, canvas);
2001 self.draw_text_caret(cx, canvas);
2002 }
2003
2004 canvas.restore();
2005 }
2006}