azul_widgets/
text_input.rs1use 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 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 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 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}