1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::prelude::*;
5
6use crate::text::{
7 Direction, EditableText, Movement, PreeditBackup, Selection, VerticalMovement, apply_movement,
8 enforce_text_bounds, ensure_visible, offset_for_delete_backwards, resolved_text_direction,
9};
10use accesskit::{ActionData, ActionRequest, TextDirection, TextPosition, TextSelection};
11use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
12use skia_safe::{Paint, PaintStyle, Rect};
13use unicode_segmentation::UnicodeSegmentation;
14
15pub enum TextEvent {
17 InsertText(String),
19 UpdatePreedit(String, Option<(usize, usize)>),
21 ClearPreedit,
23 Clear,
25 DeleteText(Movement),
27 MoveCursor(Movement, bool),
29 SelectAll,
31 SelectWord,
33 SelectParagraph,
35 StartEdit,
37 EndEdit,
39 Submit(bool),
41 Hit(f32, f32, bool),
43 Drag(f32, f32),
45 Scroll(f32, f32),
47 Copy,
49 Paste,
51 Cut,
53 SetPlaceholder(String),
55 Blur,
57 ToggleCaret,
59}
60
61pub struct Textbox<R, T> {
68 value: R,
69 kind: TextboxKind,
70 edit: bool,
71 transform: Rc<RefCell<(f32, f32)>>,
72 on_edit: Option<Box<dyn Fn(&mut EventContext, String) + Send + Sync>>,
73 on_submit: Option<Box<dyn Fn(&mut EventContext, T, bool) + Send + Sync>>,
74 on_blur: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
75 on_cancel: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
76 validate: Option<Box<dyn Fn(&T) -> bool>>,
77 placeholder: Signal<String>,
78 show_placeholder: Signal<bool>,
79 show_caret: Signal<bool>,
80 caret_timer: Timer,
81 selection: Selection,
82 preedit_backup: Option<PreeditBackup>,
83 text_overflow: Option<TextOverflow>,
84}
85
86#[derive(Copy, Clone, PartialEq, Eq)]
88enum TextboxKind {
89 SingleLine,
90 MultiLineUnwrapped,
91 MultiLineWrapped,
92}
93
94impl<R, T> Textbox<R, T>
95where
96 R: Res<T> + 'static,
97 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
98{
99 pub fn new(cx: &mut Context, value: R) -> Handle<Self>
119 where
120 R: Clone,
121 {
122 Self::new_core(cx, value, TextboxKind::SingleLine)
123 }
124
125 pub fn new_multiline(cx: &mut Context, value: R, wrap: bool) -> Handle<Self>
149 where
150 R: Clone,
151 {
152 Self::new_core(
153 cx,
154 value,
155 if wrap { TextboxKind::MultiLineWrapped } else { TextboxKind::MultiLineUnwrapped },
156 )
157 }
158
159 fn new_core(cx: &mut Context, value: R, kind: TextboxKind) -> Handle<Self>
160 where
161 R: Clone,
162 {
163 let value_text = value.clone().to_signal(cx);
164 let caret_timer = cx.environment().caret_timer;
165 let initial_text = value.get_value(cx).to_string_local(cx);
166 let show_caret = Signal::new(false);
167 let placeholder = Signal::new(String::from(""));
168 let show_placeholder = Signal::new(initial_text.is_empty());
169
170 Self {
171 value: value.clone(),
172 kind,
173 edit: false,
174 transform: Rc::new(RefCell::new((0.0, 0.0))),
175 on_edit: None,
176 on_submit: None,
177 on_blur: None,
178 on_cancel: None,
179 validate: None,
180 placeholder,
181 show_placeholder,
182 show_caret,
183 caret_timer,
184 selection: Selection::new(0, 0),
185 preedit_backup: None,
186 text_overflow: None,
187 }
188 .build(cx, move |cx| {
189 cx.add_listener(move |textbox: &mut Self, cx, event| {
190 let flag: bool = textbox.edit;
191 event.map(|window_event, meta| match window_event {
192 WindowEvent::MouseDown(_) => {
193 if flag && meta.origin != cx.current() && cx.hovered() != cx.current() {
194 cx.emit(TextEvent::Blur);
195 }
196 }
197
198 _ => {}
199 });
200 });
201 })
202 .toggle_class("multiline", kind == TextboxKind::MultiLineWrapped)
203 .text_wrap(kind == TextboxKind::MultiLineWrapped)
204 .navigable(true)
205 .role(if kind == TextboxKind::SingleLine {
206 Role::TextInput
207 } else {
208 Role::MultilineTextInput
209 })
210 .text_value(value.clone())
211 .toggle_class("caret", show_caret)
212 .placeholder_shown(show_placeholder)
213 .bind(value_text, move |handle| {
214 handle.bind(placeholder, move |handle| {
215 let text = value_text.get();
216 let txt = text.to_string_local(&handle);
217 let handle = handle.modify(|textbox| {
218 textbox.show_placeholder.set_if_changed(txt.is_empty());
219 });
220 let placeholder_text = placeholder.get().to_string_local(&handle);
221
222 if show_placeholder.get() {
223 handle.text(placeholder_text);
224 } else {
225 handle.text(txt);
226 }
227 });
228 })
229 }
230
231 fn insert_text(&mut self, cx: &mut EventContext, txt: &str) {
232 if let Some(text) = cx.style.text.get_mut(cx.current) {
233 if self.show_placeholder.get() && !txt.is_empty() {
234 text.clear();
235 self.show_placeholder.set(false);
236 }
237
238 text.edit(self.selection.range(), txt);
239
240 self.selection = Selection::caret(self.selection.min() + txt.len());
241
242 self.show_placeholder.set(text.is_empty());
243 cx.style.needs_text_update(cx.current);
244 cx.style.needs_access_update(cx.current);
245 }
246 }
247
248 fn update_preedit(
249 &mut self,
250 cx: &mut EventContext,
251 preedit_txt: &str,
252 cursor: Option<(usize, usize)>,
253 ) {
254 if preedit_txt.is_empty() || cursor.is_none() {
255 return;
256 }
257
258 if let Some(text) = cx.style.text.get_mut(cx.current) {
259 if self.show_placeholder.get() {
260 text.clear();
261 self.show_placeholder.set(false);
262 }
263
264 if !self.selection.is_caret() {
265 let start = self.selection.min();
266 let end = self.selection.max();
267
268 if end > start && end <= text.len() {
269 text.replace_range(start..end, "");
270 }
271 self.selection = Selection::caret(start);
272 }
273
274 let preedit_backup = self
275 .preedit_backup
276 .get_or_insert_with(|| PreeditBackup::new(String::new(), self.selection));
277
278 let original_selection = preedit_backup.original_selection;
279 let prev_preedit_text = &preedit_backup.prev_preedit;
280
281 if prev_preedit_text == preedit_txt {
282 let new_selection = Selection::caret(original_selection.min() + cursor.unwrap().0);
284 self.selection = new_selection;
285 } else {
286 let start = original_selection.min();
288 let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
289
290 if end > start && end <= text.len() {
292 text.replace_range(start..end, "");
293 }
294
295 text.insert_str(start, preedit_txt);
296
297 if let Some((cursor_index, _)) = cursor {
298 let new_caret = original_selection.min() + cursor_index;
299 self.selection = Selection::caret(new_caret);
300 } else {
301 let new_caret = original_selection.min() + preedit_txt.chars().count();
303 self.selection = Selection::caret(new_caret);
304 }
305
306 self.preedit_backup.as_mut().unwrap().set_prev_preedit(preedit_txt.to_string());
307 }
308
309 cx.style.needs_text_update(cx.current);
310 }
311 }
312
313 fn clear_preedit(&mut self, cx: &mut EventContext) {
314 if let Some(text) = cx.style.text.get_mut(cx.current) {
315 if let Some(preedit_backup) = self.preedit_backup.as_ref() {
316 let original_selection = preedit_backup.original_selection;
317 let prev_preedit_text = preedit_backup.prev_preedit.clone();
318
319 let start = original_selection.min();
320 let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
321
322 text.replace_range(start..end, "");
323
324 self.selection = original_selection;
325
326 self.preedit_backup = None;
327 }
328 }
329 }
330
331 fn delete_text(&mut self, cx: &mut EventContext, movement: Movement) {
332 if self.show_placeholder.get() {
333 return;
334 }
335
336 if self.preedit_backup.is_some() {
337 return;
338 }
339
340 if self.selection.is_caret() {
341 if movement == Movement::Grapheme(Direction::Upstream) {
342 if self.selection.active == 0 {
343 return;
344 }
345 if let Some(text) = cx.style.text.get_mut(cx.current) {
346 let del_offset = offset_for_delete_backwards(&self.selection, text);
347 let del_range = del_offset..self.selection.active;
348
349 self.selection = Selection::caret(del_range.start);
350
351 text.edit(del_range, "");
352
353 cx.style.needs_text_update(cx.current);
354 cx.style.needs_access_update(cx.current);
355 }
356 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
357 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
358 let to_delete = apply_movement(movement, self.selection, text, paragraph, true);
359 self.selection = to_delete;
360 let new_cursor_pos = self.selection.min();
361
362 text.edit(to_delete.range(), "");
363 self.selection = Selection::caret(new_cursor_pos);
364
365 cx.style.needs_text_update(cx.current);
366 cx.style.needs_access_update(cx.current);
367 }
368 }
369 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
370 let del_range = self.selection.range();
371
372 self.selection = Selection::caret(del_range.start);
373
374 text.edit(del_range, "");
375
376 cx.style.needs_text_update(cx.current);
377 cx.style.needs_access_update(cx.current);
378 }
379
380 if let Some(text) = cx.style.text.get_mut(cx.current) {
381 self.show_placeholder.set(text.is_empty());
382 }
383 }
384
385 fn reset_text(&mut self, cx: &mut EventContext) {
386 if let Some(text) = cx.style.text.get_mut(cx.current) {
387 text.clear();
388 self.selection = Selection::caret(0);
389 self.show_placeholder.set(true);
390 *text = self.placeholder.get().clone();
391 cx.style.needs_text_update(cx.current);
392 cx.style.needs_access_update(cx.current);
393 }
394 }
395
396 fn move_cursor(&mut self, cx: &mut EventContext, movement: Movement, selection: bool) {
400 if let Some(text) = cx.style.text.get_mut(cx.current) {
401 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
402 let new_selection =
403 apply_movement(movement, self.selection, text, paragraph, selection);
404 self.selection = new_selection;
405 cx.needs_redraw();
406 cx.style.needs_access_update(cx.current);
407 }
408 }
409 }
410
411 fn select_all(&mut self, cx: &mut EventContext) {
412 if self.show_placeholder.get() {
413 return;
414 }
415 if let Some(text) = cx.style.text.get(cx.current) {
416 self.selection.anchor = 0;
417 self.selection.active = text.len();
418 cx.needs_redraw();
419 cx.style.needs_access_update(cx.current);
420 }
421 }
422
423 fn select_word(&mut self, cx: &mut EventContext) {
424 if self.show_placeholder.get() {
425 return;
426 }
427 self.move_cursor(cx, Movement::Word(Direction::Upstream), false);
428 self.move_cursor(cx, Movement::Word(Direction::Downstream), true);
429 }
430
431 fn select_paragraph(&mut self, cx: &mut EventContext) {
432 if self.show_placeholder.get() {
433 return;
434 }
435 self.move_cursor(cx, Movement::ParagraphStart, false);
436 self.move_cursor(cx, Movement::ParagraphEnd, true);
437 }
438
439 fn deselect(&mut self) {
440 self.selection = Selection::caret(self.selection.active);
441 }
442
443 fn coordinates_global_to_text(&self, cx: &EventContext, x: f32, y: f32) -> (f32, f32) {
447 let bounds = cx.bounds();
448
449 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
450 let padding_left = cx
451 .style
452 .padding_left
453 .get_resolved(cx.current, &cx.style.custom_units_props)
454 .unwrap_or_default();
455 let padding_top = cx
456 .style
457 .padding_top
458 .get_resolved(cx.current, &cx.style.custom_units_props)
459 .unwrap_or_default();
460 let padding_right = cx
461 .style
462 .padding_right
463 .get_resolved(cx.current, &cx.style.custom_units_props)
464 .unwrap_or_default();
465 let padding_bottom = cx
466 .style
467 .padding_bottom
468 .get_resolved(cx.current, &cx.style.custom_units_props)
469 .unwrap_or_default();
470
471 let logical_parent_width = cx.physical_to_logical(bounds.w);
472 let logical_parent_height = cx.physical_to_logical(bounds.h);
473
474 let mut padding_left =
475 padding_left.to_px(logical_parent_width, 0.0) * cx.scale_factor();
476 let mut padding_right =
477 padding_right.to_px(logical_parent_width, 0.0) * cx.scale_factor();
478 let padding_top = padding_top.to_px(logical_parent_height, 0.0) * cx.scale_factor();
479 let padding_bottom =
480 padding_bottom.to_px(logical_parent_height, 0.0) * cx.scale_factor();
481
482 if resolved_text_direction(cx.style, cx.current) == crate::style::Direction::RightToLeft
483 {
484 std::mem::swap(&mut padding_left, &mut padding_right);
485 }
486
487 let (mut top, _) = match cx.style.alignment.get(cx.current).copied().unwrap_or_default()
488 {
489 Alignment::TopLeft => (0.0, 0.0),
490 Alignment::TopCenter => (0.0, 0.5),
491 Alignment::TopRight => (0.0, 1.0),
492 Alignment::Left => (0.5, 0.0),
493 Alignment::Center => (0.5, 0.5),
494 Alignment::Right => (0.5, 1.0),
495 Alignment::BottomLeft => (1.0, 0.0),
496 Alignment::BottomCenter => (1.0, 0.5),
497 Alignment::BottomRight => (1.0, 1.0),
498 };
499
500 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
501
502 let x = x - bounds.x - padding_left;
503 let y = y - bounds.y - padding_top - top;
504
505 (x, y)
506 } else {
507 (x, y)
508 }
509 }
510
511 fn hit(&mut self, cx: &mut EventContext, x: f32, y: f32, selection: bool) {
513 if let Some(text) = cx.style.text.get(cx.current) {
514 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
515 let x = x - self.transform.borrow().0;
516 let y = y - self.transform.borrow().1;
517 let gp = paragraph
518 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
519 let num_graphemes = text.graphemes(true).count();
520 let pos = (gp.position as usize).min(num_graphemes);
521 let mut cursor = text.len();
522 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
523 if pos == i {
524 cursor = j;
525 break;
526 }
527 }
528
529 if selection {
530 self.selection.active = cursor;
531 } else {
532 self.selection = Selection::caret(cursor);
533 }
534
535 cx.needs_redraw();
536 cx.style.needs_access_update(cx.current);
537 }
538 }
539 }
540
541 fn drag(&mut self, cx: &mut EventContext, x: f32, y: f32) {
543 if let Some(text) = cx.style.text.get(cx.current) {
544 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
545 let x = x - self.transform.borrow().0;
546 let y = y - self.transform.borrow().1;
547 let gp = paragraph
548 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
549 let num_graphemes = text.graphemes(true).count();
550 let pos = (gp.position as usize).min(num_graphemes);
551
552 let mut cursor = text.len();
553 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
554 if pos == i {
555 cursor = j;
556 break;
557 }
558 }
559
560 self.selection.active = cursor;
561
562 cx.needs_redraw();
563 cx.style.needs_access_update(cx.current);
564 }
565 }
566 }
567
568 #[cfg(feature = "clipboard")]
572 fn clone_selected(&self, cx: &mut EventContext) -> Option<String> {
573 if let Some(text) = cx.style.text.get(cx.current) {
574 let substring = &text[self.selection.range()];
575 return Some(substring.to_string());
576 }
577
578 None
579 }
580
581 fn clone_text(&self, cx: &mut EventContext) -> String {
582 if self.show_placeholder.get() {
583 return String::new();
584 }
585
586 if let Some(text) = cx.style.text.get(cx.current) { text.clone() } else { String::new() }
587 }
588
589 fn reset_caret_timer(&mut self, cx: &mut EventContext) {
590 cx.stop_timer(self.caret_timer);
591 if !cx.is_read_only() {
592 self.show_caret.set(true);
593 cx.start_timer(self.caret_timer);
594 }
595 }
596
597 fn reset_ime_position(&mut self, cx: &mut EventContext) {
598 cx.event_queue.push_back(
600 Event::new(WindowEvent::SetImeCursorArea(
601 (cx.bounds().x as u32, cx.bounds().y as u32),
602 ((cx.bounds().width()) as u32, cx.bounds().height() as u32),
603 ))
604 .target(cx.current),
605 );
606 }
607
608 fn draw_selection(&self, cx: &mut DrawContext, canvas: &Canvas) {
609 if !self.selection.is_caret() {
610 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
611 if let Some(text) = cx.style.text.get(cx.current) {
612 let min = text.current_grapheme_offset(self.selection.min());
613 let max = text.current_grapheme_offset(self.selection.max());
614
615 let cursor_rects = paragraph.get_rects_for_range(
616 min..max,
617 RectHeightStyle::Tight,
618 RectWidthStyle::Tight,
619 );
620
621 for cursor_rect in cursor_rects {
622 let bounds = cx.bounds();
623
624 let alignment = cx.alignment();
625
626 let (mut top, left) = match alignment {
627 Alignment::TopLeft => (0.0, 0.0),
628 Alignment::TopCenter => (0.0, 0.5),
629 Alignment::TopRight => (0.0, 1.0),
630 Alignment::Left => (0.5, 0.0),
631 Alignment::Center => (0.5, 0.5),
632 Alignment::Right => (0.5, 1.0),
633 Alignment::BottomLeft => (1.0, 0.0),
634 Alignment::BottomCenter => (1.0, 0.5),
635 Alignment::BottomRight => (1.0, 1.0),
636 };
637
638 let padding_top = match cx.padding_top() {
639 Units::Pixels(val) => val,
640 _ => 0.0,
641 };
642
643 let padding_bottom = match cx.padding_bottom() {
644 Units::Pixels(val) => val,
645 _ => 0.0,
646 };
647
648 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
649
650 let mut padding_left = match cx.padding_left() {
651 Units::Pixels(val) => val,
652 _ => 0.0,
653 };
654
655 let mut padding_right = match cx.padding_right() {
656 Units::Pixels(val) => val,
657 _ => 0.0,
658 };
659
660 if resolved_text_direction(cx.style, cx.current)
661 == crate::style::Direction::RightToLeft
662 {
663 std::mem::swap(&mut padding_left, &mut padding_right);
664 }
665
666 let x = bounds.x + padding_left + cursor_rect.rect.left + left;
667 let y = bounds.y + padding_top + cursor_rect.rect.top + top;
668
669 let x2 = x + (cursor_rect.rect.right - cursor_rect.rect.left);
670 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
671
672 let mut paint = Paint::default();
673 paint.set_anti_alias(true);
674 paint.set_style(PaintStyle::Fill);
675 paint.set_color(cx.selection_color());
676
677 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
678 }
679 }
680 }
681 }
682 }
683
684 pub fn draw_text_caret(&self, cx: &mut DrawContext, canvas: &Canvas) {
686 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
687 if let Some(text) = cx.style.text.get(cx.current) {
688 let bounds = cx.bounds();
689
690 let current = text.current_grapheme_offset(self.selection.active);
691
692 let grapheme_count = text.graphemes(true).count();
693 let (range_start, range_end, use_trailing_edge) = if current < grapheme_count {
694 (current, current + 1, false)
695 } else if current > 0 {
696 (current - 1, current, true)
698 } else {
699 return;
701 };
702
703 let rects = paragraph.get_rects_for_range(
704 range_start..range_end,
705 RectHeightStyle::Tight,
706 RectWidthStyle::Tight,
707 );
708
709 let Some(cursor_rect) = rects.first() else {
710 return;
711 };
712
713 let alignment = cx.alignment();
714
715 let (mut top, _) = match alignment {
716 Alignment::TopLeft => (0.0, 0.0),
717 Alignment::TopCenter => (0.0, 0.5),
718 Alignment::TopRight => (0.0, 1.0),
719 Alignment::Left => (0.5, 0.0),
720 Alignment::Center => (0.5, 0.5),
721 Alignment::Right => (0.5, 1.0),
722 Alignment::BottomLeft => (1.0, 0.0),
723 Alignment::BottomCenter => (1.0, 0.5),
724 Alignment::BottomRight => (1.0, 1.0),
725 };
726
727 let padding_top = match cx.padding_top() {
728 Units::Pixels(val) => val,
729 _ => 0.0,
730 };
731
732 let padding_bottom = match cx.padding_bottom() {
733 Units::Pixels(val) => val,
734 _ => 0.0,
735 };
736
737 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
738
739 let mut padding_left = match cx.padding_left() {
740 Units::Pixels(val) => val,
741 _ => 0.0,
742 };
743
744 let mut padding_right = match cx.padding_right() {
745 Units::Pixels(val) => val,
746 _ => 0.0,
747 };
748
749 if resolved_text_direction(cx.style, cx.current)
750 == crate::style::Direction::RightToLeft
751 {
752 std::mem::swap(&mut padding_left, &mut padding_right);
753 }
754
755 let caret_x =
756 if use_trailing_edge { cursor_rect.rect.right } else { cursor_rect.rect.left };
757
758 let x = (bounds.x + padding_left + caret_x).round();
759 let y = (bounds.y + padding_top + cursor_rect.rect.top + top).round();
760
761 let x2 = x + 1.0;
762 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
763
764 let mut paint = Paint::default();
765 paint.set_anti_alias(true);
766 paint.set_style(PaintStyle::Fill);
767 paint.set_color(cx.caret_color());
768
769 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
770
771 let mut transform = self.transform.borrow_mut();
772
773 let text_bounds = BoundingBox::from_min_max(
774 bounds.x + padding_left,
775 bounds.y + padding_top + top,
776 bounds.x + padding_left + paragraph.max_intrinsic_width(),
777 bounds.y + padding_top + top + paragraph.height(),
778 );
779
780 let mut bounds = bounds;
781
782 bounds =
783 bounds.shrink_sides(padding_left, padding_top, padding_right, padding_bottom);
784
785 let (tx, ty) =
786 enforce_text_bounds(&text_bounds, &bounds, (transform.0, transform.1));
787
788 let caret_box = BoundingBox::from_min_max(x, y, x2, y2);
789
790 let (new_tx, new_ty) = ensure_visible(&caret_box, &bounds, (tx, ty));
791
792 if new_tx != transform.0 || new_ty != transform.1 {
793 *transform = (new_tx, new_ty);
794 cx.needs_redraw();
795 }
796 }
797 }
798 }
799}
800
801impl<R, T> Handle<'_, Textbox<R, T>>
802where
803 R: Res<T> + 'static,
804 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
805{
806 pub fn on_edit<F>(self, callback: F) -> Self
810 where
811 F: 'static + Fn(&mut EventContext, String) + Send + Sync,
812 {
813 self.modify(|textbox| textbox.on_edit = Some(Box::new(callback)))
814 }
815
816 pub fn on_submit<F>(self, callback: F) -> Self
821 where
822 F: 'static + Fn(&mut EventContext, T, bool) + Send + Sync,
823 {
824 self.modify(|textbox| textbox.on_submit = Some(Box::new(callback)))
825 }
826
827 pub fn on_blur<F>(self, callback: F) -> Self
829 where
830 F: 'static + Fn(&mut EventContext) + Send + Sync,
831 {
832 self.modify(|textbox| textbox.on_blur = Some(Box::new(callback)))
833 }
834
835 pub fn on_cancel<F>(self, callback: F) -> Self
837 where
838 F: 'static + Fn(&mut EventContext) + Send + Sync,
839 {
840 self.modify(|textbox| textbox.on_cancel = Some(Box::new(callback)))
841 }
842
843 pub fn validate<F>(self, is_valid: F) -> Self
847 where
848 F: 'static + Fn(&T) -> bool + Send + Sync,
849 {
850 self.modify(|textbox| textbox.validate = Some(Box::new(is_valid)))
851 }
852
853 pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
855 self,
856 text: impl Res<P> + 'static,
857 ) -> Self {
858 let text = text.to_signal(self.cx);
859 self.bind(text, move |mut handle| {
860 let text = text.get();
861 let txt = text.to_string_local(&handle);
862 let entity = handle.entity();
863 handle = handle.modify(|textbox| textbox.placeholder.set(txt));
864 handle.context().style.needs_access_update(entity);
865 })
866 }
867}
868
869fn byte_offset_to_char_index(character_lengths: &[u8], byte_offset: usize) -> usize {
872 let mut cumulative = 0;
873 for (i, &len) in character_lengths.iter().enumerate() {
874 cumulative += len as usize;
875 if byte_offset < cumulative {
876 return i;
877 }
878 }
879 character_lengths.len()
880}
881
882impl<R, T> View for Textbox<R, T>
883where
884 R: Res<T> + 'static,
885 T: Clone + ToStringLocalized + std::str::FromStr + 'static,
886{
887 fn element(&self) -> Option<&'static str> {
888 Some("textbox")
889 }
890
891 fn accessibility(&self, cx: &mut AccessContext, node: &mut AccessNode) {
892 if !self.placeholder.get().is_empty() {
893 node.set_placeholder(self.placeholder.get().clone());
894 }
895
896 let node_id = node.node_id();
897
898 let selection = self.selection;
899
900 let mut selection_active_line = None;
901 let mut selection_anchor_line = None;
902 let mut selection_active_cursor = 0;
903 let mut selection_anchor_cursor = 0;
904 let mut first_line_node_id = None;
905
906 let text = if self.show_placeholder.get() {
907 ""
908 } else {
909 cx.style.text.get(cx.current).map(|t| t.as_str()).unwrap_or("")
910 };
911 let text_len = text.len();
915
916 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
917 let text_direction = if resolved_text_direction(cx.style, cx.current)
918 == crate::style::Direction::RightToLeft
919 {
920 TextDirection::RightToLeft
921 } else {
922 TextDirection::LeftToRight
923 };
924
925 let line_metrics = paragraph.get_line_metrics();
926 for line in line_metrics.iter() {
927 if line.start_index >= text_len && text_len > 0 {
929 continue;
930 }
931
932 let mut line_node = AccessNode::new_from_parent(node_id, line.line_number);
934 line_node.set_role(Role::TextRun);
935 line_node.set_text_direction(text_direction);
936 line_node.set_bounds(BoundingBox {
937 x: line.left as f32,
938 y: (line.baseline - line.ascent) as f32,
939 w: line.width as f32,
940 h: line.height as f32,
941 });
942
943 let glyph_end = line.end_index.min(text_len);
945 let estimated_chars = glyph_end - line.start_index;
946 let mut character_lengths: Vec<u8> = Vec::with_capacity(estimated_chars);
947 let mut character_positions: Vec<f32> = Vec::with_capacity(estimated_chars);
948 let mut character_widths: Vec<f32> = Vec::with_capacity(estimated_chars);
949 let mut glyph_pos = line.start_index;
950
951 while glyph_pos < glyph_end {
952 if let Some(cluster_info) = paragraph.get_glyph_cluster_at(glyph_pos) {
953 let length = cluster_info.text_range.end - cluster_info.text_range.start;
954 if length == 0 {
955 break;
956 }
957
958 character_lengths.push(length as u8);
959 character_positions.push(cluster_info.bounds.left());
960 character_widths.push(cluster_info.bounds.width());
961
962 glyph_pos += length;
963 } else {
964 break;
965 }
966 }
967
968 let line_end = if line.hard_break {
970 line.end_including_newline.min(text_len)
971 } else {
972 glyph_end
973 };
974 let line_text = text.get(line.start_index..line_end).unwrap_or("").to_owned();
975
976 if line.hard_break && line.end_including_newline <= text_len {
977 character_lengths.push(1);
978 character_positions.push(line.width as f32);
979 character_widths.push(0.0);
980 }
981
982 let mut word_starts = Vec::new();
983 let mut previous_is_alphanumeric = text
984 .get(..line.start_index)
985 .and_then(|prefix| prefix.graphemes(true).next_back())
986 .and_then(|grapheme| grapheme.chars().next())
987 .is_some_and(|ch| ch.is_alphanumeric());
988
989 for (character_index, grapheme) in line_text.graphemes(true).enumerate() {
990 let current_is_alphanumeric =
991 grapheme.chars().next().is_some_and(|ch| ch.is_alphanumeric());
992
993 if current_is_alphanumeric
994 && !previous_is_alphanumeric
995 && let Ok(character_index) = u8::try_from(character_index)
996 {
997 word_starts.push(character_index);
998 }
999
1000 previous_is_alphanumeric = current_is_alphanumeric;
1001 }
1002
1003 if first_line_node_id.is_none() {
1004 first_line_node_id = Some(line_node.node_id());
1005 }
1006
1007 if selection.active >= line.start_index && selection.active <= line_end {
1009 selection_active_line = Some(line_node.node_id());
1010 selection_active_cursor = byte_offset_to_char_index(
1011 &character_lengths,
1012 selection.active - line.start_index,
1013 );
1014 }
1015
1016 if selection.anchor >= line.start_index && selection.anchor <= line_end {
1018 selection_anchor_line = Some(line_node.node_id());
1019 selection_anchor_cursor = byte_offset_to_char_index(
1020 &character_lengths,
1021 selection.anchor - line.start_index,
1022 );
1023 }
1024
1025 line_node.set_value(line_text.into_boxed_str());
1026 line_node.set_character_lengths(character_lengths.into_boxed_slice());
1027 line_node.set_character_positions(character_positions.into_boxed_slice());
1028 line_node.set_character_widths(character_widths.into_boxed_slice());
1029 line_node.set_word_starts(word_starts.into_boxed_slice());
1030
1031 node.add_child(line_node);
1032 }
1033 }
1034
1035 if let Some(fallback) = first_line_node_id {
1036 node.set_text_selection(TextSelection {
1037 anchor: TextPosition {
1038 node: selection_anchor_line.unwrap_or(fallback),
1039 character_index: selection_anchor_cursor,
1040 },
1041 focus: TextPosition {
1042 node: selection_active_line.unwrap_or(fallback),
1043 character_index: selection_active_cursor,
1044 },
1045 });
1046 }
1047 }
1048
1049 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
1050 event.map(|window_event, meta| match window_event {
1052 WindowEvent::MouseDown(MouseButton::Left) => {
1053 if meta.origin == cx.current {
1054 return;
1055 }
1056
1057 if cx.is_over() {
1058 if !cx.is_disabled() {
1059 cx.focus_with_visibility(false);
1060 cx.capture();
1061 cx.lock_cursor_icon();
1062
1063 if !self.edit {
1064 cx.emit(TextEvent::StartEdit);
1065 }
1066 self.reset_caret_timer(cx);
1067 cx.emit(TextEvent::Hit(
1068 cx.mouse.cursor_x,
1069 cx.mouse.cursor_y,
1070 cx.modifiers.shift(),
1071 ));
1072 }
1073 } else {
1074 cx.emit(TextEvent::Submit(false));
1075 cx.release();
1076
1077 cx.event_queue.push_back(
1079 Event::new(WindowEvent::MouseDown(MouseButton::Left)).target(cx.hovered()),
1080 );
1081 cx.event_queue.push_back(
1082 Event::new(WindowEvent::PressDown { mouse: true }).target(cx.hovered()),
1083 );
1084 }
1085 }
1086
1087 WindowEvent::FocusIn => {
1088 if cx.mouse.left.pressed != cx.current()
1089 || cx.mouse.left.state == MouseButtonState::Released
1090 {
1091 cx.emit(TextEvent::StartEdit);
1092 }
1093 }
1094
1095 WindowEvent::FocusOut => {
1096 cx.emit(TextEvent::EndEdit);
1097 }
1098
1099 WindowEvent::MouseDoubleClick(MouseButton::Left) => {
1100 cx.emit(TextEvent::SelectWord);
1101 }
1102
1103 WindowEvent::MouseTripleClick(MouseButton::Left) => {
1104 cx.emit(TextEvent::SelectParagraph);
1105 }
1106
1107 WindowEvent::MouseUp(MouseButton::Left) => {
1108 self.reset_caret_timer(cx);
1109 cx.unlock_cursor_icon();
1110 cx.release();
1111 }
1112
1113 WindowEvent::MouseMove(x, y) => {
1114 if cx.mouse.left.state == MouseButtonState::Pressed
1115 && cx.mouse.left.pressed == cx.current
1116 {
1117 if self.edit {
1118 self.reset_caret_timer(cx);
1119 }
1120 if cx.mouse.left.pos_down.0 != *x || cx.mouse.left.pos_down.1 != *y {
1121 cx.emit(TextEvent::Drag(*x, *y));
1122 }
1123 }
1124 }
1125
1126 WindowEvent::MouseScroll(x, y) => {
1127 cx.emit(TextEvent::Scroll(*x, *y));
1128 }
1129
1130 WindowEvent::CharInput(c) => {
1131 if *c != '\u{1b}' && *c != '\u{8}' && *c != '\u{9}' && *c != '\u{7f}' && *c != '\u{0d}' && !cx.modifiers.ctrl() &&
1137 !cx.modifiers.logo() &&
1138 self.edit &&
1139 !cx.is_read_only()
1140 {
1141 self.reset_caret_timer(cx);
1142 cx.emit(TextEvent::InsertText(String::from(*c)));
1143 }
1144 }
1145
1146 WindowEvent::ImeCommit(text) => {
1147 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1148 self.reset_caret_timer(cx);
1149 cx.emit(TextEvent::ClearPreedit);
1150 cx.emit(TextEvent::InsertText(text.to_string()));
1151
1152 self.reset_ime_position(cx);
1153 }
1154 }
1155
1156 WindowEvent::ImePreedit(text, cursor) => {
1157 if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1158 self.reset_caret_timer(cx);
1159 cx.emit(TextEvent::UpdatePreedit(text.to_string(), *cursor));
1160 }
1161 }
1162
1163 WindowEvent::KeyDown(code, _) => match code {
1164 Code::Enter => {
1165 if matches!(self.kind, TextboxKind::SingleLine) {
1166 cx.emit(TextEvent::Submit(true));
1167 } else if !cx.is_read_only() {
1168 self.reset_caret_timer(cx);
1169 cx.emit(TextEvent::InsertText("\n".to_owned()));
1170 }
1171 }
1172
1173 Code::ArrowLeft => {
1180 self.reset_caret_timer(cx);
1181 #[cfg(target_os = "macos")]
1185 let movement = if cx.modifiers.logo() {
1186 Movement::LineStart
1187 } else if cx.modifiers.alt() {
1188 Movement::Word(Direction::Left)
1189 } else {
1190 Movement::Grapheme(Direction::Left)
1191 };
1192 #[cfg(not(target_os = "macos"))]
1193 let movement = if cx.modifiers.ctrl() {
1194 Movement::Word(Direction::Left)
1195 } else {
1196 Movement::Grapheme(Direction::Left)
1197 };
1198
1199 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1200 }
1201
1202 Code::ArrowRight => {
1203 self.reset_caret_timer(cx);
1204
1205 #[cfg(target_os = "macos")]
1206 let movement = if cx.modifiers.logo() {
1207 Movement::LineEnd
1208 } else if cx.modifiers.alt() {
1209 Movement::Word(Direction::Right)
1210 } else {
1211 Movement::Grapheme(Direction::Right)
1212 };
1213 #[cfg(not(target_os = "macos"))]
1214 let movement = if cx.modifiers.ctrl() {
1215 Movement::Word(Direction::Right)
1216 } else {
1217 Movement::Grapheme(Direction::Right)
1218 };
1219
1220 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1221 }
1222
1223 Code::ArrowUp => {
1224 self.reset_caret_timer(cx);
1225 if self.kind != TextboxKind::SingleLine {
1226 cx.emit(TextEvent::MoveCursor(
1227 Movement::Vertical(VerticalMovement::LineUp),
1228 cx.modifiers.shift(),
1229 ));
1230 }
1231 }
1232
1233 Code::ArrowDown => {
1234 self.reset_caret_timer(cx);
1235 if self.kind != TextboxKind::SingleLine {
1236 cx.emit(TextEvent::MoveCursor(
1237 Movement::Vertical(VerticalMovement::LineDown),
1238 cx.modifiers.shift(),
1239 ));
1240 }
1241 }
1242
1243 Code::Backspace => {
1244 self.reset_caret_timer(cx);
1245 if !cx.is_read_only() {
1246 #[cfg(target_os = "macos")]
1247 let movement = if cx.modifiers.logo() {
1248 Movement::LineStart
1252 } else if cx.modifiers.alt() {
1253 Movement::Word(Direction::Upstream)
1254 } else {
1255 Movement::Grapheme(Direction::Upstream)
1256 };
1257 #[cfg(not(target_os = "macos"))]
1258 let movement = if cx.modifiers.ctrl() {
1259 Movement::Word(Direction::Upstream)
1260 } else {
1261 Movement::Grapheme(Direction::Upstream)
1262 };
1263
1264 cx.emit(TextEvent::DeleteText(movement));
1265 }
1266 }
1267
1268 Code::Delete => {
1269 self.reset_caret_timer(cx);
1270 if !cx.is_read_only() {
1271 #[cfg(target_os = "macos")]
1272 let movement = if cx.modifiers.alt() {
1273 Movement::Word(Direction::Downstream)
1274 } else {
1275 Movement::Grapheme(Direction::Downstream)
1276 };
1277 #[cfg(not(target_os = "macos"))]
1278 let movement = if cx.modifiers.ctrl() {
1279 Movement::Word(Direction::Downstream)
1280 } else {
1281 Movement::Grapheme(Direction::Downstream)
1282 };
1283
1284 cx.emit(TextEvent::DeleteText(movement));
1285 }
1286 }
1287
1288 Code::Escape => {
1289 if let Some(callback) = &self.on_cancel {
1290 (callback)(cx);
1291 } else {
1292 cx.emit(TextEvent::EndEdit);
1293 }
1294 }
1295
1296 Code::Home => {
1297 self.reset_caret_timer(cx);
1298 cx.emit(TextEvent::MoveCursor(Movement::LineStart, cx.modifiers.shift()));
1299 }
1300
1301 Code::End => {
1302 self.reset_caret_timer(cx);
1303 cx.emit(TextEvent::MoveCursor(Movement::LineEnd, cx.modifiers.shift()));
1304 }
1305
1306 Code::PageUp | Code::PageDown => {
1307 self.reset_caret_timer(cx);
1308 let direction = if *code == Code::PageUp {
1309 Direction::Upstream
1310 } else {
1311 Direction::Downstream
1312 };
1313 cx.emit(TextEvent::MoveCursor(
1314 if cx.modifiers.ctrl() {
1315 Movement::Body(direction)
1316 } else {
1317 Movement::Page(direction)
1318 },
1319 cx.modifiers.shift(),
1320 ));
1321 }
1322
1323 Code::KeyA => {
1324 #[cfg(target_os = "macos")]
1325 let modifier = Modifiers::SUPER;
1326 #[cfg(not(target_os = "macos"))]
1327 let modifier = Modifiers::CTRL;
1328
1329 if cx.modifiers == &modifier {
1330 cx.emit(TextEvent::SelectAll);
1331 }
1332 }
1333
1334 Code::KeyC => {
1335 #[cfg(target_os = "macos")]
1336 let modifier = Modifiers::SUPER;
1337 #[cfg(not(target_os = "macos"))]
1338 let modifier = Modifiers::CTRL;
1339
1340 if cx.modifiers == &modifier {
1341 cx.emit(TextEvent::Copy);
1342 }
1343 }
1344
1345 Code::KeyV => {
1346 #[cfg(target_os = "macos")]
1347 let modifier = Modifiers::SUPER;
1348 #[cfg(not(target_os = "macos"))]
1349 let modifier = Modifiers::CTRL;
1350
1351 if cx.modifiers == &modifier {
1352 cx.emit(TextEvent::Paste);
1353 }
1354 }
1355
1356 Code::KeyX => {
1357 #[cfg(target_os = "macos")]
1358 let modifier = Modifiers::SUPER;
1359 #[cfg(not(target_os = "macos"))]
1360 let modifier = Modifiers::CTRL;
1361
1362 if cx.modifiers == &modifier && !cx.is_read_only() {
1363 cx.emit(TextEvent::Cut);
1364 }
1365 }
1366
1367 _ => {}
1368 },
1369
1370 WindowEvent::ActionRequest(ActionRequest {
1371 action: accesskit::Action::SetTextSelection,
1372 target_tree: _,
1373 target_node: _,
1374 data: Some(ActionData::SetTextSelection(_selection)),
1375 }) => {
1376 }
1378
1379 _ => {}
1380 });
1381
1382 event.map(|text_event, _| match text_event {
1384 TextEvent::InsertText(text) => {
1385 if self.preedit_backup.is_some() {
1386 return;
1387 }
1388
1389 if self.show_placeholder.get() {
1390 self.reset_text(cx);
1391 }
1392
1393 self.insert_text(cx, text);
1394
1395 let text = self.clone_text(cx);
1396
1397 if let Ok(value) = &text.parse::<T>() {
1398 if let Some(validate) = &self.validate {
1399 cx.set_valid(validate(value));
1400 } else {
1401 cx.set_valid(true);
1402 }
1403 } else {
1404 cx.set_valid(false);
1405 }
1406
1407 if self.edit {
1408 if let Some(callback) = &self.on_edit {
1409 (callback)(cx, text);
1410 }
1411 }
1412 }
1413
1414 TextEvent::UpdatePreedit(preedit, cursor) => {
1415 self.update_preedit(cx, preedit, *cursor);
1416 }
1417
1418 TextEvent::ClearPreedit => {
1419 self.clear_preedit(cx);
1420 }
1421
1422 TextEvent::Clear => {
1423 self.reset_text(cx);
1424 cx.needs_relayout();
1426 cx.needs_redraw();
1427 }
1428
1429 TextEvent::DeleteText(movement) => {
1430 if self.edit {
1431 self.delete_text(cx, *movement);
1432
1433 let text = self.clone_text(cx);
1434
1435 if let Ok(value) = &text.parse::<T>() {
1436 if let Some(validate) = &self.validate {
1437 cx.set_valid(validate(value));
1438 } else {
1439 cx.set_valid(true);
1440 }
1441 } else {
1442 cx.set_valid(false);
1443 }
1444
1445 if let Some(callback) = &self.on_edit {
1446 (callback)(cx, text);
1447 }
1448 }
1449 }
1450
1451 TextEvent::MoveCursor(movement, selection) => {
1452 if self.edit && !self.show_placeholder.get() && self.preedit_backup.is_none() {
1453 self.move_cursor(cx, *movement, *selection);
1454 }
1455 }
1456
1457 TextEvent::SetPlaceholder(text) => {
1458 self.placeholder.set(text.clone());
1459 cx.style.needs_access_update(cx.current);
1460 }
1461
1462 TextEvent::StartEdit => {
1463 if !cx.is_disabled() && !self.edit {
1464 self.edit = true;
1465 cx.focus_with_visibility(false);
1466 cx.capture();
1467 self.reset_caret_timer(cx);
1468 self.reset_ime_position(cx);
1469
1470 self.text_overflow = cx.style.text_overflow.get_inline(cx.current).copied();
1471 cx.style.text_overflow.remove(cx.current);
1472
1473 let text = self.value.get_value(cx);
1474 let text = text.to_string_local(cx);
1475
1476 if text.is_empty() {
1477 self.show_placeholder.set(true);
1478 self.selection = Selection::caret(0);
1479 cx.style.needs_access_update(cx.current);
1480 } else {
1481 self.show_placeholder.set(false);
1482 self.select_all(cx);
1483 }
1484
1485 if let Ok(value) = &text.parse::<T>() {
1486 if let Some(validate) = &self.validate {
1487 cx.set_valid(validate(value));
1488 } else {
1489 cx.set_valid(true);
1490 }
1491 } else {
1492 cx.set_valid(false);
1493 }
1494 }
1495
1496 cx.style.needs_text_update(cx.current);
1497 }
1498
1499 TextEvent::EndEdit => {
1500 self.deselect();
1501 self.edit = false;
1502 cx.release();
1503 cx.stop_timer(self.caret_timer);
1504
1505 let text = self.value.get_value(cx);
1506 let text = text.to_string_local(cx);
1507 self.show_placeholder.set(text.is_empty());
1508
1509 if let Some(text_overflow) = self.text_overflow {
1510 cx.style.text_overflow.insert(cx.current, text_overflow);
1511 } else {
1512 cx.style.text_overflow.remove(cx.current);
1513 }
1514
1515 self.select_all(cx);
1516
1517 if let Ok(value) = &text.parse::<T>() {
1518 if let Some(validate) = &self.validate {
1519 cx.set_valid(validate(value));
1520 } else {
1521 cx.set_valid(true);
1522 }
1523 } else {
1524 cx.set_valid(false);
1525 }
1526
1527 let mut transform = self.transform.borrow_mut();
1529 *transform = (0.0, 0.0);
1530
1531 self.selection = Selection::caret(0);
1533
1534 cx.style.needs_text_update(cx.current);
1535 cx.style.needs_access_update(cx.current);
1536 }
1537
1538 TextEvent::Blur => {
1539 if let Some(callback) = &self.on_blur {
1540 (callback)(cx);
1541 } else {
1542 cx.emit(TextEvent::Submit(false));
1543 cx.emit(TextEvent::EndEdit);
1544 }
1545 }
1546
1547 TextEvent::Submit(reason) => {
1548 if let Some(callback) = &self.on_submit {
1549 if cx.is_valid() {
1550 let text = self.clone_text(cx);
1551 if let Ok(value) = text.parse::<T>() {
1552 (callback)(cx, value, *reason);
1553 }
1554 }
1555 }
1556 }
1557
1558 TextEvent::SelectAll => {
1559 self.select_all(cx);
1560 }
1561
1562 TextEvent::SelectWord => {
1563 self.select_word(cx);
1564 }
1565
1566 TextEvent::SelectParagraph => {
1567 self.select_paragraph(cx);
1568 }
1569
1570 TextEvent::Hit(posx, posy, selection) => {
1571 if !self.show_placeholder.get() {
1572 self.hit(cx, *posx, *posy, *selection);
1573 }
1574 }
1575
1576 TextEvent::Drag(posx, posy) => {
1577 if !self.show_placeholder.get() {
1578 self.drag(cx, *posx, *posy);
1579 }
1580 }
1581
1582 TextEvent::Scroll(_x, _y) => {
1583 }
1585
1586 TextEvent::Copy =>
1587 {
1588 #[cfg(feature = "clipboard")]
1589 if self.edit {
1590 if let Some(selected_text) = self.clone_selected(cx) {
1591 if !selected_text.is_empty() {
1592 cx.set_clipboard(selected_text)
1593 .expect("Failed to add text to clipboard");
1594 }
1595 }
1596 }
1597 }
1598
1599 TextEvent::Paste =>
1600 {
1601 #[cfg(feature = "clipboard")]
1602 if self.edit {
1603 if let Ok(text) = cx.get_clipboard() {
1604 cx.emit(TextEvent::InsertText(text));
1605 }
1606 }
1607 }
1608
1609 TextEvent::Cut =>
1610 {
1611 #[cfg(feature = "clipboard")]
1612 if self.edit {
1613 if let Some(selected_text) = self.clone_selected(cx) {
1614 if !selected_text.is_empty() {
1615 cx.set_clipboard(selected_text)
1616 .expect("Failed to add text to clipboard");
1617 self.delete_text(cx, Movement::Grapheme(Direction::Upstream));
1618
1619 let text = self.clone_text(cx);
1620
1621 if let Ok(value) = &text.parse::<T>() {
1622 if let Some(validate) = &self.validate {
1623 cx.set_valid(validate(value));
1624 } else {
1625 cx.set_valid(true);
1626 }
1627 } else {
1628 cx.set_valid(false);
1629 }
1630
1631 if let Some(callback) = &self.on_edit {
1632 (callback)(cx, text);
1633 }
1634 }
1635 }
1636 }
1637 }
1638
1639 TextEvent::ToggleCaret => {
1640 self.show_caret ^= true;
1641 }
1642 });
1643 }
1644
1645 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
1647 cx.draw_shadows(canvas);
1648 cx.draw_background(canvas);
1649 cx.draw_border(canvas);
1650 cx.draw_outline(canvas);
1651 canvas.save();
1652 let transform = *self.transform.borrow();
1653 canvas.translate((transform.0, transform.1));
1654 cx.draw_text(canvas);
1655
1656 if self.edit {
1657 self.draw_selection(cx, canvas);
1658 self.draw_text_caret(cx, canvas);
1659 }
1660
1661 canvas.restore();
1662 }
1663}