azul_widgets/
text_input.rs

1//! Text input (demonstrates two-way data binding)
2
3use std::ops::Range;
4use azul_core::{
5    dom::{Dom, EventFilter, FocusEventFilter, TabIndex},
6    window::{KeyboardState, VirtualKeyCode},
7    callbacks::{Ref, Redraw, Callback, CallbackInfo, CallbackReturn},
8};
9
10#[derive(Debug, Clone, Hash, PartialEq, Eq)]
11pub struct TextInput {
12    pub on_text_input: Callback,
13    pub on_virtual_key_down: Callback,
14    pub state: Ref<TextInputState>,
15}
16
17impl Default for TextInput {
18    fn default() -> Self {
19        TextInput {
20            on_text_input: Callback(Self::default_on_text_input),
21            on_virtual_key_down: Callback(Self::default_on_virtual_key_down),
22            state: Ref::default(),
23        }
24    }
25}
26
27impl Into<Dom> for TextInput {
28    fn into(self) -> Dom {
29        self.dom()
30    }
31}
32
33#[derive(Debug, Clone, Hash, PartialEq, Eq)]
34pub struct TextInputState {
35    pub text: String,
36    pub selection: Option<Selection>,
37    pub cursor_pos: usize,
38}
39
40impl Default for TextInputState {
41    fn default() -> Self {
42        TextInputState {
43            text: String::new(),
44            selection: None,
45            cursor_pos: 0,
46        }
47    }
48}
49
50#[derive(Debug, Clone, Hash, PartialEq, Eq)]
51pub enum Selection {
52    All,
53    FromTo(Range<usize>),
54}
55
56impl TextInputState {
57
58    #[inline]
59    pub fn new<S: Into<String>>(input: S) -> Self {
60        Self {
61            text: input.into(),
62            selection: None,
63            cursor_pos: 0,
64        }
65    }
66
67    #[inline]
68    pub fn with_cursor_pos(self, cursor_pos: usize) -> Self {
69        Self { cursor_pos, .. self }
70    }
71
72    #[inline]
73    pub fn with_selection(self, selection: Option<Selection>) -> Self {
74        Self { selection, .. self }
75    }
76
77    pub fn handle_on_text_input(&mut self, keyboard_state: &KeyboardState) -> CallbackReturn {
78
79        let c = keyboard_state.current_char?;
80
81        match self.selection.clone() {
82            None => {
83                if self.cursor_pos == self.text.len() {
84                    self.text.push(c);
85                } else {
86                    // TODO: insert character at the cursor location!
87                    self.text.push(c);
88                }
89                self.cursor_pos = self.cursor_pos.saturating_add(1);
90            },
91            Some(Selection::All) => {
92                self.text = format!("{}", c);
93                self.cursor_pos = 1;
94                self.selection = None;
95            },
96            Some(Selection::FromTo(range)) => {
97                self.delete_selection(range, Some(c));
98            },
99        }
100
101        Redraw
102    }
103
104    pub fn handle_on_virtual_key_down(&mut self, keyboard_state: &KeyboardState) -> CallbackReturn {
105
106        let last_keycode = keyboard_state.current_virtual_keycode?;
107
108        match last_keycode {
109            VirtualKeyCode::Back => {
110                // TODO: shift + back = delete last word
111                let selection = self.selection.clone();
112                match selection {
113                    None => {
114                        if self.cursor_pos == self.text.len() {
115                            self.text.pop();
116                        } else {
117                            let mut a = self.text.chars().take(self.cursor_pos).collect::<String>();
118                            let new = self.text.len().min(self.cursor_pos.saturating_add(1));
119                            a.extend(self.text.chars().skip(new));
120                            self.text = a;
121                        }
122                        self.cursor_pos = self.cursor_pos.saturating_sub(1);
123                    },
124                    Some(Selection::All) => {
125                        self.text.clear();
126                        self.cursor_pos = 0;
127                        self.selection = None;
128                    },
129                    Some(Selection::FromTo(range)) => {
130                        self.delete_selection(range, None);
131                    },
132                }
133            },
134            VirtualKeyCode::Return => {
135                // TODO: selection!
136                self.text.push('\n');
137                self.cursor_pos = self.cursor_pos.saturating_add(1);
138            },
139            VirtualKeyCode::Home => {
140                self.cursor_pos = 0;
141                self.selection = None;
142            },
143            VirtualKeyCode::End => {
144                self.cursor_pos = self.text.len();
145                self.selection = None;
146            },
147            VirtualKeyCode::Escape => {
148                self.selection = None;
149            },
150            VirtualKeyCode::Right => {
151                self.cursor_pos = self.text.len().min(self.cursor_pos.saturating_add(1));
152            },
153            VirtualKeyCode::Left => {
154                self.cursor_pos = (0.max(self.cursor_pos.saturating_sub(1))).min(self.cursor_pos.saturating_add(1));
155            },
156            VirtualKeyCode::A if keyboard_state.ctrl_down => {
157                self.selection = Some(Selection::All);
158            },
159            VirtualKeyCode::C if keyboard_state.ctrl_down => {},
160            VirtualKeyCode::V if keyboard_state.ctrl_down => {},
161            _ => { },
162        }
163
164        Redraw
165    }
166
167    pub fn delete_selection(&mut self, selection: Range<usize>, new_text: Option<char>) {
168        let Range { start, end } = selection;
169        let max = if end > self.text.len() { self.text.len() } else { end };
170
171        let mut cur = start;
172        if max == self.text.len() {
173            self.text.truncate(start);
174        } else {
175            let mut a = self.text.chars().take(start).collect::<String>();
176
177            if let Some(new) = new_text {
178                a.push(new);
179                cur += 1;
180            }
181
182            a.extend(self.text.chars().skip(end));
183            self.text = a;
184        }
185
186        self.cursor_pos = cur;
187    }
188}
189
190impl TextInput {
191
192    pub fn new(state: Ref<TextInputState>) -> Self {
193        Self { state, .. Default::default() }
194    }
195
196    pub fn with_state(self, state: Ref<TextInputState>) -> Self {
197        Self { state, .. self }
198    }
199
200    pub fn on_text_input(self, callback: Callback) -> Self {
201        Self { on_text_input: callback, .. self }
202    }
203
204    pub fn on_virtual_key_down(self, callback: Callback) -> Self {
205        Self { on_text_input: callback, .. self }
206    }
207
208    pub fn dom(self) -> Dom {
209
210        let label = Dom::label(self.state.borrow().text.clone())
211            .with_class("__azul-native-input-text-label");
212
213        let upcasted_state = self.state.upcast();
214
215        Dom::div()
216            .with_class("__azul-native-input-text")
217            .with_tab_index(TabIndex::Auto)
218            .with_callback(EventFilter::Focus(FocusEventFilter::TextInput), self.on_text_input.0, upcasted_state.clone())
219            .with_callback(EventFilter::Focus(FocusEventFilter::VirtualKeyDown), self.on_virtual_key_down.0, upcasted_state)
220            .with_child(label)
221    }
222
223    pub fn default_on_text_input(info: CallbackInfo) -> CallbackReturn {
224        let text_input_state = info.state.downcast::<TextInputState>()?;
225        let keyboard_state = info.current_window_state.get_keyboard_state();
226        text_input_state.borrow_mut().handle_on_text_input(keyboard_state)
227    }
228
229    pub fn default_on_virtual_key_down(info: CallbackInfo) -> CallbackReturn {
230        let text_input_state = info.state.downcast::<TextInputState>()?;
231        let keyboard_state = info.current_window_state.get_keyboard_state();
232        text_input_state.borrow_mut().handle_on_virtual_key_down(keyboard_state)
233    }
234}