dear_imgui_rs/widget/input/
callbacks.rs1use crate::sys;
2
3bitflags::bitflags! {
7 #[repr(transparent)]
9 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10 pub struct InputTextCallback: u32 {
11 const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion as u32;
13 const HISTORY = sys::ImGuiInputTextFlags_CallbackHistory as u32;
15 const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways as u32;
17 const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter as u32;
19 const EDIT = sys::ImGuiInputTextFlags_CallbackEdit as u32;
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum HistoryDirection {
28 Up,
30 Down,
32}
33
34pub trait InputTextCallbackHandler {
39 fn char_filter(&mut self, c: char) -> Option<char> {
44 Some(c)
45 }
46
47 fn on_completion(&mut self, _data: TextCallbackData<'_>) {}
51
52 fn on_history(&mut self, _direction: HistoryDirection, _data: TextCallbackData<'_>) {}
56
57 fn on_always(&mut self, _data: TextCallbackData<'_>) {}
61
62 fn on_edit(&mut self, _data: TextCallbackData<'_>) {}
66}
67
68pub struct TextCallbackData<'cb>(
72 *mut sys::ImGuiInputTextCallbackData,
73 std::marker::PhantomData<&'cb mut sys::ImGuiInputTextCallbackData>,
74);
75
76impl<'cb> TextCallbackData<'cb> {
77 pub(super) unsafe fn new(data: *mut sys::ImGuiInputTextCallbackData) -> Self {
79 Self(data, std::marker::PhantomData)
80 }
81
82 fn data(&self) -> &sys::ImGuiInputTextCallbackData {
83 unsafe {
84 self.0
85 .as_ref()
86 .expect("internal imgui error: InputText callback data was null")
87 }
88 }
89
90 fn data_mut(&mut self) -> &mut sys::ImGuiInputTextCallbackData {
91 unsafe {
92 self.0
93 .as_mut()
94 .expect("internal imgui error: InputText callback data was null")
95 }
96 }
97
98 fn valid_text_len(&self) -> usize {
99 let data = self.data();
100 assert!(!data.Buf.is_null(), "internal imgui error: Buf was null");
101 assert!(
102 data.BufTextLen >= 0,
103 "internal imgui error: BufTextLen was negative"
104 );
105 assert!(
106 data.BufSize >= 0,
107 "internal imgui error: BufSize was negative"
108 );
109 assert!(
110 data.BufTextLen <= data.BufSize,
111 "internal imgui error: BufTextLen exceeded BufSize"
112 );
113 data.BufTextLen as usize
114 }
115
116 fn valid_text_len_i32(&self) -> i32 {
117 self.valid_text_len() as i32
118 }
119
120 fn position(name: &str, pos: i32, len: usize) -> usize {
121 let pos = usize::try_from(pos).unwrap_or_else(|_| {
122 panic!("internal imgui error: {name} was negative");
123 });
124 assert!(
125 pos <= len,
126 "internal imgui error: {name} exceeded BufTextLen"
127 );
128 pos
129 }
130
131 fn position_to_i32(name: &str, pos: usize) -> i32 {
132 i32::try_from(pos).unwrap_or_else(|_| {
133 panic!("{name} exceeded ImGui's i32 position range");
134 })
135 }
136
137 fn assert_byte_boundary(text: &str, name: &str, pos: usize) {
138 assert!(
139 text.is_char_boundary(pos),
140 "{name} must lie on a UTF-8 character boundary"
141 );
142 }
143
144 pub fn str(&self) -> &str {
146 let len = self.valid_text_len();
147 unsafe {
148 std::str::from_utf8(std::slice::from_raw_parts(self.data().Buf as *const _, len))
149 .expect("internal imgui error -- it boofed a utf8")
150 }
151 }
152
153 pub fn cursor_pos(&self) -> usize {
155 let len = self.valid_text_len();
156 Self::position("CursorPos", self.data().CursorPos, len)
157 }
158
159 pub fn set_cursor_pos(&mut self, pos: usize) {
161 let text = self.str();
162 assert!(pos <= text.len(), "cursor position out of bounds");
163 Self::assert_byte_boundary(text, "cursor position", pos);
164 self.data_mut().CursorPos = Self::position_to_i32("cursor position", pos);
165 }
166
167 pub fn selection_start(&self) -> usize {
169 let len = self.valid_text_len();
170 Self::position("SelectionStart", self.data().SelectionStart, len)
171 }
172
173 pub fn set_selection_start(&mut self, pos: usize) {
175 let text = self.str();
176 assert!(pos <= text.len(), "selection start out of bounds");
177 Self::assert_byte_boundary(text, "selection start", pos);
178 self.data_mut().SelectionStart = Self::position_to_i32("selection start", pos);
179 }
180
181 pub fn selection_end(&self) -> usize {
183 let len = self.valid_text_len();
184 Self::position("SelectionEnd", self.data().SelectionEnd, len)
185 }
186
187 pub fn set_selection_end(&mut self, pos: usize) {
189 let text = self.str();
190 assert!(pos <= text.len(), "selection end out of bounds");
191 Self::assert_byte_boundary(text, "selection end", pos);
192 self.data_mut().SelectionEnd = Self::position_to_i32("selection end", pos);
193 }
194
195 pub fn select_all(&mut self) {
197 let len = self.valid_text_len_i32();
198 let data = self.data_mut();
199 data.SelectionStart = 0;
200 data.SelectionEnd = len;
201 }
202
203 pub fn clear_selection(&mut self) {
205 let cursor_pos = Self::position_to_i32("cursor position", self.cursor_pos());
206 let data = self.data_mut();
207 data.SelectionStart = cursor_pos;
208 data.SelectionEnd = cursor_pos;
209 }
210
211 pub fn has_selection(&self) -> bool {
213 self.data().SelectionStart != self.data().SelectionEnd
214 }
215
216 pub fn remove_chars(&mut self, pos: usize, bytes_count: usize) {
218 let text = self.str();
219 let end = pos
220 .checked_add(bytes_count)
221 .expect("delete range overflowed usize");
222 assert!(end <= text.len(), "delete range out of bounds");
223 Self::assert_byte_boundary(text, "delete start", pos);
224 Self::assert_byte_boundary(text, "delete end", end);
225 let pos = Self::position_to_i32("delete start", pos);
226 let bytes_count = Self::position_to_i32("delete byte count", bytes_count);
227 unsafe {
228 sys::ImGuiInputTextCallbackData_DeleteChars(self.0, pos, bytes_count);
229 }
230 }
231
232 pub fn insert_chars(&mut self, pos: usize, text: &str) {
234 let current = self.str();
235 assert!(pos <= current.len(), "insert position out of bounds");
236 Self::assert_byte_boundary(current, "insert position", pos);
237 let pos = Self::position_to_i32("insert position", pos);
238 let text_ptr = text.as_ptr() as *const std::os::raw::c_char;
239 unsafe {
240 sys::ImGuiInputTextCallbackData_InsertChars(
241 self.0,
242 pos,
243 text_ptr,
244 text_ptr.add(text.len()),
245 );
246 }
247 }
248
249 pub unsafe fn str_as_bytes_mut(&mut self) -> &mut [u8] {
269 let len = self.valid_text_len();
270 unsafe {
271 let str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut(
272 self.data_mut().Buf as *mut u8,
273 len,
274 ))
275 .expect("internal imgui error -- it boofed a utf8");
276
277 str.as_bytes_mut()
278 }
279 }
280
281 pub fn set_dirty(&mut self) {
293 self.data_mut().BufDirty = true;
294 }
295
296 pub fn selected(&self) -> &str {
299 let text = self.str();
300 let start = self.selection_start().min(self.selection_end());
301 let end = self.selection_start().max(self.selection_end());
302 assert!(end <= text.len(), "selection range out of bounds");
303 Self::assert_byte_boundary(text, "selection start", start);
304 Self::assert_byte_boundary(text, "selection end", end);
305 &text[start..end]
306 }
307
308 pub fn push_str(&mut self, text: &str) {
312 let current_len = self.valid_text_len();
313 self.insert_chars(current_len, text);
314 }
315}
316
317pub struct PassthroughCallback;
322impl InputTextCallbackHandler for PassthroughCallback {}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 struct DefaultHandler;
329 impl InputTextCallbackHandler for DefaultHandler {}
330
331 fn callback_data_for_text(text: &mut [u8]) -> sys::ImGuiInputTextCallbackData {
332 let len = text.len().saturating_sub(1);
333 let mut data = sys::ImGuiInputTextCallbackData::default();
334 data.Buf = text.as_mut_ptr().cast();
335 data.BufTextLen = len as i32;
336 data.BufSize = text.len() as i32;
337 data.CursorPos = len as i32;
338 data.SelectionStart = 0;
339 data.SelectionEnd = len as i32;
340 data
341 }
342
343 #[test]
344 fn default_char_filter_keeps_character() {
345 let mut handler = DefaultHandler;
346 assert_eq!(handler.char_filter('x'), Some('x'));
347 }
348
349 #[test]
350 fn passthrough_char_filter_keeps_character() {
351 let mut handler = PassthroughCallback;
352 assert_eq!(handler.char_filter('x'), Some('x'));
353 }
354
355 #[test]
356 fn text_callback_positions_accept_bounds() {
357 let mut text = *b"abcd\0";
358 let mut data = callback_data_for_text(&mut text);
359 data.CursorPos = 4;
360 data.SelectionStart = 1;
361 data.SelectionEnd = 3;
362
363 let info = unsafe { TextCallbackData::new(&mut data) };
364
365 assert_eq!(info.cursor_pos(), 4);
366 assert_eq!(info.selection_start(), 1);
367 assert_eq!(info.selection_end(), 3);
368 assert_eq!(info.selected(), "bc");
369 }
370
371 #[test]
372 #[should_panic(expected = "CursorPos exceeded BufTextLen")]
373 fn text_callback_cursor_pos_rejects_out_of_bounds() {
374 let mut text = *b"abcd\0";
375 let mut data = callback_data_for_text(&mut text);
376 data.CursorPos = 5;
377
378 let info = unsafe { TextCallbackData::new(&mut data) };
379 let _ = info.cursor_pos();
380 }
381
382 #[test]
383 #[should_panic(expected = "CursorPos exceeded BufTextLen")]
384 fn text_callback_clear_selection_rejects_out_of_bounds_cursor() {
385 let mut text = *b"abcd\0";
386 let mut data = callback_data_for_text(&mut text);
387 data.CursorPos = 5;
388
389 let mut info = unsafe { TextCallbackData::new(&mut data) };
390 info.clear_selection();
391 }
392}