1use std::cell::RefCell;
2use std::rc::Rc;
3
4use crate::prelude::*;
6
7use crate::text::{
8 apply_movement, enforce_text_bounds, ensure_visible, offset_for_delete_backwards, Direction,
9 EditableText, Movement, Selection, VerticalMovement,
10};
11use accesskit::{ActionData, ActionRequest};
13use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
14use skia_safe::{Paint, PaintStyle, Rect};
15use unicode_segmentation::UnicodeSegmentation;
16
17pub enum TextEvent {
19 InsertText(String),
21 Clear,
23 DeleteText(Movement),
25 MoveCursor(Movement, bool),
27 SelectAll,
29 SelectWord,
31 SelectParagraph,
33 StartEdit,
35 EndEdit,
37 Submit(bool),
39 Hit(f32, f32, bool),
41 Drag(f32, f32),
43 Scroll(f32, f32),
45 Copy,
47 Paste,
49 Cut,
51 SetPlaceholder(String),
53 Blur,
55 ToggleCaret,
57}
58
59#[derive(Lens)]
66pub struct Textbox<L: Lens> {
67 lens: L,
68 #[lens(ignore)]
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, L::Target, 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(&L::Target) -> bool>>,
77 placeholder: String,
78 show_placeholder: bool,
79 show_caret: bool,
80 caret_timer: Timer,
81 selection: Selection,
82}
83
84#[derive(Copy, Clone, PartialEq, Eq)]
86enum TextboxKind {
87 SingleLine,
88 MultiLineUnwrapped,
89 MultiLineWrapped,
90}
91
92impl<L> Textbox<L>
93where
94 L: Lens<Target: Data + Clone + ToStringLocalized + std::str::FromStr>,
95{
96 pub fn new(cx: &mut Context, lens: L) -> Handle<Self> {
116 Self::new_core(cx, lens, TextboxKind::SingleLine)
117 }
118
119 pub fn new_multiline(cx: &mut Context, lens: L, wrap: bool) -> Handle<Self> {
143 Self::new_core(
144 cx,
145 lens,
146 if wrap { TextboxKind::MultiLineWrapped } else { TextboxKind::MultiLineUnwrapped },
147 )
148 }
149
150 fn new_core(cx: &mut Context, lens: L, kind: TextboxKind) -> Handle<Self> {
151 let caret_timer = cx.environment().caret_timer;
152
153 Self {
154 lens,
155 kind,
156 edit: false,
157 transform: Rc::new(RefCell::new((0.0, 0.0))),
158 on_edit: None,
159 on_submit: None,
160 on_blur: None,
161 on_cancel: None,
162 validate: None,
163 placeholder: String::from(""),
164 show_placeholder: true,
165 show_caret: true,
166 caret_timer,
167 selection: Selection::new(0, 0),
168 }
169 .build(cx, move |cx| {
170 cx.add_listener(move |textbox: &mut Self, cx, event| {
171 let flag: bool = textbox.edit;
172 event.map(|window_event, meta| match window_event {
173 WindowEvent::MouseDown(_) => {
174 if flag && meta.origin != cx.current() && cx.hovered() != cx.current() {
175 cx.emit(TextEvent::Blur);
176 }
177 }
178
179 _ => {}
180 });
181 });
182 })
183 .toggle_class("multiline", kind == TextboxKind::MultiLineWrapped)
184 .text_wrap(kind == TextboxKind::MultiLineWrapped)
185 .navigable(true)
186 .role(Role::TextInput)
187 .text_value(lens)
188 .toggle_class("caret", Self::show_caret)
189 .text(lens)
190 .placeholder_shown(Self::show_placeholder)
191 .bind(lens, |handle, lens| {
192 let mut text = lens.get(&handle).to_string_local(handle.cx);
193 let flag = text.is_empty();
194 if flag {
195 text = Self::placeholder.get(&handle).to_string_local(handle.cx);
196 }
197 handle
198 .modify(|textbox| {
199 textbox.show_placeholder = flag;
200 })
201 .text(text);
202 })
203 .bind(Self::show_placeholder, |handle, show_placeholder| {
204 let flag = show_placeholder.get(&handle);
205 if flag {
206 let placeholder = Self::placeholder.get(&handle).to_string_local(handle.cx);
207 handle.text(placeholder);
208 }
209 })
210 .bind(Self::placeholder, |handle, placeholder| {
211 let placeholder = placeholder.get(&handle).to_string_local(handle.cx);
212 let flag = Self::show_placeholder.get(&handle);
213 if flag {
214 handle.text(placeholder);
215 }
216 })
217 }
218
219 fn insert_text(&mut self, cx: &mut EventContext, txt: &str) {
220 if let Some(text) = cx.style.text.get_mut(cx.current) {
221 if self.show_placeholder && !txt.is_empty() {
222 text.clear();
223 self.show_placeholder = false;
224 }
225 text.edit(self.selection.range(), txt);
226 self.selection = Selection::caret(self.selection.min() + txt.len());
227 self.show_placeholder = text.is_empty();
228 cx.style.needs_text_update(cx.current);
229 }
230 }
231
232 fn delete_text(&mut self, cx: &mut EventContext, movement: Movement) {
233 if self.show_placeholder {
234 return;
235 }
236
237 if self.selection.is_caret() {
238 if movement == Movement::Grapheme(Direction::Upstream) {
239 if self.selection.active == 0 {
240 return;
241 }
242 if let Some(text) = cx.style.text.get_mut(cx.current) {
243 let del_offset = offset_for_delete_backwards(&self.selection, text);
244 let del_range = del_offset..self.selection.active;
245
246 self.selection = Selection::caret(del_range.start);
247
248 text.edit(del_range, "");
249
250 cx.style.needs_text_update(cx.current);
251 }
252 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
253 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
254 let to_delete = apply_movement(movement, self.selection, text, paragraph, true);
255 self.selection = to_delete;
256 let new_cursor_pos = self.selection.min();
257 text.edit(to_delete.range(), "");
258 self.selection = Selection::caret(new_cursor_pos);
259 cx.style.needs_text_update(cx.current);
260 }
261 }
262 } else if let Some(text) = cx.style.text.get_mut(cx.current) {
263 let del_range = self.selection.range();
264 self.selection = Selection::caret(del_range.start);
265
266 text.edit(del_range, "");
267
268 cx.style.needs_text_update(cx.current);
269 }
270
271 if let Some(text) = cx.style.text.get_mut(cx.current) {
272 self.show_placeholder = text.is_empty();
273 }
274 }
275
276 fn reset_text(&mut self, cx: &mut EventContext) {
277 if let Some(text) = cx.style.text.get_mut(cx.current) {
278 text.clear();
279 self.selection = Selection::caret(0);
280 self.show_placeholder = true;
281 *text = self.placeholder.clone();
282 cx.style.needs_text_update(cx.current);
283 }
284 }
285
286 fn move_cursor(&mut self, cx: &mut EventContext, movement: Movement, selection: bool) {
287 if let Some(text) = cx.style.text.get_mut(cx.current) {
288 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
289 let new_selection =
290 apply_movement(movement, self.selection, text, paragraph, selection);
291 self.selection = new_selection;
292 cx.needs_redraw();
293 }
294 }
295 }
296
297 fn select_all(&mut self, cx: &mut EventContext) {
298 if self.show_placeholder {
299 return;
300 }
301 if let Some(text) = cx.style.text.get(cx.current) {
302 self.selection.anchor = 0;
303 self.selection.active = text.len();
304 cx.needs_redraw();
305 }
306 }
307
308 fn select_word(&mut self, cx: &mut EventContext) {
309 if self.show_placeholder {
310 return;
311 }
312 self.move_cursor(cx, Movement::Word(Direction::Upstream), false);
313 self.move_cursor(cx, Movement::Word(Direction::Downstream), true);
314 }
315
316 fn select_paragraph(&mut self, cx: &mut EventContext) {
317 if self.show_placeholder {
318 return;
319 }
320 self.move_cursor(cx, Movement::ParagraphStart, false);
321 self.move_cursor(cx, Movement::ParagraphEnd, true);
322 }
323
324 fn deselect(&mut self) {
325 self.selection = Selection::caret(self.selection.active);
326 }
327
328 fn coordinates_global_to_text(&self, cx: &EventContext, x: f32, y: f32) -> (f32, f32) {
332 let bounds = cx.bounds();
333
334 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
335 let padding_left = cx.style.padding_left.get(cx.current).copied().unwrap_or_default();
336 let padding_top = cx.style.padding_top.get(cx.current).copied().unwrap_or_default();
337 let _padding_right =
338 cx.style.padding_right.get(cx.current).copied().unwrap_or_default();
339 let padding_bottom =
340 cx.style.padding_bottom.get(cx.current).copied().unwrap_or_default();
341
342 let logical_parent_width = cx.physical_to_logical(bounds.w);
343 let logical_parent_height = cx.physical_to_logical(bounds.h);
344
345 let padding_left = padding_left.to_px(logical_parent_width, 0.0) * cx.scale_factor();
346 let padding_top = padding_top.to_px(logical_parent_height, 0.0) * cx.scale_factor();
347 let padding_bottom =
348 padding_bottom.to_px(logical_parent_height, 0.0) * cx.scale_factor();
349
350 let (mut top, _) = match cx.style.alignment.get(cx.current).copied().unwrap_or_default()
351 {
352 Alignment::TopLeft => (0.0, 0.0),
353 Alignment::TopCenter => (0.0, 0.5),
354 Alignment::TopRight => (0.0, 1.0),
355 Alignment::Left => (0.5, 0.0),
356 Alignment::Center => (0.5, 0.5),
357 Alignment::Right => (0.5, 1.0),
358 Alignment::BottomLeft => (1.0, 0.0),
359 Alignment::BottomCenter => (1.0, 0.5),
360 Alignment::BottomRight => (1.0, 1.0),
361 };
362
363 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
364
365 let x = x - bounds.x - padding_left;
373 let y = y - bounds.y - padding_top - top;
374
375 (x, y)
376 } else {
377 (x, y)
378 }
379 }
380
381 fn hit(&mut self, cx: &mut EventContext, x: f32, y: f32, selection: bool) {
383 if let Some(text) = cx.style.text.get(cx.current) {
384 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
385 let x = x - self.transform.borrow().0;
386 let y = y - self.transform.borrow().1;
387 let gp = paragraph
388 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
389 let num_graphemes = text.graphemes(true).count();
390 let pos = (gp.position as usize).min(num_graphemes);
391 let mut cursor = text.len();
392 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
393 if pos == i {
394 cursor = j;
395 break;
396 }
397 }
398
399 if selection {
400 self.selection.active = cursor;
401 } else {
402 self.selection = Selection::caret(cursor);
403 }
404
405 cx.needs_redraw();
406 }
407 }
408 }
409
410 fn drag(&mut self, cx: &mut EventContext, x: f32, y: f32) {
412 if let Some(text) = cx.style.text.get(cx.current) {
413 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
414 let x = x - self.transform.borrow().0;
415 let y = y - self.transform.borrow().1;
416 let gp = paragraph
417 .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
418 let num_graphemes = text.graphemes(true).count();
419 let pos = (gp.position as usize).min(num_graphemes);
420
421 let mut cursor = text.len();
422 for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
423 if pos == i {
424 cursor = j;
425 break;
426 }
427 }
428
429 self.selection.active = cursor;
430
431 cx.needs_redraw();
432 }
433 }
434 }
435
436 #[cfg(feature = "clipboard")]
440 fn clone_selected(&self, cx: &mut EventContext) -> Option<String> {
441 if let Some(text) = cx.style.text.get(cx.current) {
442 let substring = &text[self.selection.range()];
443 return Some(substring.to_string());
444 }
445
446 None
447 }
448
449 fn clone_text(&self, cx: &mut EventContext) -> String {
450 if self.show_placeholder {
451 return String::new();
452 }
453
454 if let Some(text) = cx.style.text.get(cx.current) {
455 text.clone()
456 } else {
457 String::new()
458 }
459 }
460
461 fn reset_caret_timer(&mut self, cx: &mut EventContext) {
462 cx.stop_timer(self.caret_timer);
463 if !cx.is_read_only() {
464 self.show_caret = true;
465 cx.start_timer(self.caret_timer);
466 }
467 }
468
469 fn draw_selection(&self, cx: &mut DrawContext, canvas: &Canvas) {
470 if !self.selection.is_caret() {
471 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
472 if let Some(text) = cx.style.text.get(cx.current) {
473 let min = text.current_grapheme_offset(self.selection.min());
474 let max = text.current_grapheme_offset(self.selection.max());
475
476 let cursor_rects = paragraph.get_rects_for_range(
477 min..max,
478 RectHeightStyle::Tight,
479 RectWidthStyle::Tight,
480 );
481
482 for cursor_rect in cursor_rects {
483 let bounds = cx.bounds();
484
485 let alignment = cx.alignment();
486
487 let (mut top, left) = match alignment {
488 Alignment::TopLeft => (0.0, 0.0),
489 Alignment::TopCenter => (0.0, 0.5),
490 Alignment::TopRight => (0.0, 1.0),
491 Alignment::Left => (0.5, 0.0),
492 Alignment::Center => (0.5, 0.5),
493 Alignment::Right => (0.5, 1.0),
494 Alignment::BottomLeft => (1.0, 0.0),
495 Alignment::BottomCenter => (1.0, 0.5),
496 Alignment::BottomRight => (1.0, 1.0),
497 };
498
499 let padding_top = match cx.padding_top() {
500 Units::Pixels(val) => val,
501 _ => 0.0,
502 };
503
504 let padding_bottom = match cx.padding_bottom() {
505 Units::Pixels(val) => val,
506 _ => 0.0,
507 };
508
509 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
510
511 let padding_left = match cx.padding_left() {
512 Units::Pixels(val) => val,
513 _ => 0.0,
514 };
515
516 let x = bounds.x + padding_left + cursor_rect.rect.left + left;
517 let y = bounds.y + padding_top + cursor_rect.rect.top + top;
518
519 let x2 = x + (cursor_rect.rect.right - cursor_rect.rect.left);
520 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
521
522 let mut paint = Paint::default();
523 paint.set_anti_alias(true);
524 paint.set_style(PaintStyle::Fill);
525 paint.set_color(cx.selection_color());
526
527 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
528 }
529 }
530 }
531 }
532 }
533
534 pub fn draw_text_caret(&self, cx: &mut DrawContext, canvas: &Canvas) {
536 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
537 if let Some(text) = cx.style.text.get(cx.current) {
538 let bounds = cx.bounds();
539
540 let current = text.current_grapheme_offset(self.selection.active);
541
542 let rects = paragraph.get_rects_for_range(
543 current..current + 1,
544 RectHeightStyle::Tight,
545 RectWidthStyle::Tight,
546 );
547
548 let cursor_rect = rects.first().unwrap();
549
550 let alignment = cx.alignment();
551
552 let (mut top, _) = match alignment {
553 Alignment::TopLeft => (0.0, 0.0),
554 Alignment::TopCenter => (0.0, 0.5),
555 Alignment::TopRight => (0.0, 1.0),
556 Alignment::Left => (0.5, 0.0),
557 Alignment::Center => (0.5, 0.5),
558 Alignment::Right => (0.5, 1.0),
559 Alignment::BottomLeft => (1.0, 0.0),
560 Alignment::BottomCenter => (1.0, 0.5),
561 Alignment::BottomRight => (1.0, 1.0),
562 };
563
564 let padding_top = match cx.padding_top() {
565 Units::Pixels(val) => val,
566 _ => 0.0,
567 };
568
569 let padding_bottom = match cx.padding_bottom() {
570 Units::Pixels(val) => val,
571 _ => 0.0,
572 };
573
574 top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
575
576 let padding_left = match cx.padding_left() {
577 Units::Pixels(val) => val,
578 _ => 0.0,
579 };
580
581 let padding_right = match cx.padding_right() {
582 Units::Pixels(val) => val,
583 _ => 0.0,
584 };
585
586 let x = (bounds.x + padding_left + cursor_rect.rect.left).round();
587 let y = (bounds.y + padding_top + cursor_rect.rect.top + top).round();
588
589 let x2 = x + 1.0;
590 let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
591
592 let mut paint = Paint::default();
593 paint.set_anti_alias(true);
594 paint.set_style(PaintStyle::Fill);
595 paint.set_color(cx.caret_color());
596
597 canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
598
599 let mut transform = self.transform.borrow_mut();
600
601 let text_bounds = cx.text_context.text_bounds.get(cx.current).unwrap();
602
603 let mut bounds = cx.bounds();
604 bounds =
605 bounds.shrink_sides(padding_left, padding_top, padding_right, padding_bottom);
606
607 let (tx, ty) =
608 enforce_text_bounds(text_bounds, &bounds, (transform.0, transform.1));
609
610 let caret_box = BoundingBox::from_min_max(x, y, x2, y2);
611
612 let (new_tx, new_ty) = ensure_visible(&caret_box, &bounds, (tx, ty));
613
614 if new_tx != transform.0 || new_ty != transform.1 {
615 *transform = (new_tx, new_ty);
616 cx.needs_redraw();
617 }
618 }
619 }
620 }
621}
622
623impl<L: Lens> Handle<'_, Textbox<L>> {
624 pub fn on_edit<F>(self, callback: F) -> Self
628 where
629 F: 'static + Fn(&mut EventContext, String) + Send + Sync,
630 {
631 self.modify(|textbox: &mut Textbox<L>| textbox.on_edit = Some(Box::new(callback)))
632 }
633
634 pub fn on_submit<F>(self, callback: F) -> Self
639 where
640 F: 'static + Fn(&mut EventContext, L::Target, bool) + Send + Sync,
641 {
642 self.modify(|textbox: &mut Textbox<L>| textbox.on_submit = Some(Box::new(callback)))
643 }
644
645 pub fn on_blur<F>(self, callback: F) -> Self
647 where
648 F: 'static + Fn(&mut EventContext) + Send + Sync,
649 {
650 self.modify(|textbox: &mut Textbox<L>| textbox.on_blur = Some(Box::new(callback)))
651 }
652
653 pub fn on_cancel<F>(self, callback: F) -> Self
655 where
656 F: 'static + Fn(&mut EventContext) + Send + Sync,
657 {
658 self.modify(|textbox: &mut Textbox<L>| textbox.on_cancel = Some(Box::new(callback)))
659 }
660
661 pub fn validate<F>(self, is_valid: F) -> Self
665 where
666 F: 'static + Fn(&L::Target) -> bool + Send + Sync,
667 {
668 self.modify(|textbox| textbox.validate = Some(Box::new(is_valid)))
669 }
670
671 pub fn placeholder<P: ToStringLocalized>(self, text: impl Res<P>) -> Self {
673 text.set_or_bind(self.cx, self.entity, move |cx, val| {
674 let txt = val.get(cx).to_string_local(cx);
675 cx.emit(TextEvent::SetPlaceholder(txt.clone()));
676 cx.style.name.insert(cx.current, txt);
677 cx.needs_relayout();
678 cx.needs_redraw(self.entity);
679 });
680
681 self
682 }
683}
684
685impl<L> View for Textbox<L>
686where
687 L: Lens<Target: Data + ToStringLocalized + std::str::FromStr>,
688{
689 fn element(&self) -> Option<&'static str> {
690 Some("textbox")
691 }
692
693 fn accessibility(&self, cx: &mut AccessContext, node: &mut AccessNode) {
694 let _bounds = cx.bounds();
695
696 let node_id = node.node_id();
697
698 let mut _selection = self.selection;
699
700 let mut _current_cursor = 0;
706 let mut _prev_line_index = usize::MAX;
707
708 if let Some(_text) = cx.style.text.get(cx.current) {
709 if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
710 let line_metrics = paragraph.get_line_metrics();
711 for line in line_metrics.iter() {
712 let mut line_node = AccessNode::new_from_parent(node_id, line.line_number);
714 line_node.set_role(Role::TextInput);
715 line_node.set_bounds(BoundingBox {
716 x: line.left as f32,
717 y: (line.baseline - line.ascent) as f32,
718 w: line.width as f32,
719 h: line.height as f32,
720 });
721 let mut character_lengths = Vec::new();
728 let mut character_positions = Vec::new();
729 let mut character_widths = Vec::new();
730
731 let mut glyph_pos = line.start_index;
739
740 for _ in line.start_index..line.end_index {
741 if let Some(cluster_info) = paragraph.get_glyph_cluster_at(glyph_pos) {
742 let length =
743 cluster_info.text_range.end - cluster_info.text_range.start;
744
745 let position = cluster_info.bounds.left();
748 let width = cluster_info.bounds.width();
749
750 character_lengths.push(length as u8);
751 character_positions.push(position);
752 character_widths.push(width);
753
754 glyph_pos += length;
755
756 }
760 }
761
762 line_node.set_character_lengths(character_lengths.into_boxed_slice());
777 line_node.set_character_positions(character_positions.into_boxed_slice());
778 line_node.set_character_widths(character_widths.into_boxed_slice());
779 node.add_child(line_node);
819
820 }
823 }
824 }
825
826 }
837
838 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
839 event.map(|window_event, meta| match window_event {
841 WindowEvent::MouseDown(MouseButton::Left) => {
842 if meta.origin == cx.current {
843 return;
844 }
845
846 if cx.is_over() {
847 if !cx.is_disabled() {
848 cx.focus_with_visibility(false);
849 cx.capture();
850 cx.set_checked(true);
851 cx.lock_cursor_icon();
852
853 if !self.edit {
854 cx.emit(TextEvent::StartEdit);
855 }
856 self.reset_caret_timer(cx);
857 cx.emit(TextEvent::Hit(
858 cx.mouse.cursor_x,
859 cx.mouse.cursor_y,
860 cx.modifiers.shift(),
861 ));
862 }
863 } else {
864 cx.emit(TextEvent::Submit(false));
865 cx.release();
866 cx.set_checked(false);
867
868 cx.event_queue.push_back(
870 Event::new(WindowEvent::MouseDown(MouseButton::Left)).target(cx.hovered()),
871 );
872 cx.event_queue.push_back(
873 Event::new(WindowEvent::PressDown { mouse: true }).target(cx.hovered()),
874 );
875 }
876 }
877
878 WindowEvent::FocusIn => {
879 if cx.mouse.left.pressed != cx.current()
880 || cx.mouse.left.state == MouseButtonState::Released
881 {
882 cx.emit(TextEvent::StartEdit);
883 }
884 }
885
886 WindowEvent::FocusOut => {
887 cx.emit(TextEvent::EndEdit);
888 }
889
890 WindowEvent::MouseDoubleClick(MouseButton::Left) => {
891 cx.emit(TextEvent::SelectWord);
892 }
893
894 WindowEvent::MouseTripleClick(MouseButton::Left) => {
895 cx.emit(TextEvent::SelectParagraph);
896 }
897
898 WindowEvent::MouseUp(MouseButton::Left) => {
899 self.reset_caret_timer(cx);
900 cx.unlock_cursor_icon();
901 cx.release();
902 }
903
904 WindowEvent::MouseMove(x, y) => {
905 if cx.mouse.left.state == MouseButtonState::Pressed
906 && cx.mouse.left.pressed == cx.current
907 {
908 if self.edit {
909 self.reset_caret_timer(cx);
910 }
911 if cx.mouse.left.pos_down.0 != *x || cx.mouse.left.pos_down.1 != *y {
912 cx.emit(TextEvent::Drag(cx.mouse.cursor_x, cx.mouse.cursor_y));
913 }
914 }
915 }
916
917 WindowEvent::MouseScroll(x, y) => {
918 cx.emit(TextEvent::Scroll(*x, *y));
919 }
920
921 WindowEvent::CharInput(c) => {
922 if *c != '\u{1b}' && *c != '\u{8}' && *c != '\u{9}' && *c != '\u{7f}' && *c != '\u{0d}' && !cx.modifiers.ctrl() &&
928 !cx.modifiers.logo() &&
929 self.edit &&
930 !cx.is_read_only()
931 {
932 self.reset_caret_timer(cx);
933 cx.emit(TextEvent::InsertText(String::from(*c)));
934 }
935 }
936
937 WindowEvent::KeyDown(code, _) => match code {
938 Code::Enter => {
939 if matches!(self.kind, TextboxKind::SingleLine) {
940 cx.emit(TextEvent::Submit(true));
941 } else if !cx.is_read_only() {
942 self.reset_caret_timer(cx);
943 cx.emit(TextEvent::InsertText("\n".to_owned()));
944 }
945 }
946
947 Code::Space => {
948 cx.emit(TextEvent::InsertText(String::from(" ")));
949 }
950
951 Code::ArrowLeft => {
952 self.reset_caret_timer(cx);
953 let movement = if cx.modifiers.ctrl() {
954 Movement::Word(Direction::Left)
955 } else {
956 Movement::Grapheme(Direction::Left)
957 };
958
959 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
960 }
961
962 Code::ArrowRight => {
963 self.reset_caret_timer(cx);
964
965 let movement = if cx.modifiers.ctrl() {
966 Movement::Word(Direction::Right)
967 } else {
968 Movement::Grapheme(Direction::Right)
969 };
970
971 cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
972 }
973
974 Code::ArrowUp => {
975 self.reset_caret_timer(cx);
976 if self.kind != TextboxKind::SingleLine {
977 cx.emit(TextEvent::MoveCursor(
978 Movement::Vertical(VerticalMovement::LineUp),
979 cx.modifiers.shift(),
980 ));
981 }
982 }
983
984 Code::ArrowDown => {
985 self.reset_caret_timer(cx);
986 if self.kind != TextboxKind::SingleLine {
987 cx.emit(TextEvent::MoveCursor(
988 Movement::Vertical(VerticalMovement::LineDown),
989 cx.modifiers.shift(),
990 ));
991 }
992 }
993
994 Code::Backspace => {
995 self.reset_caret_timer(cx);
996 if !cx.is_read_only() {
997 if cx.modifiers.ctrl() {
998 cx.emit(TextEvent::DeleteText(Movement::Word(Direction::Upstream)));
999 } else {
1000 cx.emit(TextEvent::DeleteText(Movement::Grapheme(Direction::Upstream)));
1001 }
1002 }
1003 }
1004
1005 Code::Delete => {
1006 self.reset_caret_timer(cx);
1007 if !cx.is_read_only() {
1008 if cx.modifiers.ctrl() {
1009 cx.emit(TextEvent::DeleteText(Movement::Word(Direction::Downstream)));
1010 } else {
1011 cx.emit(TextEvent::DeleteText(Movement::Grapheme(
1012 Direction::Downstream,
1013 )));
1014 }
1015 }
1016 }
1017
1018 Code::Escape => {
1019 if let Some(callback) = &self.on_cancel {
1020 (callback)(cx);
1021 } else {
1022 cx.emit(TextEvent::EndEdit);
1023 }
1024 }
1025
1026 Code::Home => {
1027 self.reset_caret_timer(cx);
1028 cx.emit(TextEvent::MoveCursor(Movement::LineStart, cx.modifiers.shift()));
1029 }
1030
1031 Code::End => {
1032 self.reset_caret_timer(cx);
1033 cx.emit(TextEvent::MoveCursor(Movement::LineEnd, cx.modifiers.shift()));
1034 }
1035
1036 Code::PageUp | Code::PageDown => {
1037 self.reset_caret_timer(cx);
1038 let direction = if *code == Code::PageUp {
1039 Direction::Upstream
1040 } else {
1041 Direction::Downstream
1042 };
1043 cx.emit(TextEvent::MoveCursor(
1044 if cx.modifiers.ctrl() {
1045 Movement::Body(direction)
1046 } else {
1047 Movement::Page(direction)
1048 },
1049 cx.modifiers.shift(),
1050 ));
1051 }
1052
1053 Code::KeyA => {
1054 #[cfg(target_os = "macos")]
1055 let modifier = Modifiers::SUPER;
1056 #[cfg(not(target_os = "macos"))]
1057 let modifier = Modifiers::CTRL;
1058
1059 if cx.modifiers == &modifier {
1060 cx.emit(TextEvent::SelectAll);
1061 }
1062 }
1063
1064 Code::KeyC => {
1065 #[cfg(target_os = "macos")]
1066 let modifier = Modifiers::SUPER;
1067 #[cfg(not(target_os = "macos"))]
1068 let modifier = Modifiers::CTRL;
1069
1070 if cx.modifiers == &modifier {
1071 cx.emit(TextEvent::Copy);
1072 }
1073 }
1074
1075 Code::KeyV => {
1076 #[cfg(target_os = "macos")]
1077 let modifier = Modifiers::SUPER;
1078 #[cfg(not(target_os = "macos"))]
1079 let modifier = Modifiers::CTRL;
1080
1081 if cx.modifiers == &modifier {
1082 cx.emit(TextEvent::Paste);
1083 }
1084 }
1085
1086 Code::KeyX => {
1087 #[cfg(target_os = "macos")]
1088 let modifier = Modifiers::SUPER;
1089 #[cfg(not(target_os = "macos"))]
1090 let modifier = Modifiers::CTRL;
1091
1092 if cx.modifiers == &modifier && !cx.is_read_only() {
1093 cx.emit(TextEvent::Cut);
1094 }
1095 }
1096
1097 _ => {}
1098 },
1099
1100 WindowEvent::ActionRequest(ActionRequest {
1101 action: accesskit::Action::SetTextSelection,
1102 target: _,
1103 data: Some(ActionData::SetTextSelection(_selection)),
1104 }) => {
1105 }
1153
1154 _ => {}
1155 });
1156
1157 event.map(|text_event, _| match text_event {
1159 TextEvent::InsertText(text) => {
1160 if self.show_placeholder {
1161 self.reset_text(cx);
1162 }
1163
1164 self.insert_text(cx, text);
1165
1166 let text = self.clone_text(cx);
1167
1168 if let Ok(value) = &text.parse::<L::Target>() {
1169 if let Some(validate) = &self.validate {
1170 cx.set_valid(validate(value));
1171 } else {
1172 cx.set_valid(true);
1173 }
1174 } else {
1175 cx.set_valid(false);
1176 }
1177
1178 if self.edit {
1179 if let Some(callback) = &self.on_edit {
1180 (callback)(cx, text);
1181 }
1182 }
1183 }
1184
1185 TextEvent::Clear => {
1186 self.reset_text(cx);
1187 cx.needs_relayout();
1189 cx.needs_redraw();
1190 }
1191
1192 TextEvent::DeleteText(movement) => {
1193 if self.edit {
1194 self.delete_text(cx, *movement);
1195
1196 let text = self.clone_text(cx);
1197
1198 if let Ok(value) = &text.parse::<L::Target>() {
1199 if let Some(validate) = &self.validate {
1200 cx.set_valid(validate(value));
1201 } else {
1202 cx.set_valid(true);
1203 }
1204 } else {
1205 cx.set_valid(false);
1206 }
1207
1208 if let Some(callback) = &self.on_edit {
1209 (callback)(cx, text);
1210 }
1211 }
1212 }
1213
1214 TextEvent::MoveCursor(movement, selection) => {
1215 if self.edit && !self.show_placeholder {
1216 self.move_cursor(cx, *movement, *selection);
1217 }
1218 }
1219
1220 TextEvent::SetPlaceholder(text) => self.placeholder.clone_from(text),
1221
1222 TextEvent::StartEdit => {
1223 if !cx.is_disabled() && !self.edit {
1224 self.edit = true;
1225 cx.focus_with_visibility(false);
1226 cx.capture();
1227 cx.set_checked(true);
1228 self.reset_caret_timer(cx);
1229
1230 let text = self.lens.get(cx);
1231 let text = text.to_string_local(cx);
1232
1233 if text.is_empty() {
1234 self.show_placeholder = true;
1235 self.selection = Selection::caret(0);
1236 } else {
1237 self.select_all(cx);
1238 }
1239
1240 if let Ok(value) = &text.parse::<L::Target>() {
1241 if let Some(validate) = &self.validate {
1242 cx.set_valid(validate(value));
1243 } else {
1244 cx.set_valid(true);
1245 }
1246 } else {
1247 cx.set_valid(false);
1248 }
1249 }
1250 }
1251
1252 TextEvent::EndEdit => {
1253 self.deselect();
1254 self.edit = false;
1255 cx.set_checked(false);
1256 cx.release();
1257 cx.stop_timer(self.caret_timer);
1258
1259 let text = self.lens.get(cx);
1260 let text = text.to_string_local(cx);
1261
1262 self.select_all(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 }
1283
1284 TextEvent::Blur => {
1285 cx.set_checked(false);
1286 if let Some(callback) = &self.on_blur {
1287 (callback)(cx);
1288 } else {
1289 cx.emit(TextEvent::Submit(false));
1290 cx.emit(TextEvent::EndEdit);
1291 }
1292 }
1293
1294 TextEvent::Submit(reason) => {
1295 if let Some(callback) = &self.on_submit {
1296 if cx.is_valid() {
1297 let text = self.clone_text(cx);
1298 if let Ok(value) = text.parse::<L::Target>() {
1299 (callback)(cx, value, *reason);
1300 }
1301 }
1302 }
1303 }
1304
1305 TextEvent::SelectAll => {
1306 self.select_all(cx);
1307 }
1308
1309 TextEvent::SelectWord => {
1310 self.select_word(cx);
1311 }
1312
1313 TextEvent::SelectParagraph => {
1314 self.select_paragraph(cx);
1315 }
1316
1317 TextEvent::Hit(posx, posy, selection) => {
1318 if !self.show_placeholder {
1319 self.hit(cx, *posx, *posy, *selection);
1320 }
1321 }
1322
1323 TextEvent::Drag(posx, posy) => {
1324 if !self.show_placeholder {
1325 self.drag(cx, *posx, *posy);
1326 }
1327 }
1328
1329 TextEvent::Scroll(_x, _y) => {
1330 }
1332
1333 TextEvent::Copy =>
1334 {
1335 #[cfg(feature = "clipboard")]
1336 if self.edit {
1337 if let Some(selected_text) = self.clone_selected(cx) {
1338 if !selected_text.is_empty() {
1339 cx.set_clipboard(selected_text)
1340 .expect("Failed to add text to clipboard");
1341 }
1342 }
1343 }
1344 }
1345
1346 TextEvent::Paste =>
1347 {
1348 #[cfg(feature = "clipboard")]
1349 if self.edit {
1350 if let Ok(text) = cx.get_clipboard() {
1351 cx.emit(TextEvent::InsertText(text));
1352 }
1353 }
1354 }
1355
1356 TextEvent::Cut =>
1357 {
1358 #[cfg(feature = "clipboard")]
1359 if self.edit {
1360 if let Some(selected_text) = self.clone_selected(cx) {
1361 if !selected_text.is_empty() {
1362 cx.set_clipboard(selected_text)
1363 .expect("Failed to add text to clipboard");
1364 self.delete_text(cx, Movement::Grapheme(Direction::Upstream));
1365
1366 let text = self.clone_text(cx);
1367
1368 if let Ok(value) = &text.parse::<L::Target>() {
1369 if let Some(validate) = &self.validate {
1370 cx.set_valid(validate(value));
1371 } else {
1372 cx.set_valid(true);
1373 }
1374 } else {
1375 cx.set_valid(false);
1376 }
1377
1378 if let Some(callback) = &self.on_edit {
1379 (callback)(cx, text);
1380 }
1381 }
1382 }
1383 }
1384 }
1385
1386 TextEvent::ToggleCaret => {
1387 self.show_caret ^= true;
1388 }
1389 });
1390 }
1391
1392 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
1394 cx.draw_shadows(canvas);
1395 cx.draw_background(canvas);
1396 cx.draw_border(canvas);
1397 cx.draw_outline(canvas);
1398 canvas.save();
1399 let transform = *self.transform.borrow();
1400 canvas.translate((transform.0, transform.1));
1401 cx.draw_text(canvas);
1403 if self.edit {
1404 self.draw_selection(cx, canvas);
1405 self.draw_text_caret(cx, canvas);
1406 }
1407 canvas.restore();
1408 }
1409}