1use {
2 crate::{
3 makepad_derive_widget::*,
4 makepad_draw::*,
5 widget::*,
6 }
7};
8
9live_design!{
10 DrawLabel = {{DrawLabel}} {}
11 TextInputBase = {{TextInput}} {}
12}
13
14#[derive(Clone)]
15struct UndoItem {
16 text: String,
17 undo_group: UndoGroup,
18 cursor_head: usize,
19 cursor_tail: usize
20}
21
22#[derive(PartialEq, Copy, Clone)]
23pub enum UndoGroup {
24 TextInput(u64),
25 Backspace(u64),
26 Delete(u64),
27 External(u64),
28 Cut(u64),
29}
30
31
32#[derive(Live, LiveHook)]
33#[repr(C)]
34pub struct DrawLabel {
35 #[deref] draw_super: DrawText,
36 #[live] is_empty: f32,
37}
38
39
40#[derive(Live)]
41pub struct TextInput {
42 #[animator] animator: Animator,
43
44 #[live] draw_bg: DrawColor,
45 #[live] draw_select: DrawQuad,
46 #[live] draw_cursor: DrawQuad,
47 #[live] draw_text: DrawLabel,
48
49 #[walk] walk: Walk,
50 #[layout] layout: Layout,
51
52 #[live] label_align: Align,
53
54 #[live] cursor_size: f64,
55 #[live] cursor_margin_bottom: f64,
56 #[live] cursor_margin_top: f64,
57 #[live] select_pad_edges: f64,
58 #[live] empty_message: String,
59 #[live] numeric_only: bool,
60 #[live] secret: bool,
61 #[live] on_focus_select_all: bool,
62 #[live] pub read_only: bool,
63
64 #[live] pub text: String,
67 #[live] ascii_only: bool,
68 #[rust] double_tap_start: Option<(usize, usize)>,
69 #[rust] undo_id: u64,
70
71 #[rust] last_undo: Option<UndoItem>,
72 #[rust] undo_stack: Vec<UndoItem>,
73 #[rust] redo_stack: Vec<UndoItem>,
74 #[rust] cursor_tail: usize,
75 #[rust] cursor_head: usize
76}
77
78impl LiveHook for TextInput {
79 fn before_live_design(cx: &mut Cx) {
80 register_widget!(cx, TextInput)
81 }
82}
83
84impl Widget for TextInput {
85 fn widget_uid(&self) -> WidgetUid {return WidgetUid(self as *const _ as u64)}
86 fn redraw(&mut self, cx: &mut Cx) {
94 self.draw_bg.redraw(cx);
95 }
96
97 fn handle_widget_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, WidgetActionItem)) {
98 let uid = self.widget_uid();
99 self.handle_event_with(cx, event, &mut | cx, action | {
100 dispatch_action(cx, WidgetActionItem::new(action.into(), uid))
101 });
102 }
103
104 fn walk(&mut self, _cx:&mut Cx) -> Walk {self.walk}
105
106 fn draw_walk_widget(&mut self, cx: &mut Cx2d, walk: Walk) -> WidgetDraw {
107 self.draw_walk(cx, walk);
108 WidgetDraw::done()
109 }
110
111
112 fn text(&self) -> String {
113 self.text.clone()
114 }
115
116 fn set_text(&mut self, v: &str) {
117 self.filter_input(&v, None);
118 }
119}
120
121#[derive(Clone, PartialEq, WidgetAction)]
122pub enum TextInputAction {
123 Change(String),
124 Return(String),
125 Escape,
126 KeyFocus,
127 KeyFocusLost,
128 None
129}
130
131impl TextInput {
132
133 pub fn sorted_cursor(&self) -> (usize, usize) {
134 if self.cursor_head < self.cursor_tail {
135 (self.cursor_head, self.cursor_tail)
136 }
137 else {
138 (self.cursor_tail, self.cursor_head)
139 }
140 }
141
142 pub fn selected_text(&mut self) -> String {
143 let mut ret = String::new();
144 let (left, right) = self.sorted_cursor();
145 for (i, c) in self.text.chars().enumerate() {
146 if i >= left && i< right {
147 ret.push(c);
148 }
149 if i >= right {
150 break;
151 }
152 }
153 ret
154 }
155
156 fn consume_undo_item(&mut self, item: UndoItem) {
157 self.text = item.text;
158 self.cursor_head = item.cursor_head;
159 self.cursor_tail = item.cursor_tail;
160 }
161
162 pub fn undo(&mut self) {
163 if let Some(item) = self.undo_stack.pop() {
164 let redo_item = self.create_undo_item(item.undo_group);
165 self.consume_undo_item(item.clone());
166 self.redo_stack.push(redo_item);
167 }
168 }
169
170 pub fn redo(&mut self) {
171 if let Some(item) = self.redo_stack.pop() {
172 let undo_item = self.create_undo_item(item.undo_group);
173 self.consume_undo_item(item.clone());
174 self.undo_stack.push(undo_item);
175 }
176 }
177
178 pub fn select_all(&mut self) {
179 self.cursor_tail = 0;
180 self.cursor_head = self.text.chars().count();
181 }
182
183 fn create_undo_item(&mut self, undo_group: UndoGroup) -> UndoItem {
184 UndoItem {
185 undo_group: undo_group,
186 text: self.text.clone(),
187 cursor_head: self.cursor_head,
188 cursor_tail: self.cursor_tail
189 }
190 }
191
192 pub fn create_external_undo(&mut self) {
193 self.create_undo(UndoGroup::External(self.undo_id))
194 }
195
196 pub fn create_undo(&mut self, undo_group: UndoGroup) {
197 if self.read_only {
198 return
199 }
200 self.redo_stack.clear();
201 let new_item = self.create_undo_item(undo_group);
202 if let Some(item) = self.undo_stack.last_mut() {
203 if item.undo_group != undo_group {
204 self.last_undo = Some(new_item.clone());
205 self.undo_stack.push(new_item);
206 }
207 else {
208 self.last_undo = Some(new_item);
209 }
210 }
211 else {
212 self.last_undo = Some(new_item.clone());
213 self.undo_stack.push(new_item);
214 }
215 }
216
217 pub fn replace_text(&mut self, inp: &str) {
218 let mut new = String::new();
219 let (left, right) = self.sorted_cursor();
220 let mut chars_inserted = 0;
221 let mut inserted = false;
222 for (i, c) in self.text.chars().enumerate() {
223 if i == left {
225 inserted = true;
226 for c in inp.chars() {
227 chars_inserted += 1;
228 new.push(c);
229 }
230 }
231 if i < left || i >= right {
233 new.push(c);
234 }
235 }
236 if !inserted { for c in inp.chars() {
238 chars_inserted += 1;
239 new.push(c);
240 }
241 }
242 self.cursor_head = left + chars_inserted;
243 self.cursor_tail = self.cursor_head;
244 self.text = new;
245 }
246
247 pub fn select_word(&mut self, around: usize) {
248 let mut first_ws = Some(0);
249 let mut last_ws = None;
250 let mut after_center = false;
251 for (i, c) in self.text.chars().enumerate() {
252 last_ws = Some(i + 1);
253 if i >= around {
254 after_center = true;
255 }
256 if c.is_whitespace() {
257 last_ws = Some(i);
258 if after_center {
259 break;
260 }
261 first_ws = Some(i + 1);
262 }
263 }
264 if let Some(first_ws) = first_ws {
265 if let Some(last_ws) = last_ws {
266 self.cursor_tail = first_ws;
267 self.cursor_head = last_ws;
268 }
269 }
270 }
271
272 pub fn change(&mut self, cx: &mut Cx, s: &str, dispatch_action: &mut dyn FnMut(&mut Cx, TextInputAction)) {
273 if self.read_only {
274 return
275 }
276 self.replace_text(s);
277 dispatch_action(cx, TextInputAction::Change(self.text.clone()));
278 self.draw_bg.redraw(cx);
279 }
280
281 pub fn set_key_focus(&self, cx: &mut Cx) {
282 cx.set_key_focus(self.draw_bg.area());
283 }
284
285 pub fn filter_input(&mut self, input: &str, output: Option<&mut String>) {
286 let output = if let Some(output) = output {
287 output
288 }
289 else {
290 &mut self.text
291 };
292 output.clear();
293 if self.ascii_only {
294 for c in input.as_bytes() {
295 if *c>31 && *c<127 {
296 output.push(*c as char);
297 }
298 }
299 }
300 else if self.numeric_only {
301 let mut output = String::new();
302 for c in input.chars() {
303 if c.is_ascii_digit() || c == '.' {
304 output.push(c);
305 }
306 else if c == ',' {
307 output.push('.');
309 }
310 }
311 }
312 else {
313 output.push_str(input);
314 }
315 }
316
317 pub fn handle_event(&mut self, cx: &mut Cx, event: &Event) -> Vec<TextInputAction> {
318 let mut actions = Vec::new();
319 self.handle_event_with(cx, event, &mut | _, a | actions.push(a));
320 actions
321 }
322
323 pub fn handle_event_with(&mut self, cx: &mut Cx, event: &Event, dispatch_action: &mut dyn FnMut(&mut Cx, TextInputAction)) {
324 self.animator_handle_event(cx, event);
325 match event.hits(cx, self.draw_bg.area()) {
326 Hit::KeyFocusLost(_) => {
327 self.animator_play(cx, id!(focus.off));
328 cx.hide_text_ime();
329 dispatch_action(cx, TextInputAction::Return(self.text.clone()));
330 dispatch_action(cx, TextInputAction::KeyFocusLost);
331 }
332 Hit::KeyFocus(_) => {
333 self.undo_id += 1;
334 self.animator_play(cx, id!(focus.on));
335 if self.on_focus_select_all {
337 self.select_all();
338 }
339 self.draw_bg.redraw(cx);
340 dispatch_action(cx, TextInputAction::KeyFocus);
341 }
342 Hit::TextInput(te) => {
343 let mut input = String::new();
344 self.filter_input(&te.input, Some(&mut input));
345 if input.len() == 0 {
346 return
347 }
348 let last_undo = self.last_undo.take();
349 if te.replace_last {
350 self.undo_id += 1;
351 self.create_undo(UndoGroup::TextInput(self.undo_id));
352 if let Some(item) = last_undo {
353 self.consume_undo_item(item);
354 }
355 }
356 else {
357 if input == " " {
358 self.undo_id += 1;
359 }
360 self.create_undo(UndoGroup::TextInput(self.undo_id));
362 }
363 self.change(cx, &input, dispatch_action);
364 }
365 Hit::TextCopy(ce) => {
366 self.undo_id += 1;
367 *ce.response.borrow_mut() = Some(self.selected_text());
368 }
369 Hit::TextCut(tc) => {
370 self.undo_id += 1;
371 if self.cursor_head != self.cursor_tail {
372 *tc.response.borrow_mut() = Some(self.selected_text());
373 self.create_undo(UndoGroup::Cut(self.undo_id));
374 self.change(cx, "", dispatch_action);
375 }
376 }
377 Hit::KeyDown(ke) => match ke.key_code {
378
379 KeyCode::Tab => {
380 }
382 KeyCode::ReturnKey => {
383 cx.hide_text_ime();
384 dispatch_action(cx, TextInputAction::Return(self.text.clone()));
385 },
386 KeyCode::Escape => {
387 dispatch_action(cx, TextInputAction::Escape);
388 },
389 KeyCode::KeyZ if ke.modifiers.logo || ke.modifiers.shift => {
390 if self.read_only {
391 return
392 }
393 self.undo_id += 1;
394 if ke.modifiers.shift {
395 self.redo();
396 }
397 else {
398 self.undo();
399 }
400 dispatch_action(cx, TextInputAction::Change(self.text.clone()));
401 self.draw_bg.redraw(cx);
402 }
403 KeyCode::KeyA if ke.modifiers.logo || ke.modifiers.control => {
404 self.undo_id += 1;
405 self.cursor_tail = 0;
406 self.cursor_head = self.text.chars().count();
407 self.draw_bg.redraw(cx);
408 }
409 KeyCode::ArrowLeft => if !ke.modifiers.logo {
410
411 self.undo_id += 1;
412 if self.cursor_head>0 {
413 self.cursor_head -= 1;
414 }
415 if !ke.modifiers.shift {
416 self.cursor_tail = self.cursor_head;
417 }
418 self.draw_bg.redraw(cx);
419 },
420 KeyCode::ArrowRight => if !ke.modifiers.logo {
421 self.undo_id += 1;
422 if self.cursor_head < self.text.chars().count() {
423 self.cursor_head += 1;
424 }
425 if !ke.modifiers.shift {
426 self.cursor_tail = self.cursor_head;
427 }
428 self.draw_bg.redraw(cx);
429 }
430 KeyCode::ArrowDown => if !ke.modifiers.logo {
431 self.undo_id += 1;
432 if let Some(pos) = self.draw_text.get_cursor_pos(cx, 0.0, self.cursor_head) {
434 if let Some(pos) = self.draw_text.closest_offset(cx, dvec2(pos.x, pos.y + self.draw_text.get_line_spacing() * 1.5)) {
435 self.cursor_head = pos;
436 if !ke.modifiers.shift {
437 self.cursor_tail = self.cursor_head;
438 }
439 self.draw_bg.redraw(cx);
440 }
441 }
442 },
443 KeyCode::ArrowUp => if !ke.modifiers.logo {
444 self.undo_id += 1;
445 if let Some(pos) = self.draw_text.get_cursor_pos(cx, 0.0, self.cursor_head) {
447 if let Some(pos) = self.draw_text.closest_offset(cx, dvec2(pos.x, pos.y - self.draw_text.get_line_spacing() * 0.5)) {
448 self.cursor_head = pos;
449 if !ke.modifiers.shift {
450 self.cursor_tail = self.cursor_head;
451 }
452 self.draw_bg.redraw(cx);
453 }
454 }
455 },
456 KeyCode::Home => if !ke.modifiers.logo {
457 self.undo_id += 1;
458 self.cursor_head = 0;
459 if !ke.modifiers.shift {
460 self.cursor_tail = self.cursor_head;
461 }
462 self.draw_bg.redraw(cx);
463 }
464 KeyCode::End => if !ke.modifiers.logo {
465 self.undo_id += 1;
466 self.cursor_head = self.text.chars().count();
467
468 if !ke.modifiers.shift {
469 self.cursor_tail = self.cursor_head;
470 }
471 self.draw_bg.redraw(cx);
472 }
473 KeyCode::Backspace => {
474 self.create_undo(UndoGroup::Backspace(self.undo_id));
475 if self.cursor_head == self.cursor_tail {
476 if self.cursor_tail > 0 {
477 self.cursor_tail -= 1;
478 }
479 }
480 self.change(cx, "", dispatch_action);
481 }
482 KeyCode::Delete => {
483 self.create_undo(UndoGroup::Delete(self.undo_id));
484 if self.cursor_head == self.cursor_tail {
485 if self.cursor_head < self.text.chars().count() {
486 self.cursor_head += 1;
487 }
488 }
489 self.change(cx, "", dispatch_action);
490 }
491 _ => ()
492 }
493 Hit::FingerHoverIn(_) => {
494 cx.set_cursor(MouseCursor::Text);
495 self.animator_play(cx, id!(hover.on));
496 }
497 Hit::FingerHoverOut(_) => {
498 self.animator_play(cx, id!(hover.off));
499 },
500 Hit::FingerDown(fe) => {
501 cx.set_cursor(MouseCursor::Text);
502 self.set_key_focus(cx);
503 if let Some(pos) = self.draw_text.closest_offset(cx, fe.abs) {
506 let pos = pos.min(self.text.chars().count());
508 if fe.tap_count == 1 {
509 if pos != self.cursor_head {
510 self.cursor_head = pos;
511 if !fe.modifiers.shift {
512 self.cursor_tail = pos;
513 }
514 }
515 self.draw_bg.redraw(cx);
516 }
517 if fe.tap_count == 2 {
518 self.select_word(pos);
520 self.double_tap_start = Some((self.cursor_head, self.cursor_tail));
521 }
522 if fe.tap_count == 3 {
523 self.select_all();
524 }
525 self.draw_bg.redraw(cx);
526 }
527 },
528 Hit::FingerUp(fe) => {
529 self.double_tap_start = None;
530 if let Some(pos) = self.draw_text.closest_offset(cx, fe.abs) {
531 let pos = pos.min(self.text.chars().count());
532 if !fe.modifiers.shift && fe.tap_count == 1 && fe.was_tap() {
533 self.cursor_head = pos;
534 self.cursor_tail = self.cursor_head;
535 self.draw_bg.redraw(cx);
536 }
537 }
538 if fe.was_long_press() {
539 cx.show_clipboard_actions(self.selected_text());
540 }
541 if fe.is_over && fe.device.has_hovers() {
542 self.animator_play(cx, id!(hover.on));
543 }
544 else {
545 self.animator_play(cx, id!(hover.off));
546 }
547 }
548 Hit::FingerMove(fe) => {
549 if let Some(pos) = self.draw_text.closest_offset(cx, fe.abs) {
550 let pos = pos.min(self.text.chars().count());
551 if fe.tap_count == 2 {
552 let (head, tail) = self.double_tap_start.unwrap();
553 self.select_word(pos);
555 if head > self.cursor_head {
556 self.cursor_head = head
557 }
558 if tail < self.cursor_tail {
559 self.cursor_tail = tail;
560 }
561 self.draw_bg.redraw(cx);
562 }
563 else if fe.tap_count == 1 {
564 if let Some(pos_start) = self.draw_text.closest_offset(cx, fe.abs_start) {
565 let pos_start = pos_start.min(self.text.chars().count());
566
567 self.cursor_head = pos_start;
568 self.cursor_tail = self.cursor_head;
569 }
570 if pos != self.cursor_head {
571 self.cursor_head = pos;
572 }
573 self.draw_bg.redraw(cx);
574 }
575 }
576 }
577 _ => ()
578 }
579 }
580
581 pub fn draw_walk(&mut self, cx: &mut Cx2d, walk: Walk) {
582
583 self.draw_bg.begin(cx, walk, self.layout);
584 let turtle_rect = cx.turtle().rect();
585
586 self.draw_select.append_to_draw_call(cx);
588
589 if self.text.len() == 0 {
590 self.draw_text.is_empty = 1.0;
591 self.draw_text.draw_walk(cx, Walk::size(self.walk.width, self.walk.height), self.label_align, &self.empty_message);
592 }
593 else {
594 self.draw_text.is_empty = 0.0;
595 if self.secret {
596 self.draw_text.draw_walk(cx, Walk::size(
597 self.walk.width,
598 self.walk.height
599 ), self.label_align, &"*".repeat(self.text.len()));
600 }
601 else {
602 self.draw_text.draw_walk(cx, Walk::size(
603 self.walk.width,
604 self.walk.height
605 ), self.label_align, &self.text);
606 }
607 }
608
609 let mut turtle = cx.turtle().padded_rect_used();
610 turtle.pos.y -= self.cursor_margin_top;
611 turtle.size.y += self.cursor_margin_top + self.cursor_margin_bottom;
612 let line_spacing = self.draw_text.get_line_spacing();
614 let top_drop = self.draw_text.get_font_size() * 0.2;
615 let head = self.draw_text.get_cursor_pos(cx, 0.0, self.cursor_head)
616 .unwrap_or(dvec2(turtle.pos.x, 0.0));
617
618 if !self.read_only && self.cursor_head == self.cursor_tail {
619 self.draw_cursor.draw_abs(cx, Rect {
620 pos: dvec2(head.x - 0.5 * self.cursor_size, head.y - top_drop),
621 size: dvec2(self.cursor_size, line_spacing)
622 });
623 }
624
625 if self.cursor_head != self.cursor_tail {
628 let top_drop = self.draw_text.get_font_size() * 0.3;
629 let bottom_drop = self.draw_text.get_font_size() * 0.1;
630
631 let (start, end) = self.sorted_cursor();
632 let rects = self.draw_text.get_selection_rects(cx, start, end, dvec2(0.0, -top_drop), dvec2(0.0, bottom_drop));
633 for rect in rects {
634 self.draw_select.draw_abs(cx, rect);
635 }
636 }
637 self.draw_bg.end(cx);
638
639 if cx.has_key_focus(self.draw_bg.area()) {
640 let ime_x = self.draw_text.get_cursor_pos(cx, 0.5, self.cursor_head)
642 .unwrap_or(dvec2(turtle.pos.x, 0.0)).x;
643
644 if self.numeric_only {
645 cx.hide_text_ime();
646 }
647 else {
648 let ime_abs = dvec2(ime_x, turtle.pos.y);
649 cx.show_text_ime(self.draw_bg.area(), ime_abs - turtle_rect.pos);
650 }
651 }
652
653 cx.add_nav_stop(self.draw_bg.area(), NavRole::TextInput, Margin::default())
654 }
655}
656
657#[derive(Clone, PartialEq, WidgetRef)]
658pub struct TextInputRef(WidgetRef);
659
660impl TextInputRef {
661 pub fn changed(&self, actions: &WidgetActions) -> Option<String> {
662 if let Some(item) = actions.find_single_action(self.widget_uid()) {
663 if let TextInputAction::Change(val) = item.action() {
664 return Some(val);
665 }
666 }
667 None
668 }
669
670}