Skip to main content

appcui/ui/textfield/
textfield.rs

1use super::{
2    events::{EventData, TextFieldEventsType},
3    CharClass, Flags, Selection,
4};
5use crate::prelude::*;
6use crate::utils::GlyphParser;
7
8struct Cursor {
9    pos: usize,
10    start: usize,
11    end: usize,
12}
13
14#[allow(dead_code)]
15const MAX_UNDO_DEPTH: usize = 100;
16
17#[allow(dead_code)]
18#[derive(Copy, Clone)]
19enum LastAction {
20    None,
21    AddChar(CharClass),
22    Delete,
23    Other,
24}
25
26#[allow(dead_code)]
27#[derive(Clone)]
28struct TextSnapshot {
29    glyphs: String,
30    cursor_pos: usize,
31    selection: Selection,
32}
33
34#[CustomControl(overwrite=OnPaint+OnKeyPressed+OnMouseEvent+OnResize+OnFocus, internal=true)]
35pub struct TextField {
36    cursor: Cursor,
37    selection: Selection,
38    glyphs: String,
39    drag_started: bool,
40    flags: Flags,
41    undo_stack: Vec<TextSnapshot>,
42    redo_stack: Vec<TextSnapshot>,
43    last_action: LastAction,
44    history_suspended: bool,
45}
46impl TextField {
47    /// Creates a new TextField control with the specified text, layout and flags.
48    /// The flags can be a combination of the following values:
49    /// * `Flags::Readonly` - if set, the text field will be readonly
50    /// * `Flags::ProcessEnter` - if set, the text field will process the Enter key and raise an event
51    /// * `Flags::DisableAutoSelectOnFocus` - if set, the text field will not select all text when it gets focus
52    ///
53    /// # Example
54    /// ```rust, no_run
55    /// use appcui::prelude::*;
56    ///
57    /// let mut textfield = TextField::new("Hello World",
58    ///                                    layout!("x:1,y:1,w:20,h:1"),
59    ///                                    textfield::Flags::None);
60    /// ```
61    pub fn new(text: &str, layout: Layout, flags: Flags) -> Self {
62        let mut obj = Self {
63            base: ControlBase::with_status_flags(layout, StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput),
64            cursor: Cursor { pos: 0, start: 0, end: 0 },
65            selection: Selection::NONE,
66            glyphs: String::from(text),
67            drag_started: false,
68            flags,
69            undo_stack: Vec::new(),
70            redo_stack: Vec::new(),
71            last_action: LastAction::None,
72            history_suspended: false,
73        };
74        obj.set_size_bounds(3, 1, u16::MAX, u16::MAX);
75        obj.cursor.pos = obj.glyphs.len();
76        obj
77    }
78
79    /// Returns **true** if the TextField control is readonly, **false** otherwise.
80    #[inline(always)]
81    pub fn is_readonly(&self) -> bool {
82        self.flags.contains(Flags::Readonly)
83    }
84
85    /// Returns the text of the TextField control.
86    #[inline(always)]
87    pub fn text(&self) -> &str {
88        &self.glyphs
89    }
90
91    /// Sets the text of the TextField control.
92    #[inline(always)]
93    pub fn set_text(&mut self, text: &str) {
94        self.cursor = Cursor { pos: 0, start: 0, end: 0 };
95        self.selection = Selection::NONE;
96        self.glyphs.clear();
97        self.glyphs.push_str(text);
98        self.undo_stack.clear();
99        self.redo_stack.clear();
100        self.last_action = LastAction::None;
101        self.history_suspended = false;
102        self.move_cursor_to(self.glyphs.len(), false, true);
103    }
104
105    fn update_scroll_view(&mut self, force_end_update: bool) {
106        if (self.cursor.pos >= self.cursor.start) && (self.cursor.pos < self.cursor.end) {
107            // nothing to do --> curent pos is already in the view window
108            if force_end_update {
109                let sz = self.size();
110                let visible_glyphs = ((sz.width as usize) - 2) * (sz.height as usize);
111                self.cursor.end = self.glyphs.next_pos(self.cursor.start, visible_glyphs);
112            }
113            return;
114        }
115        let sz = self.size();
116        let visible_glyphs = (if sz.width > 2 {
117            ((sz.width as usize) - 2) * (sz.height as usize)
118        } else {
119            0
120        })
121        .max(1);
122
123        if self.cursor.pos < self.cursor.start {
124            // scroll to the left
125            self.cursor.start = self.cursor.pos;
126            self.cursor.end = self.glyphs.next_pos(self.cursor.pos, visible_glyphs);
127        } else {
128            // scroll to the right
129            self.cursor.start = self.glyphs.previous_pos(self.cursor.pos, visible_glyphs - 1);
130            // we add ONE to the end pot to satisfy (self.cursor.pos < self.cursor.end) condition
131            self.cursor.end = self.cursor.pos;
132        }
133    }
134    fn move_cursor_with(&mut self, no_of_glyphs: i32, select: bool) {
135        let new_poz = if no_of_glyphs >= 0 {
136            self.glyphs.next_pos(self.cursor.pos, no_of_glyphs as usize)
137        } else {
138            self.glyphs.previous_pos(self.cursor.pos, (-no_of_glyphs) as usize)
139        };
140        self.move_cursor_to(new_poz, select, false);
141    }
142    fn move_cursor_to(&mut self, new_offset: usize, select: bool, force_end_update: bool) {
143        let current_pos = self.cursor.pos;
144        self.cursor.pos = new_offset.min(self.glyphs.len());
145        self.update_scroll_view(force_end_update);
146        if select {
147            self.selection.update(current_pos, self.cursor.pos);
148        } else {
149            self.selection = Selection::NONE;
150        }
151    }
152    fn move_to_next_word(&mut self, select: bool) {
153        if let Some(char_class) = self.glyphs.glyph(self.cursor.pos).map(|c| CharClass::from(c.0)) {
154            let mut pos = self.cursor.pos;
155            let mut new_char_class = char_class;
156            // skip current class
157            while let Some((c, size)) = self.glyphs.glyph(pos) {
158                if CharClass::from(c) != char_class {
159                    new_char_class = CharClass::from(c);
160                    break;
161                }
162                pos += size as usize;
163            }
164            if (new_char_class != char_class) && (new_char_class == CharClass::Space) {
165                // skip the spaces until we reach a new char class
166                while let Some((c, size)) = self.glyphs.glyph(pos) {
167                    if CharClass::from(c) != new_char_class {
168                        break;
169                    }
170                    pos += size as usize;
171                }
172            }
173            pos = pos.min(self.glyphs.len());
174            self.move_cursor_to(pos, select, false);
175        }
176    }
177    fn move_to_previous_word(&mut self, select: bool) {
178        if let Some(char_class) = self.glyphs.previous_glyph(self.cursor.pos).map(|c| CharClass::from(c.0)) {
179            let mut pos = self.cursor.pos;
180            let mut new_char_class = char_class;
181            // skip current class
182            while let Some((c, size)) = self.glyphs.previous_glyph(pos) {
183                if CharClass::from(c) != char_class {
184                    new_char_class = CharClass::from(c);
185                    break;
186                }
187                if size as usize <= pos {
188                    pos -= size as usize;
189                } else {
190                    pos = 0;
191                    break;
192                }
193            }
194            if (new_char_class != char_class) && (char_class == CharClass::Space) {
195                // skip the the current class until I rech the start of it
196                while let Some((c, size)) = self.glyphs.previous_glyph(pos) {
197                    if CharClass::from(c) != new_char_class {
198                        break;
199                    }
200                    if size as usize <= pos {
201                        pos -= size as usize;
202                    } else {
203                        pos = 0;
204                        break;
205                    }
206                }
207            }
208            self.move_cursor_to(pos, select, false);
209        }
210    }
211    fn copy_text(&mut self) {
212        if !self.selection.is_empty() {
213            RuntimeManager::get()
214                .backend_mut()
215                .set_clipboard_text(&self.glyphs[self.selection.start..self.selection.end]);
216        }
217    }
218    // true if the text was changed, false otherwise
219    fn paste_text(&mut self) -> bool {
220        if self.is_readonly() {
221            return false;
222        }
223        let had_selection = !self.selection.is_empty();
224        let clipboard_text = RuntimeManager::get().backend().clipboard_text();
225        let has_clipboard_text = clipboard_text.as_ref().map(|txt| !txt.is_empty()).unwrap_or(false);
226        if !(had_selection || has_clipboard_text) {
227            return false;
228        }
229        if !self.history_suspended {
230            self.push_undo_snapshot();
231        }
232        let mut text_was_modified = false;
233        if had_selection {
234            let old_history_suspended = self.history_suspended;
235            self.history_suspended = true;
236            text_was_modified = self.delete_selection();
237            self.history_suspended = old_history_suspended;
238        }
239        if let Some(txt) = clipboard_text {
240            self.glyphs.insert_str(self.cursor.pos, &txt);
241            text_was_modified |= !txt.is_empty();
242            self.move_cursor_to(self.cursor.pos + txt.len(), false, true);
243        }
244        if text_was_modified {
245            self.last_action = LastAction::Other;
246        }
247        text_was_modified
248    }
249    // true if the text was changed, false otherwise
250    fn cut_text(&mut self) -> bool {
251        if self.is_readonly() {
252            return false;
253        }
254        if !self.selection.is_empty() {
255            if !self.history_suspended {
256                self.push_undo_snapshot();
257            }
258            RuntimeManager::get()
259                .backend_mut()
260                .set_clipboard_text(&self.glyphs[self.selection.start..self.selection.end]);
261            let old_history_suspended = self.history_suspended;
262            self.history_suspended = true;
263            let result = self.delete_selection();
264            self.history_suspended = old_history_suspended;
265            if result {
266                self.last_action = LastAction::Delete;
267            }
268            result
269        } else {
270            false
271        }
272    }
273    // true if the text was changed, false otherwise
274    fn convert_selection_or_word(&mut self, callback: fn(text: &str) -> String) -> bool {
275        if self.is_readonly() {
276            return false;
277        }
278        if self.selection.is_empty() {
279            self.select_word(self.cursor.pos);
280        }
281
282        if !self.selection.is_empty() {
283            if !self.history_suspended {
284                self.push_undo_snapshot();
285            }
286            let s = callback(&self.glyphs[self.selection.start..self.selection.end]);
287            let text_changed = s != self.glyphs[self.selection.start..self.selection.end];
288            self.glyphs.replace_range(self.selection.start..self.selection.end, &s);
289            let start = self.selection.start;
290            let count = s.count_glyphs();
291            self.selection = Selection::NONE;
292            self.cursor.pos = start;
293            self.move_cursor_with(count as i32, true);
294            if text_changed {
295                self.last_action = LastAction::Other;
296            }
297            text_changed
298        } else {
299            false
300        }
301    }
302
303    fn select_all(&mut self) {
304        self.last_action = LastAction::None;
305        self.selection = Selection::NONE;
306        self.selection.update(0, self.glyphs.len());
307        self.move_cursor_to(self.glyphs.len(), true, false);
308    }
309    // true if the text was changed, false otherwise
310    fn delete_selection(&mut self) -> bool {
311        if !self.selection.is_empty() {
312            if !self.history_suspended {
313                self.push_undo_snapshot();
314            }
315            let new_pos = self.selection.start;
316            self.glyphs.replace_range(self.selection.start..self.selection.end, "");
317            self.selection = Selection::NONE;
318            self.move_cursor_to(new_pos, false, true);
319            self.last_action = LastAction::Delete;
320            true
321        } else {
322            false
323        }
324    }
325    // true if the text was changed, false otherwise
326    fn delete_current_character(&mut self) -> bool {
327        if self.is_readonly() {
328            return false;
329        }
330        if self.selection.is_empty() {
331            let next_pos = self.glyphs.next_pos(self.cursor.pos, 1);
332            if self.cursor.pos < next_pos {
333                if !self.history_suspended {
334                    self.push_undo_snapshot();
335                }
336                self.glyphs.replace_range(self.cursor.pos..next_pos, "");
337                self.update_scroll_view(true);
338                self.last_action = LastAction::Delete;
339                return true;
340            }
341            false
342        } else {
343            self.delete_selection()
344        }
345    }
346    // true if the text was changed, false otherwise
347    fn delete_previous_character(&mut self) -> bool {
348        if self.is_readonly() {
349            return false;
350        }
351        if self.selection.is_empty() {
352            let prev_pos = self.glyphs.previous_pos(self.cursor.pos, 1);
353            if prev_pos < self.cursor.pos {
354                if !self.history_suspended {
355                    self.push_undo_snapshot();
356                }
357                let end_pos = self.cursor.pos;
358                self.glyphs.replace_range(prev_pos..end_pos, "");
359                self.move_cursor_to(prev_pos, false, true);
360                self.last_action = LastAction::Delete;
361                return true;
362            }
363            false
364        } else {
365            self.delete_selection()
366        }
367    }
368    fn add_char(&mut self, character: char) -> bool {
369        if self.is_readonly() {
370            return false;
371        }
372        let char_class = CharClass::from(character);
373        let can_coalesce = self.selection.is_empty() && matches!(self.last_action, LastAction::AddChar(prev_class) if prev_class == char_class);
374        if !can_coalesce && !self.history_suspended {
375            self.push_undo_snapshot();
376        }
377        if !self.selection.is_empty() {
378            let old_history_suspended = self.history_suspended;
379            self.history_suspended = true;
380            self.delete_selection();
381            self.history_suspended = old_history_suspended;
382        }
383        self.glyphs.insert(self.cursor.pos, character);
384        self.move_cursor_with(1, false);
385        self.last_action = LastAction::AddChar(char_class);
386        true
387    }
388    fn select_word(&mut self, offset: usize) {
389        if let Some((start, end)) = self.glyphs.word_range(offset, |c| CharClass::from(c) == CharClass::Word) {
390            self.selection = Selection::NONE;
391            self.move_cursor_to(start, false, true);
392            self.move_cursor_to(end, true, true);
393        }
394    }
395    fn mouse_pos_to_glyph_offset(&self, x: i32, y: i32, within_control: bool) -> Option<usize> {
396        let sz = self.size();
397        let w = sz.width as i32;
398        let h = sz.height as i32;
399        if within_control && ((x < 1) || (x >= w - 1) || (y < 0) || (y >= h)) {
400            return None;
401        }
402        let glyphs_count = (x - 1) + y * (w - 2);
403        match glyphs_count.cmp(&0) {
404            std::cmp::Ordering::Less => Some(self.glyphs.previous_pos(self.cursor.start, (-glyphs_count) as usize)),
405            std::cmp::Ordering::Equal => Some(self.cursor.start),
406            std::cmp::Ordering::Greater => Some(self.glyphs.next_pos(self.cursor.start, glyphs_count as usize)),
407        }
408    }
409    fn make_snapshot(&self) -> TextSnapshot {
410        TextSnapshot {
411            glyphs: self.glyphs.clone(),
412            cursor_pos: self.cursor.pos,
413            selection: self.selection,
414        }
415    }
416    fn push_snapshot_with_limit(stack: &mut Vec<TextSnapshot>, snapshot: TextSnapshot) {
417        if stack.len() >= MAX_UNDO_DEPTH {
418            stack.remove(0);
419        }
420        stack.push(snapshot);
421    }
422    fn push_undo_snapshot(&mut self) {
423        let snapshot = TextSnapshot {
424            glyphs: self.glyphs.clone(),
425            cursor_pos: self.cursor.pos,
426            selection: self.selection,
427        };
428        Self::push_snapshot_with_limit(&mut self.undo_stack, snapshot);
429        self.redo_stack.clear();
430    }
431    fn restore_snapshot(&mut self, snapshot: TextSnapshot, notify: bool) {
432        self.glyphs = snapshot.glyphs;
433        self.cursor.pos = snapshot.cursor_pos.min(self.glyphs.len());
434        self.selection = snapshot.selection;
435        self.update_scroll_view(true);
436        if notify {
437            self.notify_text_changed();
438        }
439    }
440    /// Reverts the most recent text mutation, if history is available.
441    ///
442    /// Undo restores text, cursor position and selection to the previous snapshot.
443    /// If there is no undo entry, this method has no effect.
444    pub fn undo(&mut self) {
445        let current = self.make_snapshot();
446        if let Some(snapshot) = self.undo_stack.pop() {
447            Self::push_snapshot_with_limit(&mut self.redo_stack, current);
448            self.last_action = LastAction::None;
449            self.restore_snapshot(snapshot, true);
450        }
451    }
452    /// Re-applies the most recently undone text mutation, if redo history is available.
453    ///
454    /// Redo restores text, cursor position and selection to the snapshot that was
455    /// previously reverted by [`TextField::undo`]. If there is no redo entry, this
456    /// method has no effect.
457    pub fn redo(&mut self) {
458        let current = self.make_snapshot();
459        if let Some(snapshot) = self.redo_stack.pop() {
460            Self::push_snapshot_with_limit(&mut self.undo_stack, current);
461            self.last_action = LastAction::None;
462            self.restore_snapshot(snapshot, true);
463        }
464    }
465
466    fn notify_text_changed(&mut self) {
467        self.raise_event(ControlEvent {
468            emitter: self.handle,
469            receiver: self.event_processor,
470            data: ControlEventData::TextField(EventData {
471                evtype: TextFieldEventsType::OnTextChanged,
472            }),
473        });
474    }
475}
476impl OnResize for TextField {
477    fn on_resize(&mut self, _old_size: Size, new_size: Size) {
478        // we need to compute the end scroll based on the new size
479        let visible_chars = if new_size.width > 2 {
480            ((new_size.width - 2) as usize) * (new_size.height as usize)
481        } else {
482            0
483        };
484        self.cursor.end = self.glyphs.next_pos(self.cursor.start, visible_chars);
485        // check if the current cursor is within the scroll view and if not update the scroll view
486        self.update_scroll_view(false);
487    }
488}
489impl OnPaint for TextField {
490    fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
491        let attr = match () {
492            _ if !self.is_enabled() => theme.editor.inactive,
493            _ if self.has_focus() => theme.editor.focused,
494            _ if self.is_mouse_over() => theme.editor.hovered,
495            _ => theme.editor.normal,
496        };
497        surface.clear(Character::with_attributes(' ', attr));
498        // paint
499        let show_cursor = self.has_focus();
500        let sz = self.size();
501        let w = (sz.width - 1) as i32;
502        let mut count = (sz.width - 2) * sz.height;
503        let mut pos = self.cursor.start;
504        let mut x = 1;
505        let mut y = 0;
506        let mut ch = Character::with_attributes(' ', attr);
507        let mut ch_selected = Character::with_attributes(' ', theme.editor.pressed_or_selected);
508        while let Some((code, glyph_size)) = self.glyphs.glyph(pos) {
509            if (show_cursor) && self.selection.contains(pos) {
510                ch_selected.code = code;
511                surface.write_char(x, y, ch_selected);
512            } else {
513                ch.code = code;
514                surface.write_char(x, y, ch);
515            }
516            if show_cursor && (pos == self.cursor.pos) {
517                surface.set_cursor(x, y);
518            }
519            x += 1;
520            if x >= w {
521                x = 1;
522                y += 1;
523            }
524            pos += glyph_size as usize;
525            count -= 1;
526            if count == 0 {
527                break;
528            }
529        }
530        // if it is the last char
531        if show_cursor && (pos == self.cursor.pos) {
532            // if the cursor is located on the fist line outside the view --> put it on the last char but on previous line
533            if (y == sz.height as i32) && (x == 1) {
534                surface.set_cursor(sz.width as i32 - 1, sz.height as i32 - 1);
535            } else {
536                surface.set_cursor(x, y);
537            }
538        }
539    }
540}
541impl OnKeyPressed for TextField {
542    fn on_key_pressed(&mut self, key: Key, character: char) -> EventProcessStatus {
543        match key.value() {
544            key!("Left") | key!("Shift+Left") => {
545                self.last_action = LastAction::None;
546                self.move_cursor_with(-1, key.modifier.contains(KeyModifier::Shift));
547                return EventProcessStatus::Processed;
548            }
549            key!("Right") | key!("Shift+Right") => {
550                self.last_action = LastAction::None;
551                self.move_cursor_with(1, key.modifier.contains(KeyModifier::Shift));
552                return EventProcessStatus::Processed;
553            }
554            key!("Up") | key!("Shift+Up") => {
555                self.last_action = LastAction::None;
556                self.move_cursor_with(-((self.size().width as i32) - 2), key.modifier.contains(KeyModifier::Shift));
557                return EventProcessStatus::Processed;
558            }
559            key!("Down") | key!("Shift+Down") => {
560                self.last_action = LastAction::None;
561                self.move_cursor_with((self.size().width as i32) - 2, key.modifier.contains(KeyModifier::Shift));
562                return EventProcessStatus::Processed;
563            }
564            key!("Home") | key!("Shift+Home") => {
565                self.last_action = LastAction::None;
566                self.move_cursor_to(0, key.modifier.contains(KeyModifier::Shift), false);
567                return EventProcessStatus::Processed;
568            }
569            key!("End") | key!("Shift+End") => {
570                self.last_action = LastAction::None;
571                self.move_cursor_to(self.glyphs.len(), key.modifier.contains(KeyModifier::Shift), false);
572                return EventProcessStatus::Processed;
573            }
574            key!("Ctrl+Left") | key!("Ctrl+Shift+Left") => {
575                self.last_action = LastAction::None;
576                self.move_to_previous_word(key.modifier.contains(KeyModifier::Shift));
577                return EventProcessStatus::Processed;
578            }
579            key!("Ctrl+Right") | key!("Ctrl+Shift+Right") => {
580                self.last_action = LastAction::None;
581                self.move_to_next_word(key.modifier.contains(KeyModifier::Shift));
582                return EventProcessStatus::Processed;
583            }
584            // clipboard
585            key!("Ctrl+C") | key!("Ctrl+Insert") => {
586                self.copy_text();
587                return EventProcessStatus::Processed;
588            }
589            // start checking if the text was changed
590            key!("Ctrl+X") | key!("Shift+Del") => {
591                if self.cut_text() {
592                    self.notify_text_changed();
593                }
594                return EventProcessStatus::Processed;
595            }
596            key!("Ctrl+V") | key!("Shift+Insert") => {
597                if self.paste_text() {
598                    self.notify_text_changed();
599                }
600                return EventProcessStatus::Processed;
601            }
602            key!("Ctrl+Shift+U") => {
603                if self.convert_selection_or_word(|s| s.to_uppercase()) {
604                    self.notify_text_changed();
605                }
606                return EventProcessStatus::Processed;
607            }
608            key!("Ctrl+U") => {
609                if self.convert_selection_or_word(|s| s.to_lowercase()) {
610                    self.notify_text_changed();
611                }
612                return EventProcessStatus::Processed;
613            }
614            key!("Ctrl+A") => {
615                self.select_all();
616                return EventProcessStatus::Processed;
617            }
618            key!("Ctrl+Z") => {
619                self.undo();
620                return EventProcessStatus::Processed;
621            }
622            key!("Ctrl+Y") | key!("Ctrl+Shift+Z") => {
623                self.redo();
624                return EventProcessStatus::Processed;
625            }
626            key!("Delete") => {
627                if self.delete_current_character() {
628                    self.notify_text_changed();
629                }
630                return EventProcessStatus::Processed;
631            }
632            key!("Back") => {
633                if self.delete_previous_character() {
634                    self.notify_text_changed();
635                }
636                return EventProcessStatus::Processed;
637            }
638            key!("Enter") => {
639                if self.flags.contains(Flags::ProcessEnter) {
640                    self.raise_event(ControlEvent {
641                        emitter: self.handle,
642                        receiver: self.event_processor,
643                        data: ControlEventData::TextField(EventData {
644                            evtype: TextFieldEventsType::OnValidate,
645                        }),
646                    });
647                    return EventProcessStatus::Processed;
648                }
649            }
650
651            _ => {}
652        }
653        if (character as u32) > 0 {
654            if self.add_char(character) {
655                self.notify_text_changed();
656            }
657            return EventProcessStatus::Processed;
658        }
659        EventProcessStatus::Ignored
660    }
661}
662impl OnFocus for TextField {
663    fn on_focus(&mut self) {
664        if !self.flags.contains(Flags::DisableAutoSelectOnFocus) {
665            self.select_all();
666        }
667    }
668}
669impl OnMouseEvent for TextField {
670    fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
671        match event {
672            MouseEvent::Enter | MouseEvent::Leave => {
673                self.last_action = LastAction::None;
674                self.drag_started = false;
675                EventProcessStatus::Processed
676            }
677            MouseEvent::Over(_) => EventProcessStatus::Ignored,
678            MouseEvent::Pressed(data) => {
679                if let Some(new_pos) = self.mouse_pos_to_glyph_offset(data.x, data.y, true) {
680                    self.last_action = LastAction::None;
681                    self.move_cursor_to(new_pos, false, false);
682                    self.drag_started = true;
683                }
684                EventProcessStatus::Processed
685            }
686            MouseEvent::Released(_) => {
687                self.last_action = LastAction::None;
688                self.drag_started = false;
689                EventProcessStatus::Processed
690            }
691            MouseEvent::DoubleClick(data) => {
692                if let Some(ofs) = self.mouse_pos_to_glyph_offset(data.x, data.y, true) {
693                    self.last_action = LastAction::None;
694                    self.select_word(ofs);
695                }
696                EventProcessStatus::Processed
697            }
698            MouseEvent::Drag(data) => {
699                if self.drag_started {
700                    if let Some(new_pos) = self.mouse_pos_to_glyph_offset(data.x, data.y, false) {
701                        self.last_action = LastAction::None;
702                        self.move_cursor_to(new_pos, true, true);
703                    }
704                }
705                EventProcessStatus::Processed
706            }
707            MouseEvent::Wheel(_) => EventProcessStatus::Ignored,
708        }
709    }
710}