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