makepad_widgets/
text_input.rs

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] label_walk: Walk,
65    
66    #[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 bind_read(&mut self, _cx: &mut Cx, nodes: &[LiveNode]) {
87        
88        if let Some(LiveValue::Float(v)) = nodes.read_path(&self.bind) {
89            self.set_internal(*v as f32);
90        }
91    }*/
92    
93    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            // cursor insertion point
224            if i == left {
225                inserted = true;
226                for c in inp.chars() {
227                    chars_inserted += 1;
228                    new.push(c);
229                }
230            }
231            // outside of the selection so copy
232            if i < left || i >= right {
233                new.push(c);
234            }
235        }
236        if !inserted { // end of string or empty string
237            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                    // some day someone is going to search for this for days
308                    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                // select all
336                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                    // if this one follows a space, it still needs to eat it
361                    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                    // dispatch_action(cx, self, TextInputAction::Tab(key.mod_shift));
381                }
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                    // we need to figure out what is below our current cursor
433                    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                    // we need to figure out what is below our current cursor
446                    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                // ok so we need to calculate where we put the cursor down.
504                //elf.
505                if let Some(pos) = self.draw_text.closest_offset(cx, fe.abs) {
506                    //log!("{} {}", pos, fe.abs);
507                    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                        // lets select the word.
519                        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                        // ok so. now we do a word select and merge the selection
554                        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        // this makes sure selection goes behind the text
587        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        // move the IME
613        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        // draw selection rects
626        
627        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            // ok so. if we have the IME we should inject a tracking point
641            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}