From 71a0f73034ab44c60afb53a550c96172b2eae46a Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Sat, 4 Sep 2021 12:32:48 -0700 Subject: [PATCH 01/15] initial commit --- imgui-examples/examples/keyboard.rs | 1 - imgui/src/input_widget.rs | 439 ++++++++++++++++++++++++---- 2 files changed, 385 insertions(+), 55 deletions(-) diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index d857ce7..b6fa795 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -81,7 +81,6 @@ fn main() { // example, if you try to type into this input, the // above interaction still counts the key presses. ui.input_text(im_str!("##Dummy text input widget"), &mut text_buffer) - .resize_buffer(true) // Auto-resize ImString as required .hint(im_str!("Example text input")) .build(); diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index d4f7c9b..fad6c14 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -1,11 +1,31 @@ use bitflags::bitflags; use std::marker::PhantomData; -use std::os::raw::{c_int, c_void}; +use std::ops::Range; +use std::os::raw::{c_char, c_int, c_void}; use std::ptr; use crate::sys; use crate::{ImStr, ImString, Ui}; +bitflags!( + /// Callback flags + pub struct InputTextCallback: u32 { + /// Call user function on pressing TAB (for completion handling) + const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; + /// Call user function on pressing Up/Down arrows (for history handling) + const HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; + /// Call user function every time. User code may query cursor position, modify text buffer. + const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; + /// Call user function to filter character. + const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; + /// Allow buffer capacity resize + notify when the string wants to be resized + const RESIZE = sys::ImGuiInputTextFlags_CallbackResize; + /// Callback on buffer edit (note that InputText already returns true on edit, the + /// callback is useful mainly to manipulate the underlying buffer while focus is active) + const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; + } +); + bitflags!( /// Flags for text inputs #[repr(C)] @@ -52,6 +72,308 @@ bitflags!( } ); +pub trait TextCallbackHandler { + /// Filters a char -- returning a `None` means that the char is removed, + /// and returning another char substitutes it out. + /// + /// The default implementation is a passthrough filter (ie, doesn't do anything). + fn char_filter(&mut self, c: char, _: &TextInformation<'_>) -> Option { + Some(c) + } + + /// Allows one to perform autocompletion work when the Tab key has been pressed. + fn on_completion(&mut self, _: &mut TextInformation<'_>) {} + + /// A callback when one of the direction keys have been pressed. + fn on_history(&mut self, _: EventDirection, _: &mut TextInformation<'_>) {} + + // fn on_always(&mut self, state: &mut TextCallbackState); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EventDirection { + Up, + Down, +} + +pub struct TextInformation<'a> { + /// All of the flags that are POSSIBLY given in a callback. + pub flags: InputTextFlags, + + /// The buffer itself. If you can access this mutably, you can + /// do things to the underlying buffer. + pub buf: TextCallbackBuffer<'a>, +} + +#[repr(C)] +pub struct UserData<'a> { + container: &'a mut ImString, + cback_handler: &'a mut dyn TextCallbackHandler, +} + +struct CallbackData<'a> { + event_flag: InputTextCallback, + user_data: &'a mut UserData<'a>, +} + +pub struct TextCallbackData<'a> { + pub event_flag: InputTextFlags, + pub flags: InputTextFlags, + pub user_data: UserData<'a>, + pub event_char: char, + pub event_key: EventDirection, + pub buf: TextCallbackBuffer<'a>, +} + +pub struct TextCallbackBuffer<'a> { + buf: &'a mut str, + dirty: &'a mut bool, + cursor_pos: &'a mut i32, + selection_start: &'a mut i32, + selection_end: &'a mut i32, + callback_data: *mut sys::ImGuiInputTextCallbackData, +} + +impl TextCallbackBuffer<'_> { + /// Get a reference to the text callback buffer's buf. + pub fn buf(&self) -> &str { + self.buf + } + + /// Gives access to the underlying byte array MUTABLY. + /// This is very unsafe, and the following invariants must be + /// upheld: + /// 1. Keep the data utf8 valid. + /// 2. After editing the string, call [set_dirty]. + /// + /// We present this string with a `str` interface immutably, which + /// actually somewhat weakens `trunc` operations on the string. + /// You can use [remove_chars] to handle this operation completely + /// safely for you. However, if this is still too limiting, + /// please submit an issue. + pub unsafe fn buf_mut(&mut self) -> &mut [u8] { + self.buf.as_bytes_mut() + } + + /// Sets the dirty flag on the text to imgui, indicating that + /// it should reapply this string to its internal state. + /// + /// **NB:** You only need to use this method if you're using `[buf_mut]`. + /// If you use the helper methods [remove_chars] and [insert_chars], + /// this will be set for you. However, this is no downside to setting + /// the dirty flag spuriously except the minor CPU time imgui will spend. + pub fn set_dirty(&mut self) { + *self.dirty = true; + } + + /// Gets a range of the selected text. See [selection_start_mut] and + /// [selection_end_mut] to mutably edit these values. + /// + /// This Range is given in `usize` so that it might be used in indexing + /// operations more easily. To quickly grab the selected text, use [selected]. + pub fn selection(&self) -> Range { + *self.selection_start as usize..*self.selection_end as usize + } + + /// Returns the selected text directly. Note that if no text is selected, + /// an empty str slice will be returned. + pub fn selected(&self) -> &str { + &self.buf[self.selection()] + } + + /// Sets the cursor to select all. This is always a valid operation, + /// and so it takes an `&self`. + pub fn select_all(&self) { + unsafe { + sys::ImGuiInputTextCallbackData_SelectAll(self.callback_data); + } + } + + /// Clears the selection. This is always a valid operation, + /// and so it takes an `&self`. + pub fn clear_selection(&self) { + unsafe { + sys::ImGuiInputTextCallbackData_ClearSelection(self.callback_data); + } + } + + /// Checks if there is a selection within the text. + pub fn has_selection(&self) -> bool { + unsafe { sys::ImGuiInputTextCallbackData_HasSelection(self.callback_data) } + } + + /// Inserts the given string at the given position. If this + /// would require the String to resize, it will be resized by calling the + /// Callback_Resize callback. This is automatically handled. + /// + /// ## Panics + /// Panics if the `pos` is not a char_boundary. + pub fn insert_chars(&mut self, pos: usize, s: &str) { + assert!(s.is_char_boundary(pos)); + unsafe { + self.insert_chars_unsafe(pos, s); + } + } + + /// Inserts the given string at the given position, unsafely. If this + /// would require the String to resize, it will be resized by calling the + /// Callback_Resize callback. This is automatically handled. + /// + /// It is up to the caller to confirm that the `pos` is a valid byte + /// position, or use [insert_chars] which will panic if it isn't. + pub unsafe fn insert_chars_unsafe(&mut self, pos: usize, s: &str) { + let start = s.as_ptr(); + let end = start.add(s.len()); + + sys::ImGuiInputTextCallbackData_InsertChars( + self.callback_data, + pos as i32, + start as *const c_char, + end as *const c_char, + ) + } + + /// Removes the given number of characters from the string starting + /// at some byte pos. + /// + /// ## Panics + /// Panics if the `pos` is not a char boundary or if + /// there are not enough chars remaining. + pub fn remove_chars(&mut self, pos: usize, char_count: usize) { + let inner = &self.buf[pos..]; + let byte_count = inner + .char_indices() + .nth(char_count) + .expect("not enough characters in string") + .0; + + unsafe { + self.remove_chars_unchecked(pos, byte_count); + } + } + + /// Removes the given number of bytes from the string starting + /// at some byte pos, without checking for utf8 validity. Use [remove_chars] + /// for a safe variant. + pub unsafe fn remove_chars_unchecked(&mut self, pos: usize, byte_count: usize) { + sys::ImGuiInputTextCallbackData_DeleteChars( + self.callback_data, + pos as i32, + byte_count as i32, + ) + } + + /// Get a reference to the text callback buffer's cursor pos. + pub fn cursor_pos(&self) -> usize { + *self.cursor_pos as usize + } + + /// Set the text callback buffer's cursor pos. + pub fn set_cursor_pos(&mut self, cursor_pos: usize) { + *self.cursor_pos = cursor_pos as i32; + } + + /// Get a mutable reference to the text callback buffer's selection start. + pub fn selection_start_mut(&mut self) -> &mut i32 { + self.selection_start + } + + /// Get a mutable reference to the text callback buffer's selection start. + pub fn selection_end_mut(&mut self) -> &mut i32 { + self.selection_end + } +} + +/// Currently, this might contain UB. We may be holdling two mutable pointers to the same +/// data. Not sure yet though. +extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { + // This is always safe to do because imgui promises that this is a valid pointer. + // let data = unsafe { *data }; + + // convert it to a friendlier type + let (mut text_info, callback_data) = unsafe { + let text_info = TextInformation { + flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), + buf: TextCallbackBuffer { + buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + (*data).Buf as *mut u8, + (*data).BufSize as usize - 1, + )) + .expect("internal imgui error -- it boofed a utf8"), + dirty: &mut (*data).BufDirty, + cursor_pos: &mut (*data).CursorPos, + selection_start: &mut (*data).SelectionStart, + selection_end: &mut (*data).SelectionEnd, + callback_data: data, + }, + }; + + let callback_data = CallbackData { + event_flag: InputTextCallback::from_bits((*data).EventFlag as u32).unwrap(), + user_data: &mut *((*data).UserData as *mut UserData), + }; + + (text_info, callback_data) + }; + + // check this callback. + match callback_data.event_flag { + InputTextCallback::ALWAYS => { + todo!() + } + InputTextCallback::RESIZE => { + unsafe { + let requested_size = (*data).BufSize as usize; + let buffer = &mut callback_data.user_data.container; + if requested_size > buffer.capacity_with_nul() { + // Refresh the buffer's length to take into account changes made by dear imgui. + buffer.refresh_len(); + buffer.reserve(requested_size - buffer.0.len()); + debug_assert!(buffer.capacity_with_nul() >= requested_size); + (*data).Buf = buffer.as_mut_ptr(); + (*data).BufDirty = true; + } + } + } + InputTextCallback::CHAR_FILTER => { + let chr = unsafe { char::from_u32((*data).EventChar).unwrap() }; + let new_data = match callback_data + .user_data + .cback_handler + .char_filter(chr, &text_info) + { + Some(value) => u32::from(value), + // 0 means "do not use this char" in imgui docs + None => 0, + }; + // set the new char... + unsafe { + (*data).EventChar = new_data; + } + } + InputTextCallback::COMPLETION => { + // callback_data.user_data.cback_handler.on_completion(&mut) + } + InputTextCallback::HISTORY => { + let key = unsafe { + let key = (*data).EventKey as u32; + match key { + sys::ImGuiKey_UpArrow => EventDirection::Up, + sys::ImGuiKey_DownArrow => EventDirection::Down, + _ => panic!("Unexpected key"), + } + }; + callback_data + .user_data + .cback_handler + .on_history(key, &mut text_info); + } + _ => {} + } + + 0 +} + macro_rules! impl_text_flags { ($InputType:ident) => { #[inline] @@ -96,35 +418,35 @@ macro_rules! impl_text_flags { self } - #[inline] - pub fn callback_completion(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CALLBACK_COMPLETION, value); - self - } + // #[inline] + // pub fn callback_completion(mut self, value: bool) -> Self { + // self.flags.set(InputTextFlags::CALLBACK_COMPLETION, value); + // self + // } - #[inline] - pub fn callback_history(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CALLBACK_HISTORY, value); - self - } + // #[inline] + // pub fn callback_history(mut self, value: bool) -> Self { + // self.flags.set(InputTextFlags::CALLBACK_HISTORY, value); + // self + // } - #[inline] - pub fn callback_always(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CALLBACK_ALWAYS, value); - self - } + // #[inline] + // pub fn callback_always(mut self, value: bool) -> Self { + // self.flags.set(InputTextFlags::CALLBACK_ALWAYS, value); + // self + // } - #[inline] - pub fn callback_char_filter(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CALLBACK_CHAR_FILTER, value); - self - } + // #[inline] + // pub fn callback_char_filter(mut self, value: bool) -> Self { + // self.flags.set(InputTextFlags::CALLBACK_CHAR_FILTER, value); + // self + // } - #[inline] - pub fn resize_buffer(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CALLBACK_RESIZE, value); - self - } + // #[inline] + // pub fn resize_buffer(mut self, value: bool) -> Self { + // self.flags.set(InputTextFlags::CALLBACK_RESIZE, value); + // self + // } #[inline] pub fn allow_tab_input(mut self, value: bool) -> Self { @@ -187,30 +509,16 @@ macro_rules! impl_step_params { }; } -extern "C" fn resize_callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { - unsafe { - if (*data).EventFlag == InputTextFlags::CALLBACK_RESIZE.bits() as i32 { - if let Some(buffer) = ((*data).UserData as *mut ImString).as_mut() { - let requested_size = (*data).BufSize as usize; - if requested_size > buffer.capacity_with_nul() { - // Refresh the buffer's length to take into account changes made by dear imgui. - buffer.refresh_len(); - buffer.reserve(requested_size - buffer.0.len()); - debug_assert!(buffer.capacity_with_nul() >= requested_size); - (*data).Buf = buffer.as_mut_ptr(); - (*data).BufDirty = true; - } - } - } - 0 - } -} +static mut PASSTHROUGH_CALLBACK: PassthroughCallback = PassthroughCallback; +pub struct PassthroughCallback; +impl TextCallbackHandler for PassthroughCallback {} #[must_use] pub struct InputText<'ui, 'p> { label: &'p ImStr, hint: Option<&'p ImStr>, buf: &'p mut ImString, + callback_handler: &'p mut dyn TextCallbackHandler, flags: InputTextFlags, _phantom: PhantomData<&'ui Ui<'ui>>, } @@ -220,8 +528,10 @@ impl<'ui, 'p> InputText<'ui, 'p> { InputText { label, hint: None, + // this is fine because no one else has access to this and imgui is single threaded. + callback_handler: unsafe { &mut PASSTHROUGH_CALLBACK }, buf, - flags: InputTextFlags::empty(), + flags: InputTextFlags::CALLBACK_RESIZE, _phantom: PhantomData, } } @@ -238,15 +548,36 @@ impl<'ui, 'p> InputText<'ui, 'p> { // TODO: boxed closure...? // pub fn callback(self) -> Self { } + #[inline] + pub fn callback( + mut self, + callbacks: InputTextCallback, + callback: &'p mut dyn TextCallbackHandler, + ) -> Self { + if callbacks.contains(InputTextCallback::COMPLETION) { + self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); + } + if callbacks.contains(InputTextCallback::HISTORY) { + self.flags.insert(InputTextFlags::CALLBACK_HISTORY); + } + if callbacks.contains(InputTextCallback::ALWAYS) { + self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); + } + if callbacks.contains(InputTextCallback::CHAR_FILTER) { + self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); + } + self.callback_handler = callback; + self + } + pub fn build(self) -> bool { let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); - let (callback, data): (sys::ImGuiInputTextCallback, _) = { - if self.flags.contains(InputTextFlags::CALLBACK_RESIZE) { - (Some(resize_callback), self.buf as *mut _ as *mut c_void) - } else { - (None, ptr::null_mut()) - } + + let mut data = UserData { + container: self.buf, + cback_handler: self.callback_handler, }; + let data = &mut data as *mut _ as *mut c_void; unsafe { let result = if let Some(hint) = self.hint { @@ -256,7 +587,7 @@ impl<'ui, 'p> InputText<'ui, 'p> { ptr, capacity, self.flags.bits() as i32, - callback, + Some(callback), data, ) } else { @@ -265,7 +596,7 @@ impl<'ui, 'p> InputText<'ui, 'p> { ptr, capacity, self.flags.bits() as i32, - callback, + Some(callback), data, ) }; @@ -304,7 +635,7 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p> { let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); let (callback, data): (sys::ImGuiInputTextCallback, _) = { if self.flags.contains(InputTextFlags::CALLBACK_RESIZE) { - (Some(resize_callback), self.buf as *mut _ as *mut c_void) + (Some(callback), self.buf as *mut _ as *mut c_void) } else { (None, ptr::null_mut()) } From 9b27edf440614f612e53b8c48b137e6a6ea8d8bb Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Sun, 5 Sep 2021 11:05:24 -0700 Subject: [PATCH 02/15] working on some tests. this commit is broken --- imgui-examples/examples/keyboard.rs | 12 ++++++++++++ imgui/src/input_widget.rs | 22 +++++++++++----------- imgui/src/lib.rs | 5 +++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index b6fa795..5b59ba5 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -76,11 +76,23 @@ fn main() { ctrl_a_counter )); + struct Cback; + impl TextCallbackHandler for Cback { + fn char_filter(&mut self, c: char, txt: &TextInformation<'_>) -> Option { + if c == 'a' { + None + } else { + Some(c) + } + } + } + // Note that `is_key_released` gives the state of the // key regardless of what widget has focus, for // example, if you try to type into this input, the // above interaction still counts the key presses. ui.input_text(im_str!("##Dummy text input widget"), &mut text_buffer) + .callback(InputTextCallback::CHAR_FILTER, &mut Cback) .hint(im_str!("Example text input")) .build(); diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index fad6c14..e2a726a 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -116,14 +116,14 @@ struct CallbackData<'a> { user_data: &'a mut UserData<'a>, } -pub struct TextCallbackData<'a> { - pub event_flag: InputTextFlags, - pub flags: InputTextFlags, - pub user_data: UserData<'a>, - pub event_char: char, - pub event_key: EventDirection, - pub buf: TextCallbackBuffer<'a>, -} +// pub struct TextCallbackData<'a> { +// pub event_flag: InputTextFlags, +// pub flags: InputTextFlags, +// pub user_data: UserData<'a>, +// pub event_char: char, +// pub event_key: EventDirection, +// pub buf: TextCallbackBuffer<'a>, +// } pub struct TextCallbackBuffer<'a> { buf: &'a mut str, @@ -295,10 +295,10 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { let text_info = TextInformation { flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), buf: TextCallbackBuffer { - buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + buf: std::str::from_utf8_mut(dbg!(std::slice::from_raw_parts_mut( (*data).Buf as *mut u8, - (*data).BufSize as usize - 1, - )) + (*data).BufSize as usize, + ))) .expect("internal imgui error -- it boofed a utf8"), dirty: &mut (*data).BufDirty, cursor_pos: &mut (*data).CursorPos, diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index a887b71..16e6c84 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -20,8 +20,9 @@ pub use self::fonts::glyph_ranges::*; pub use self::input::keyboard::*; pub use self::input::mouse::*; pub use self::input_widget::{ - InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, InputInt3, InputInt4, - InputText, InputTextFlags, InputTextMultiline, + EventDirection, InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, + InputInt3, InputInt4, InputText, InputTextCallback, InputTextMultiline, TextCallbackBuffer, + TextCallbackHandler, TextInformation, InputTextFlags }; pub use self::io::*; pub use self::layout::*; From f6d9d1b77dccd270409d2c3467431b6cae07a7a8 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Sun, 5 Sep 2021 15:46:22 -0700 Subject: [PATCH 03/15] added safety guide --- imgui/src/input_widget.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index e2a726a..4b80d46 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -141,6 +141,9 @@ impl TextCallbackBuffer<'_> { } /// Gives access to the underlying byte array MUTABLY. + /// + /// ## Safety + /// /// This is very unsafe, and the following invariants must be /// upheld: /// 1. Keep the data utf8 valid. @@ -219,6 +222,8 @@ impl TextCallbackBuffer<'_> { /// would require the String to resize, it will be resized by calling the /// Callback_Resize callback. This is automatically handled. /// + /// ## Safety + /// /// It is up to the caller to confirm that the `pos` is a valid byte /// position, or use [insert_chars] which will panic if it isn't. pub unsafe fn insert_chars_unsafe(&mut self, pos: usize, s: &str) { @@ -255,6 +260,11 @@ impl TextCallbackBuffer<'_> { /// Removes the given number of bytes from the string starting /// at some byte pos, without checking for utf8 validity. Use [remove_chars] /// for a safe variant. + /// + /// ## Safety + /// + /// It is up to the caller to ensure that the position is at a valid utf8 char_boundary + /// and that there are enough bytes within the string remaining. pub unsafe fn remove_chars_unchecked(&mut self, pos: usize, byte_count: usize) { sys::ImGuiInputTextCallbackData_DeleteChars( self.callback_data, From d6069b976ae04278b6293860c470a00b74871f54 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Sun, 5 Sep 2021 22:55:03 -0700 Subject: [PATCH 04/15] think of the widgets! --- imgui-examples/examples/keyboard.rs | 23 ++++++++++++-- imgui/src/input_widget.rs | 48 ++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index 5b59ba5..be2b241 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -11,7 +11,8 @@ fn main() { let mut uncaptured_counter = 0u32; let mut home_counter = 0u32; let mut f1_release_count = 0u32; - let mut text_buffer = ImString::new(""); + let mut text_buffer = ImString::new("with some buffer"); + text_buffer.reserve(100); system.main_loop(move |_, ui| { Window::new(im_str!("Means of accessing key state")) @@ -78,13 +79,24 @@ fn main() { struct Cback; impl TextCallbackHandler for Cback { - fn char_filter(&mut self, c: char, txt: &TextInformation<'_>) -> Option { + fn char_filter(&mut self, c: char) -> Option { if c == 'a' { None } else { Some(c) } } + + fn on_completion(&mut self, info: &mut TextInformation<'_>) { + println!("Completion txt was {:?}", info.buf.buf()) + } + + fn on_history(&mut self, dir: EventDirection, info: &mut TextInformation<'_>) { + match dir { + EventDirection::Up => info.buf.clear(), + EventDirection::Down => info.buf.insert_chars(0, "Hello there"), + } + } } // Note that `is_key_released` gives the state of the @@ -92,7 +104,12 @@ fn main() { // example, if you try to type into this input, the // above interaction still counts the key presses. ui.input_text(im_str!("##Dummy text input widget"), &mut text_buffer) - .callback(InputTextCallback::CHAR_FILTER, &mut Cback) + .callback( + InputTextCallback::CHAR_FILTER + | InputTextCallback::COMPLETION + | InputTextCallback::HISTORY, + &mut Cback, + ) .hint(im_str!("Example text input")) .build(); diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 4b80d46..9738b20 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -46,6 +46,8 @@ bitflags!( const CALLBACK_COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; /// Call user function on pressing Up/Down arrows (for history handling) const CALLBACK_HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; + /// Call user function on pressing Up/Down arrows (for history handling) + const CALLBACK_EDIT = sys::ImGuiInputTextFlags_CallbackEdit; /// Call user function every time. User code may query cursor position, modify text buffer. const CALLBACK_ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; /// Call user function to filter character. @@ -77,13 +79,16 @@ pub trait TextCallbackHandler { /// and returning another char substitutes it out. /// /// The default implementation is a passthrough filter (ie, doesn't do anything). - fn char_filter(&mut self, c: char, _: &TextInformation<'_>) -> Option { + fn char_filter(&mut self, c: char) -> Option { Some(c) } /// Allows one to perform autocompletion work when the Tab key has been pressed. fn on_completion(&mut self, _: &mut TextInformation<'_>) {} + /// Allows one to edit the inner buffer whenever the buffer has been changed. + fn on_edit(&mut self, _: &mut TextInformation<'_>) {} + /// A callback when one of the direction keys have been pressed. fn on_history(&mut self, _: EventDirection, _: &mut TextInformation<'_>) {} @@ -238,6 +243,13 @@ impl TextCallbackBuffer<'_> { ) } + /// Clears the string. + pub fn clear(&mut self) { + unsafe { + self.remove_chars_unchecked(0, self.buf().len()); + } + } + /// Removes the given number of characters from the string starting /// at some byte pos. /// @@ -298,18 +310,18 @@ impl TextCallbackBuffer<'_> { /// data. Not sure yet though. extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { // This is always safe to do because imgui promises that this is a valid pointer. - // let data = unsafe { *data }; - - // convert it to a friendlier type let (mut text_info, callback_data) = unsafe { + dbg!(*data); + // println!("the friggen CHAR is {}", im_str); let text_info = TextInformation { flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), buf: TextCallbackBuffer { - buf: std::str::from_utf8_mut(dbg!(std::slice::from_raw_parts_mut( + buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( (*data).Buf as *mut u8, - (*data).BufSize as usize, - ))) + (*data).BufTextLen as usize, + )) .expect("internal imgui error -- it boofed a utf8"), + dirty: &mut (*data).BufDirty, cursor_pos: &mut (*data).CursorPos, selection_start: &mut (*data).SelectionStart, @@ -327,7 +339,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { }; // check this callback. - match callback_data.event_flag { + match dbg!(callback_data.event_flag) { InputTextCallback::ALWAYS => { todo!() } @@ -347,11 +359,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { } InputTextCallback::CHAR_FILTER => { let chr = unsafe { char::from_u32((*data).EventChar).unwrap() }; - let new_data = match callback_data - .user_data - .cback_handler - .char_filter(chr, &text_info) - { + let new_data = match callback_data.user_data.cback_handler.char_filter(chr) { Some(value) => u32::from(value), // 0 means "do not use this char" in imgui docs None => 0, @@ -362,7 +370,10 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { } } InputTextCallback::COMPLETION => { - // callback_data.user_data.cback_handler.on_completion(&mut) + callback_data + .user_data + .cback_handler + .on_completion(&mut text_info); } InputTextCallback::HISTORY => { let key = unsafe { @@ -378,6 +389,12 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { .cback_handler .on_history(key, &mut text_info); } + InputTextCallback::EDIT => { + callback_data + .user_data + .cback_handler + .on_completion(&mut text_info); + } _ => {} } @@ -576,6 +593,9 @@ impl<'ui, 'p> InputText<'ui, 'p> { if callbacks.contains(InputTextCallback::CHAR_FILTER) { self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); } + if callbacks.contains(InputTextCallback::EDIT) { + self.flags.insert(InputTextFlags::CALLBACK_EDIT); + } self.callback_handler = callback; self } From 1a2a7d1473ea1746c4241d478a34deba6eb06dd7 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Mon, 6 Sep 2021 11:57:52 -0700 Subject: [PATCH 05/15] looking good feeling good --- imgui-examples/examples/keyboard.rs | 29 +--- imgui/src/input_widget.rs | 240 +++++++++++++++------------- 2 files changed, 134 insertions(+), 135 deletions(-) diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index be2b241..bf8fb9f 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -77,39 +77,12 @@ fn main() { ctrl_a_counter )); - struct Cback; - impl TextCallbackHandler for Cback { - fn char_filter(&mut self, c: char) -> Option { - if c == 'a' { - None - } else { - Some(c) - } - } - - fn on_completion(&mut self, info: &mut TextInformation<'_>) { - println!("Completion txt was {:?}", info.buf.buf()) - } - - fn on_history(&mut self, dir: EventDirection, info: &mut TextInformation<'_>) { - match dir { - EventDirection::Up => info.buf.clear(), - EventDirection::Down => info.buf.insert_chars(0, "Hello there"), - } - } - } - // Note that `is_key_released` gives the state of the // key regardless of what widget has focus, for // example, if you try to type into this input, the // above interaction still counts the key presses. ui.input_text(im_str!("##Dummy text input widget"), &mut text_buffer) - .callback( - InputTextCallback::CHAR_FILTER - | InputTextCallback::COMPLETION - | InputTextCallback::HISTORY, - &mut Cback, - ) + // .do_not_resize() if you pass this, then this won't resize! .hint(im_str!("Example text input")) .build(); diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 9738b20..c624fd5 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -8,7 +8,8 @@ use crate::sys; use crate::{ImStr, ImString, Ui}; bitflags!( - /// Callback flags + /// Callback flags for an `InputText` widget. These correspond to + /// the general textflags. pub struct InputTextCallback: u32 { /// Call user function on pressing TAB (for completion handling) const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; @@ -18,8 +19,22 @@ bitflags!( const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; /// Call user function to filter character. const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; - /// Allow buffer capacity resize + notify when the string wants to be resized - const RESIZE = sys::ImGuiInputTextFlags_CallbackResize; + /// Callback on buffer edit (note that InputText already returns true on edit, the + /// callback is useful mainly to manipulate the underlying buffer while focus is active) + const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; + } +); + +bitflags!( + /// Callback flags for an `InputTextMultiline` widget. These correspond to the + /// general textflags. + pub struct InputTextMultilineCallbacks: u32 { + /// Call user function on pressing TAB (for completion handling) + const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; + /// Call user function every time. User code may query cursor position, modify text buffer. + const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; + /// Call user function to filter character. + const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; /// Callback on buffer edit (note that InputText already returns true on edit, the /// callback is useful mainly to manipulate the underlying buffer while focus is active) const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; @@ -78,21 +93,23 @@ pub trait TextCallbackHandler { /// Filters a char -- returning a `None` means that the char is removed, /// and returning another char substitutes it out. /// - /// The default implementation is a passthrough filter (ie, doesn't do anything). + /// Because of upstream ImGui choices, you do not have access to the buffer + /// during this callback (for some reason). fn char_filter(&mut self, c: char) -> Option { Some(c) } /// Allows one to perform autocompletion work when the Tab key has been pressed. - fn on_completion(&mut self, _: &mut TextInformation<'_>) {} + fn on_completion(&mut self, _: TextInformation<'_>) {} /// Allows one to edit the inner buffer whenever the buffer has been changed. - fn on_edit(&mut self, _: &mut TextInformation<'_>) {} + fn on_edit(&mut self, _: TextInformation<'_>) {} /// A callback when one of the direction keys have been pressed. - fn on_history(&mut self, _: EventDirection, _: &mut TextInformation<'_>) {} + fn on_history(&mut self, _: EventDirection, _: TextInformation<'_>) {} - // fn on_always(&mut self, state: &mut TextCallbackState); + /// A callback which will always fire, each tick. + fn on_always(&mut self, _: TextInformation<'_>) {} } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -110,26 +127,6 @@ pub struct TextInformation<'a> { pub buf: TextCallbackBuffer<'a>, } -#[repr(C)] -pub struct UserData<'a> { - container: &'a mut ImString, - cback_handler: &'a mut dyn TextCallbackHandler, -} - -struct CallbackData<'a> { - event_flag: InputTextCallback, - user_data: &'a mut UserData<'a>, -} - -// pub struct TextCallbackData<'a> { -// pub event_flag: InputTextFlags, -// pub flags: InputTextFlags, -// pub user_data: UserData<'a>, -// pub event_char: char, -// pub event_key: EventDirection, -// pub buf: TextCallbackBuffer<'a>, -// } - pub struct TextCallbackBuffer<'a> { buf: &'a mut str, dirty: &'a mut bool, @@ -212,7 +209,7 @@ impl TextCallbackBuffer<'_> { /// Inserts the given string at the given position. If this /// would require the String to resize, it will be resized by calling the - /// Callback_Resize callback. This is automatically handled. + /// `CALLBACK_RESIZE` callback. This is automatically handled. /// /// ## Panics /// Panics if the `pos` is not a char_boundary. @@ -306,44 +303,68 @@ impl TextCallbackBuffer<'_> { } } +#[repr(C)] +struct UserData<'a> { + container: &'a mut ImString, + cback_handler: &'a mut dyn TextCallbackHandler, +} + /// Currently, this might contain UB. We may be holdling two mutable pointers to the same /// data. Not sure yet though. extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { - // This is always safe to do because imgui promises that this is a valid pointer. - let (mut text_info, callback_data) = unsafe { - dbg!(*data); - // println!("the friggen CHAR is {}", im_str); - let text_info = TextInformation { - flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), - buf: TextCallbackBuffer { - buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( - (*data).Buf as *mut u8, - (*data).BufTextLen as usize, - )) - .expect("internal imgui error -- it boofed a utf8"), + struct CallbackData<'a> { + event_flag: InputTextFlags, + user_data: &'a mut UserData<'a>, + } - dirty: &mut (*data).BufDirty, - cursor_pos: &mut (*data).CursorPos, - selection_start: &mut (*data).SelectionStart, - selection_end: &mut (*data).SelectionEnd, - callback_data: data, - }, - }; - - let callback_data = CallbackData { - event_flag: InputTextCallback::from_bits((*data).EventFlag as u32).unwrap(), + let callback_data = unsafe { + CallbackData { + event_flag: InputTextFlags::from_bits((*data).EventFlag as u32).unwrap(), user_data: &mut *((*data).UserData as *mut UserData), - }; + } + }; - (text_info, callback_data) + let make_txt_data = || { + // This safe in every callback EXCEPT RESIZE. + unsafe { + TextInformation { + flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), + buf: TextCallbackBuffer { + // specifically, this will bork in resize + buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + (*data).Buf as *mut u8, + (*data).BufTextLen as usize, + )) + .expect("internal imgui error -- it boofed a utf8"), + + dirty: &mut (*data).BufDirty, + cursor_pos: &mut (*data).CursorPos, + selection_start: &mut (*data).SelectionStart, + selection_end: &mut (*data).SelectionEnd, + callback_data: data, + }, + } + } }; // check this callback. - match dbg!(callback_data.event_flag) { - InputTextCallback::ALWAYS => { - todo!() + match callback_data.event_flag { + InputTextFlags::CALLBACK_ALWAYS => { + let text_info = make_txt_data(); + callback_data.user_data.cback_handler.on_always(text_info); } - InputTextCallback::RESIZE => { + InputTextFlags::CALLBACK_EDIT => { + let text_info = make_txt_data(); + callback_data.user_data.cback_handler.on_edit(text_info); + } + InputTextFlags::CALLBACK_COMPLETION => { + let text_info = make_txt_data(); + callback_data + .user_data + .cback_handler + .on_completion(text_info); + } + InputTextFlags::CALLBACK_RESIZE => { unsafe { let requested_size = (*data).BufSize as usize; let buffer = &mut callback_data.user_data.container; @@ -357,7 +378,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { } } } - InputTextCallback::CHAR_FILTER => { + InputTextFlags::CALLBACK_CHAR_FILTER => { let chr = unsafe { char::from_u32((*data).EventChar).unwrap() }; let new_data = match callback_data.user_data.cback_handler.char_filter(chr) { Some(value) => u32::from(value), @@ -369,13 +390,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { (*data).EventChar = new_data; } } - InputTextCallback::COMPLETION => { - callback_data - .user_data - .cback_handler - .on_completion(&mut text_info); - } - InputTextCallback::HISTORY => { + InputTextFlags::CALLBACK_HISTORY => { let key = unsafe { let key = (*data).EventKey as u32; match key { @@ -384,17 +399,14 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { _ => panic!("Unexpected key"), } }; + let text_info = make_txt_data(); + callback_data .user_data .cback_handler - .on_history(key, &mut text_info); - } - InputTextCallback::EDIT => { - callback_data - .user_data - .cback_handler - .on_completion(&mut text_info); + .on_history(key, text_info); } + _ => {} } @@ -445,36 +457,6 @@ macro_rules! impl_text_flags { self } - // #[inline] - // pub fn callback_completion(mut self, value: bool) -> Self { - // self.flags.set(InputTextFlags::CALLBACK_COMPLETION, value); - // self - // } - - // #[inline] - // pub fn callback_history(mut self, value: bool) -> Self { - // self.flags.set(InputTextFlags::CALLBACK_HISTORY, value); - // self - // } - - // #[inline] - // pub fn callback_always(mut self, value: bool) -> Self { - // self.flags.set(InputTextFlags::CALLBACK_ALWAYS, value); - // self - // } - - // #[inline] - // pub fn callback_char_filter(mut self, value: bool) -> Self { - // self.flags.set(InputTextFlags::CALLBACK_CHAR_FILTER, value); - // self - // } - - // #[inline] - // pub fn resize_buffer(mut self, value: bool) -> Self { - // self.flags.set(InputTextFlags::CALLBACK_RESIZE, value); - // self - // } - #[inline] pub fn allow_tab_input(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::ALLOW_TAB_INPUT, value); @@ -572,8 +554,16 @@ impl<'ui, 'p> InputText<'ui, 'p> { impl_text_flags!(InputText); - // TODO: boxed closure...? - // pub fn callback(self) -> Self { } + /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes + /// for `InputText` and `InputTextMultiline`. + /// + /// If, for some reason, you don't want this, you can run this function to prevent this. + /// In that case, edits which would cause a resize will not occur. + #[inline] + pub fn do_not_resize(mut self) -> Self { + self.flags.remove(InputTextFlags::CALLBACK_RESIZE); + self + } #[inline] pub fn callback( @@ -642,6 +632,7 @@ pub struct InputTextMultiline<'ui, 'p> { buf: &'p mut ImString, flags: InputTextFlags, size: [f32; 2], + callback_handler: &'p mut dyn TextCallbackHandler, _phantom: PhantomData<&'ui Ui<'ui>>, } @@ -650,16 +641,51 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p> { InputTextMultiline { label, buf, - flags: InputTextFlags::empty(), + flags: InputTextFlags::CALLBACK_RESIZE, size, + // this is safe because + callback_handler: unsafe { &mut PASSTHROUGH_CALLBACK }, _phantom: PhantomData, } } impl_text_flags!(InputText); - // TODO: boxed closure...? - // pub fn callback(self) -> Self { } + /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes + /// for `InputText` and `InputTextMultiline`. + /// + /// If, for some reason, you don't want this, you can run this function to prevent this. + /// In that case, edits which would cause a resize will not occur. + #[inline] + pub fn do_not_resize(mut self) -> Self { + self.flags.remove(InputTextFlags::CALLBACK_RESIZE); + self + } + + #[inline] + pub fn callback( + mut self, + callbacks: InputTextMultilineCallbacks, + callback: &'p mut dyn TextCallbackHandler, + ) -> Self { + if callbacks.contains(InputTextMultilineCallbacks::COMPLETION) { + self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); + } + // if callbacks.contains(InputTextMultilineCallbacks::HISTORY) { + // self.flags.insert(InputTextFlags::CALLBACK_HISTORY); + // } + if callbacks.contains(InputTextMultilineCallbacks::ALWAYS) { + self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); + } + if callbacks.contains(InputTextMultilineCallbacks::CHAR_FILTER) { + self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); + } + if callbacks.contains(InputTextMultilineCallbacks::EDIT) { + self.flags.insert(InputTextFlags::CALLBACK_EDIT); + } + self.callback_handler = callback; + self + } pub fn build(self) -> bool { let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); From fd5d6b2d9fbb560182190b8c910ebd81e2c19b94 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Mon, 6 Sep 2021 12:00:40 -0700 Subject: [PATCH 06/15] added the callbacks for multiline input --- imgui/src/input_widget.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index c624fd5..226dc37 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -2,7 +2,6 @@ use bitflags::bitflags; use std::marker::PhantomData; use std::ops::Range; use std::os::raw::{c_char, c_int, c_void}; -use std::ptr; use crate::sys; use crate::{ImStr, ImString, Ui}; @@ -689,13 +688,12 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p> { pub fn build(self) -> bool { let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); - let (callback, data): (sys::ImGuiInputTextCallback, _) = { - if self.flags.contains(InputTextFlags::CALLBACK_RESIZE) { - (Some(callback), self.buf as *mut _ as *mut c_void) - } else { - (None, ptr::null_mut()) - } + + let mut data = UserData { + container: self.buf, + cback_handler: self.callback_handler, }; + let data = &mut data as *mut _ as *mut c_void; unsafe { let result = sys::igInputTextMultiline( @@ -704,7 +702,7 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p> { capacity, self.size.into(), self.flags.bits() as i32, - callback, + Some(callback), data, ); self.buf.refresh_len(); From 64f57879408661bf0ef66cad008a5956717f7c18 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Mon, 6 Sep 2021 16:05:19 -0700 Subject: [PATCH 07/15] Somehow the text flags are getting sheared in multiline inputs? let's just ignore them! --- imgui-examples/examples/keyboard.rs | 12 ++++-- imgui/src/input_widget.rs | 61 +++++++++++------------------ imgui/src/lib.rs | 6 +-- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index bf8fb9f..73cd38a 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -81,10 +81,14 @@ fn main() { // key regardless of what widget has focus, for // example, if you try to type into this input, the // above interaction still counts the key presses. - ui.input_text(im_str!("##Dummy text input widget"), &mut text_buffer) - // .do_not_resize() if you pass this, then this won't resize! - .hint(im_str!("Example text input")) - .build(); + ui.input_text_multiline( + im_str!("##Dummy text input widget"), + &mut text_buffer, + [100.0, 100.0], + ) + // .do_not_resize() if you pass this, then this won't resize! + // .hint(im_str!("Example text input")) + .build(); // If you want to check if a widget is capturing // keyboard input, you can check diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 226dc37..968802f 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -27,7 +27,7 @@ bitflags!( bitflags!( /// Callback flags for an `InputTextMultiline` widget. These correspond to the /// general textflags. - pub struct InputTextMultilineCallbacks: u32 { + pub struct InputTextMultilineCallback: u32 { /// Call user function on pressing TAB (for completion handling) const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; /// Call user function every time. User code may query cursor position, modify text buffer. @@ -99,16 +99,16 @@ pub trait TextCallbackHandler { } /// Allows one to perform autocompletion work when the Tab key has been pressed. - fn on_completion(&mut self, _: TextInformation<'_>) {} + fn on_completion(&mut self, _: TextCallbackBuffer<'_>) {} /// Allows one to edit the inner buffer whenever the buffer has been changed. - fn on_edit(&mut self, _: TextInformation<'_>) {} + fn on_edit(&mut self, _: TextCallbackBuffer<'_>) {} /// A callback when one of the direction keys have been pressed. - fn on_history(&mut self, _: EventDirection, _: TextInformation<'_>) {} + fn on_history(&mut self, _: EventDirection, _: TextCallbackBuffer<'_>) {} /// A callback which will always fire, each tick. - fn on_always(&mut self, _: TextInformation<'_>) {} + fn on_always(&mut self, _: TextCallbackBuffer<'_>) {} } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -117,15 +117,6 @@ pub enum EventDirection { Down, } -pub struct TextInformation<'a> { - /// All of the flags that are POSSIBLY given in a callback. - pub flags: InputTextFlags, - - /// The buffer itself. If you can access this mutably, you can - /// do things to the underlying buffer. - pub buf: TextCallbackBuffer<'a>, -} - pub struct TextCallbackBuffer<'a> { buf: &'a mut str, dirty: &'a mut bool, @@ -213,7 +204,7 @@ impl TextCallbackBuffer<'_> { /// ## Panics /// Panics if the `pos` is not a char_boundary. pub fn insert_chars(&mut self, pos: usize, s: &str) { - assert!(s.is_char_boundary(pos)); + assert!(self.buf.is_char_boundary(pos)); unsafe { self.insert_chars_unsafe(pos, s); } @@ -326,22 +317,19 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { let make_txt_data = || { // This safe in every callback EXCEPT RESIZE. unsafe { - TextInformation { - flags: InputTextFlags::from_bits((*data).Flags as u32).unwrap(), - buf: TextCallbackBuffer { - // specifically, this will bork in resize - buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( - (*data).Buf as *mut u8, - (*data).BufTextLen as usize, - )) - .expect("internal imgui error -- it boofed a utf8"), + TextCallbackBuffer { + // specifically, this will bork in resize + buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + (*data).Buf as *mut u8, + (*data).BufTextLen as usize, + )) + .expect("internal imgui error -- it boofed a utf8"), - dirty: &mut (*data).BufDirty, - cursor_pos: &mut (*data).CursorPos, - selection_start: &mut (*data).SelectionStart, - selection_end: &mut (*data).SelectionEnd, - callback_data: data, - }, + dirty: &mut (*data).BufDirty, + cursor_pos: &mut (*data).CursorPos, + selection_start: &mut (*data).SelectionStart, + selection_end: &mut (*data).SelectionEnd, + callback_data: data, } } }; @@ -664,22 +652,19 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p> { #[inline] pub fn callback( mut self, - callbacks: InputTextMultilineCallbacks, + callbacks: InputTextMultilineCallback, callback: &'p mut dyn TextCallbackHandler, ) -> Self { - if callbacks.contains(InputTextMultilineCallbacks::COMPLETION) { + if callbacks.contains(InputTextMultilineCallback::COMPLETION) { self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); } - // if callbacks.contains(InputTextMultilineCallbacks::HISTORY) { - // self.flags.insert(InputTextFlags::CALLBACK_HISTORY); - // } - if callbacks.contains(InputTextMultilineCallbacks::ALWAYS) { + if callbacks.contains(InputTextMultilineCallback::ALWAYS) { self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); } - if callbacks.contains(InputTextMultilineCallbacks::CHAR_FILTER) { + if callbacks.contains(InputTextMultilineCallback::CHAR_FILTER) { self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); } - if callbacks.contains(InputTextMultilineCallbacks::EDIT) { + if callbacks.contains(InputTextMultilineCallback::EDIT) { self.flags.insert(InputTextFlags::CALLBACK_EDIT); } self.callback_handler = callback; diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 16e6c84..8bfe9bf 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -19,11 +19,7 @@ pub use self::fonts::glyph::*; pub use self::fonts::glyph_ranges::*; pub use self::input::keyboard::*; pub use self::input::mouse::*; -pub use self::input_widget::{ - EventDirection, InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, - InputInt3, InputInt4, InputText, InputTextCallback, InputTextMultiline, TextCallbackBuffer, - TextCallbackHandler, TextInformation, InputTextFlags -}; +pub use self::input_widget::*; pub use self::io::*; pub use self::layout::*; pub use self::list_clipper::ListClipper; From f011d9e3377c39fd4e0f84e8ffb818efc28c1494 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Tue, 7 Sep 2021 14:57:24 -0700 Subject: [PATCH 08/15] redid buffer creation --- imgui/src/input_widget.rs | 96 +++++++++++++++++++++++++++------------ 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 968802f..dc4b0eb 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -118,18 +118,58 @@ pub enum EventDirection { } pub struct TextCallbackBuffer<'a> { - buf: &'a mut str, + str: &'a mut str, dirty: &'a mut bool, cursor_pos: &'a mut i32, + selection_start: &'a mut i32, selection_end: &'a mut i32, callback_data: *mut sys::ImGuiInputTextCallbackData, + buf_start: *const c_char, + buf_len: *const c_int, } -impl TextCallbackBuffer<'_> { +impl<'a> TextCallbackBuffer<'a> { + /// Creates the buffer. + unsafe fn new(data: &'a mut sys::ImGuiInputTextCallbackData) -> Self { + let ptr = data as *mut _; + let mut output = Self { + // specifically, this will bork in resize + str: std::str::from_utf8_mut(&mut []).unwrap(), + dirty: &mut data.BufDirty, + cursor_pos: &mut data.CursorPos, + selection_start: &mut data.SelectionStart, + selection_end: &mut data.SelectionEnd, + callback_data: ptr, + buf_start: (*data).Buf as *const _, + buf_len: &(*data).BufTextLen as *const i32, + }; + + output.refresh_str(); + + output + } + + /// Refreshes the public facing `str`, such that it appears + /// to users as if we're working with a normal Rust string. + /// + /// You should only need to call this if you use [str_as_bytes_mut]. + pub fn refresh_str(&mut self) { + // safety -- we never hand out mutable pointers to BufStart or BufLen, + // so this is always set by imgui, and imgui always is utf8, + // but we are more or less trusting that imgui won't boof this. + unsafe { + self.str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + self.buf_start as *const _ as *mut _, + *self.buf_len as usize, + )) + .expect("internal imgui error -- it boofed a utf8") + } + } + /// Get a reference to the text callback buffer's buf. - pub fn buf(&self) -> &str { - self.buf + pub fn str(&self) -> &str { + self.str } /// Gives access to the underlying byte array MUTABLY. @@ -140,14 +180,15 @@ impl TextCallbackBuffer<'_> { /// upheld: /// 1. Keep the data utf8 valid. /// 2. After editing the string, call [set_dirty]. + /// 3. Finally, call [refresh_str]. /// /// We present this string with a `str` interface immutably, which /// actually somewhat weakens `trunc` operations on the string. /// You can use [remove_chars] to handle this operation completely /// safely for you. However, if this is still too limiting, /// please submit an issue. - pub unsafe fn buf_mut(&mut self) -> &mut [u8] { - self.buf.as_bytes_mut() + pub unsafe fn str_as_bytes_mut(&mut self) -> &mut [u8] { + self.str.as_bytes_mut() } /// Sets the dirty flag on the text to imgui, indicating that @@ -173,7 +214,7 @@ impl TextCallbackBuffer<'_> { /// Returns the selected text directly. Note that if no text is selected, /// an empty str slice will be returned. pub fn selected(&self) -> &str { - &self.buf[self.selection()] + &self.str[self.selection()] } /// Sets the cursor to select all. This is always a valid operation, @@ -197,6 +238,16 @@ impl TextCallbackBuffer<'_> { unsafe { sys::ImGuiInputTextCallbackData_HasSelection(self.callback_data) } } + /// Pushes the given str to the end of this buffer. If this + /// would require the String to resize, it will be resized by calling the + /// `CALLBACK_RESIZE` callback. This is automatically handled. + pub fn push_str(&mut self, s: &str) { + // this is safe because the ench of a self.str is a char_boundary. + unsafe { + self.insert_chars_unsafe(self.str.len(), s); + } + } + /// Inserts the given string at the given position. If this /// would require the String to resize, it will be resized by calling the /// `CALLBACK_RESIZE` callback. This is automatically handled. @@ -204,7 +255,7 @@ impl TextCallbackBuffer<'_> { /// ## Panics /// Panics if the `pos` is not a char_boundary. pub fn insert_chars(&mut self, pos: usize, s: &str) { - assert!(self.buf.is_char_boundary(pos)); + assert!(self.str.is_char_boundary(pos)); unsafe { self.insert_chars_unsafe(pos, s); } @@ -227,13 +278,14 @@ impl TextCallbackBuffer<'_> { pos as i32, start as *const c_char, end as *const c_char, - ) + ); + self.refresh_str(); } - /// Clears the string. + /// Clears the string to an empty buffer. pub fn clear(&mut self) { unsafe { - self.remove_chars_unchecked(0, self.buf().len()); + self.remove_chars_unchecked(0, self.str.len()); } } @@ -244,7 +296,7 @@ impl TextCallbackBuffer<'_> { /// Panics if the `pos` is not a char boundary or if /// there are not enough chars remaining. pub fn remove_chars(&mut self, pos: usize, char_count: usize) { - let inner = &self.buf[pos..]; + let inner = &self.str[pos..]; let byte_count = inner .char_indices() .nth(char_count) @@ -269,7 +321,8 @@ impl TextCallbackBuffer<'_> { self.callback_data, pos as i32, byte_count as i32, - ) + ); + self.refresh_str(); } /// Get a reference to the text callback buffer's cursor pos. @@ -316,22 +369,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { let make_txt_data = || { // This safe in every callback EXCEPT RESIZE. - unsafe { - TextCallbackBuffer { - // specifically, this will bork in resize - buf: std::str::from_utf8_mut(std::slice::from_raw_parts_mut( - (*data).Buf as *mut u8, - (*data).BufTextLen as usize, - )) - .expect("internal imgui error -- it boofed a utf8"), - - dirty: &mut (*data).BufDirty, - cursor_pos: &mut (*data).CursorPos, - selection_start: &mut (*data).SelectionStart, - selection_end: &mut (*data).SelectionEnd, - callback_data: data, - } - } + unsafe { TextCallbackBuffer::new(&mut *data) } }; // check this callback. From e17dc259c1298f8a3e642a96530a4b821fd129e6 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Tue, 7 Sep 2021 15:28:44 -0700 Subject: [PATCH 09/15] using std to handle u32s --- imgui/src/input_widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index dc4b0eb..cdc5e82 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -404,7 +404,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { } } InputTextFlags::CALLBACK_CHAR_FILTER => { - let chr = unsafe { char::from_u32((*data).EventChar).unwrap() }; + let chr = unsafe { std::char::from_u32((*data).EventChar).unwrap() }; let new_data = match callback_data.user_data.cback_handler.char_filter(chr) { Some(value) => u32::from(value), // 0 means "do not use this char" in imgui docs From d00dd15a9ceca76799847a3c3abfd10b93c0ee88 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 09:21:49 -0700 Subject: [PATCH 10/15] redid how the callback worked to keep it significantly safer --- imgui/src/input_widget.rs | 127 +++++++++++++------------------------- 1 file changed, 43 insertions(+), 84 deletions(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index cdc5e82..f75df4b 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -117,61 +117,25 @@ pub enum EventDirection { Down, } -pub struct TextCallbackBuffer<'a> { - str: &'a mut str, - dirty: &'a mut bool, - cursor_pos: &'a mut i32, - - selection_start: &'a mut i32, - selection_end: &'a mut i32, - callback_data: *mut sys::ImGuiInputTextCallbackData, - buf_start: *const c_char, - buf_len: *const c_int, -} +pub struct TextCallbackBuffer<'a>(&'a mut sys::ImGuiInputTextCallbackData); impl<'a> TextCallbackBuffer<'a> { /// Creates the buffer. unsafe fn new(data: &'a mut sys::ImGuiInputTextCallbackData) -> Self { - let ptr = data as *mut _; - let mut output = Self { - // specifically, this will bork in resize - str: std::str::from_utf8_mut(&mut []).unwrap(), - dirty: &mut data.BufDirty, - cursor_pos: &mut data.CursorPos, - selection_start: &mut data.SelectionStart, - selection_end: &mut data.SelectionEnd, - callback_data: ptr, - buf_start: (*data).Buf as *const _, - buf_len: &(*data).BufTextLen as *const i32, - }; - - output.refresh_str(); - - output + Self(data) } - /// Refreshes the public facing `str`, such that it appears - /// to users as if we're working with a normal Rust string. - /// - /// You should only need to call this if you use [str_as_bytes_mut]. - pub fn refresh_str(&mut self) { - // safety -- we never hand out mutable pointers to BufStart or BufLen, - // so this is always set by imgui, and imgui always is utf8, - // but we are more or less trusting that imgui won't boof this. + /// Get a reference to the text callback buffer's str. + pub fn str(&self) -> &str { unsafe { - self.str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut( - self.buf_start as *const _ as *mut _, - *self.buf_len as usize, + std::str::from_utf8(std::slice::from_raw_parts( + self.0.Buf as *const _, + self.0.BufTextLen as usize, )) .expect("internal imgui error -- it boofed a utf8") } } - /// Get a reference to the text callback buffer's buf. - pub fn str(&self) -> &str { - self.str - } - /// Gives access to the underlying byte array MUTABLY. /// /// ## Safety @@ -180,15 +144,20 @@ impl<'a> TextCallbackBuffer<'a> { /// upheld: /// 1. Keep the data utf8 valid. /// 2. After editing the string, call [set_dirty]. - /// 3. Finally, call [refresh_str]. /// - /// We present this string with a `str` interface immutably, which - /// actually somewhat weakens `trunc` operations on the string. - /// You can use [remove_chars] to handle this operation completely - /// safely for you. However, if this is still too limiting, - /// please submit an issue. + /// To truncate the string, please use [remove_chars]. To extend + /// the string, please use [insert_chars] and [push_str]. + /// + /// This function should have highly limited usage, but could be for + /// editing certain characters in the buffer based on some external condition. pub unsafe fn str_as_bytes_mut(&mut self) -> &mut [u8] { - self.str.as_bytes_mut() + let str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut( + self.0.Buf as *const _ as *mut _, + self.0.BufTextLen as usize, + )) + .expect("internal imgui error -- it boofed a utf8"); + + str.as_bytes_mut() } /// Sets the dirty flag on the text to imgui, indicating that @@ -199,7 +168,7 @@ impl<'a> TextCallbackBuffer<'a> { /// this will be set for you. However, this is no downside to setting /// the dirty flag spuriously except the minor CPU time imgui will spend. pub fn set_dirty(&mut self) { - *self.dirty = true; + self.0.BufDirty = true; } /// Gets a range of the selected text. See [selection_start_mut] and @@ -208,34 +177,34 @@ impl<'a> TextCallbackBuffer<'a> { /// This Range is given in `usize` so that it might be used in indexing /// operations more easily. To quickly grab the selected text, use [selected]. pub fn selection(&self) -> Range { - *self.selection_start as usize..*self.selection_end as usize + self.0.SelectionStart as usize..self.0.SelectionEnd as usize } /// Returns the selected text directly. Note that if no text is selected, /// an empty str slice will be returned. pub fn selected(&self) -> &str { - &self.str[self.selection()] + &self.str()[self.selection()] } /// Sets the cursor to select all. This is always a valid operation, /// and so it takes an `&self`. - pub fn select_all(&self) { + pub fn select_all(&mut self) { unsafe { - sys::ImGuiInputTextCallbackData_SelectAll(self.callback_data); + sys::ImGuiInputTextCallbackData_SelectAll(self.0); } } /// Clears the selection. This is always a valid operation, /// and so it takes an `&self`. - pub fn clear_selection(&self) { + pub fn clear_selection(&mut self) { unsafe { - sys::ImGuiInputTextCallbackData_ClearSelection(self.callback_data); + sys::ImGuiInputTextCallbackData_ClearSelection(self.0); } } /// Checks if there is a selection within the text. pub fn has_selection(&self) -> bool { - unsafe { sys::ImGuiInputTextCallbackData_HasSelection(self.callback_data) } + !self.selection().is_empty() } /// Pushes the given str to the end of this buffer. If this @@ -244,7 +213,7 @@ impl<'a> TextCallbackBuffer<'a> { pub fn push_str(&mut self, s: &str) { // this is safe because the ench of a self.str is a char_boundary. unsafe { - self.insert_chars_unsafe(self.str.len(), s); + self.insert_chars_unsafe(self.0.BufTextLen as usize, s); } } @@ -255,7 +224,7 @@ impl<'a> TextCallbackBuffer<'a> { /// ## Panics /// Panics if the `pos` is not a char_boundary. pub fn insert_chars(&mut self, pos: usize, s: &str) { - assert!(self.str.is_char_boundary(pos)); + assert!(self.str().is_char_boundary(pos)); unsafe { self.insert_chars_unsafe(pos, s); } @@ -274,18 +243,17 @@ impl<'a> TextCallbackBuffer<'a> { let end = start.add(s.len()); sys::ImGuiInputTextCallbackData_InsertChars( - self.callback_data, + self.0, pos as i32, start as *const c_char, end as *const c_char, ); - self.refresh_str(); } /// Clears the string to an empty buffer. pub fn clear(&mut self) { unsafe { - self.remove_chars_unchecked(0, self.str.len()); + self.remove_chars_unchecked(0, self.0.BufTextLen as usize); } } @@ -296,7 +264,7 @@ impl<'a> TextCallbackBuffer<'a> { /// Panics if the `pos` is not a char boundary or if /// there are not enough chars remaining. pub fn remove_chars(&mut self, pos: usize, char_count: usize) { - let inner = &self.str[pos..]; + let inner = &self.str()[pos..]; let byte_count = inner .char_indices() .nth(char_count) @@ -317,32 +285,27 @@ impl<'a> TextCallbackBuffer<'a> { /// It is up to the caller to ensure that the position is at a valid utf8 char_boundary /// and that there are enough bytes within the string remaining. pub unsafe fn remove_chars_unchecked(&mut self, pos: usize, byte_count: usize) { - sys::ImGuiInputTextCallbackData_DeleteChars( - self.callback_data, - pos as i32, - byte_count as i32, - ); - self.refresh_str(); + sys::ImGuiInputTextCallbackData_DeleteChars(self.0, pos as i32, byte_count as i32); } /// Get a reference to the text callback buffer's cursor pos. pub fn cursor_pos(&self) -> usize { - *self.cursor_pos as usize + self.0.CursorPos as usize } /// Set the text callback buffer's cursor pos. pub fn set_cursor_pos(&mut self, cursor_pos: usize) { - *self.cursor_pos = cursor_pos as i32; + self.0.CursorPos = cursor_pos as i32; } /// Get a mutable reference to the text callback buffer's selection start. pub fn selection_start_mut(&mut self) -> &mut i32 { - self.selection_start + &mut self.0.SelectionStart } - /// Get a mutable reference to the text callback buffer's selection start. + /// Get a mutable reference to the text callback buffer's selection end.. pub fn selection_end_mut(&mut self) -> &mut i32 { - self.selection_end + &mut self.0.SelectionEnd } } @@ -367,23 +330,19 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { } }; - let make_txt_data = || { - // This safe in every callback EXCEPT RESIZE. - unsafe { TextCallbackBuffer::new(&mut *data) } - }; - // check this callback. match callback_data.event_flag { InputTextFlags::CALLBACK_ALWAYS => { - let text_info = make_txt_data(); + let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; callback_data.user_data.cback_handler.on_always(text_info); } InputTextFlags::CALLBACK_EDIT => { - let text_info = make_txt_data(); + let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; + callback_data.user_data.cback_handler.on_edit(text_info); } InputTextFlags::CALLBACK_COMPLETION => { - let text_info = make_txt_data(); + let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; callback_data .user_data .cback_handler @@ -424,7 +383,7 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { _ => panic!("Unexpected key"), } }; - let text_info = make_txt_data(); + let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; callback_data .user_data From 213433ffd3e9498cc5f6f14394b99508f1c5ff83 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 10:13:38 -0700 Subject: [PATCH 11/15] updated code significantly and removed UB --- imgui/src/input_widget.rs | 1039 +++++++++++++++++++------------------ 1 file changed, 535 insertions(+), 504 deletions(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index f75df4b..9969558 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -6,40 +6,6 @@ use std::os::raw::{c_char, c_int, c_void}; use crate::sys; use crate::{ImStr, ImString, Ui}; -bitflags!( - /// Callback flags for an `InputText` widget. These correspond to - /// the general textflags. - pub struct InputTextCallback: u32 { - /// Call user function on pressing TAB (for completion handling) - const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; - /// Call user function on pressing Up/Down arrows (for history handling) - const HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; - /// Call user function every time. User code may query cursor position, modify text buffer. - const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; - /// Call user function to filter character. - const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; - /// Callback on buffer edit (note that InputText already returns true on edit, the - /// callback is useful mainly to manipulate the underlying buffer while focus is active) - const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; - } -); - -bitflags!( - /// Callback flags for an `InputTextMultiline` widget. These correspond to the - /// general textflags. - pub struct InputTextMultilineCallback: u32 { - /// Call user function on pressing TAB (for completion handling) - const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; - /// Call user function every time. User code may query cursor position, modify text buffer. - const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; - /// Call user function to filter character. - const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; - /// Callback on buffer edit (note that InputText already returns true on edit, the - /// callback is useful mainly to manipulate the underlying buffer while focus is active) - const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; - } -); - bitflags!( /// Flags for text inputs #[repr(C)] @@ -88,38 +54,547 @@ bitflags!( } ); +macro_rules! impl_text_flags { + ($InputType:ident) => { + #[inline] + pub fn flags(mut self, flags: InputTextFlags) -> Self { + self.flags = flags; + self + } + + #[inline] + pub fn chars_decimal(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::CHARS_DECIMAL, value); + self + } + + #[inline] + pub fn chars_hexadecimal(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::CHARS_HEXADECIMAL, value); + self + } + + #[inline] + pub fn chars_uppercase(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::CHARS_UPPERCASE, value); + self + } + + #[inline] + pub fn chars_noblank(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::CHARS_NO_BLANK, value); + self + } + + #[inline] + pub fn auto_select_all(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::AUTO_SELECT_ALL, value); + self + } + + #[inline] + pub fn enter_returns_true(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::ENTER_RETURNS_TRUE, value); + self + } + + #[inline] + pub fn allow_tab_input(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::ALLOW_TAB_INPUT, value); + self + } + + #[inline] + pub fn no_horizontal_scroll(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::NO_HORIZONTAL_SCROLL, value); + self + } + + /// Note: this is equivalent to `always_overwrite` + #[inline] + pub fn always_insert_mode(self, value: bool) -> Self { + self.always_overwrite(value) + } + + #[inline] + #[allow(deprecated)] + pub fn always_overwrite(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::ALWAYS_OVERWRITE, value); + self + } + + #[inline] + pub fn read_only(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::READ_ONLY, value); + self + } + + #[inline] + pub fn password(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::PASSWORD, value); + self + } + + #[inline] + pub fn no_undo_redo(mut self, value: bool) -> Self { + self.flags.set(InputTextFlags::NO_UNDO_REDO, value); + self + } + }; +} + +macro_rules! impl_step_params { + ($InputType:ident, $Value:ty) => { + #[inline] + pub fn step(mut self, value: $Value) -> Self { + self.step = value; + self + } + + #[inline] + pub fn step_fast(mut self, value: $Value) -> Self { + self.step_fast = value; + self + } + }; +} + +#[must_use] +pub struct InputText<'ui, 'p, T = PassthroughCallback> { + label: &'p ImStr, + hint: Option<&'p ImStr>, + buf: &'p mut ImString, + callback_handler: T, + flags: InputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui, 'p> InputText<'ui, 'p, PassthroughCallback> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, buf: &'p mut ImString) -> Self { + InputText { + label, + hint: None, + // this is fine because no one else has access to this and imgui is single threaded. + callback_handler: PassthroughCallback, + buf, + flags: InputTextFlags::CALLBACK_RESIZE, + _phantom: PhantomData, + } + } +} + +impl<'ui, 'p, T: TextCallbackHandler> InputText<'ui, 'p, T> { + /// Sets the hint displayed in the input text background. + #[inline] + pub fn hint(mut self, hint: &'p ImStr) -> Self { + self.hint = Some(hint); + self + } + + impl_text_flags!(InputText); + + /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes + /// for `InputText` and `InputTextMultiline`. + /// + /// If, for some reason, you don't want this, you can run this function to prevent this. + /// In that case, edits which would cause a resize will not occur. + #[inline] + pub fn do_not_resize(mut self) -> Self { + self.flags.remove(InputTextFlags::CALLBACK_RESIZE); + self + } + + #[inline] + pub fn callback(mut self, callbacks: InputTextCallback, callback: T) -> InputText<'ui, 'p, T> { + if callbacks.contains(InputTextCallback::COMPLETION) { + self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); + } + if callbacks.contains(InputTextCallback::HISTORY) { + self.flags.insert(InputTextFlags::CALLBACK_HISTORY); + } + if callbacks.contains(InputTextCallback::ALWAYS) { + self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); + } + if callbacks.contains(InputTextCallback::CHAR_FILTER) { + self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); + } + if callbacks.contains(InputTextCallback::EDIT) { + self.flags.insert(InputTextFlags::CALLBACK_EDIT); + } + self.callback_handler = callback; + self + } + + pub fn build(self) -> bool { + let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); + + let mut data = UserData { + container: self.buf, + cback_handler: self.callback_handler, + }; + let data = &mut data as *mut _ as *mut c_void; + + unsafe { + let result = if let Some(hint) = self.hint { + sys::igInputTextWithHint( + self.label.as_ptr(), + hint.as_ptr(), + ptr, + capacity, + self.flags.bits() as i32, + Some(callback::), + data, + ) + } else { + sys::igInputText( + self.label.as_ptr(), + ptr, + capacity, + self.flags.bits() as i32, + Some(callback::), + data, + ) + }; + self.buf.refresh_len(); + result + } + } +} + +#[must_use] +pub struct InputTextMultiline<'ui, 'p, T = PassthroughCallback> { + label: &'p ImStr, + buf: &'p mut ImString, + flags: InputTextFlags, + size: [f32; 2], + callback_handler: T, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui, 'p> InputTextMultiline<'ui, 'p, PassthroughCallback> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, buf: &'p mut ImString, size: [f32; 2]) -> Self { + InputTextMultiline { + label, + buf, + flags: InputTextFlags::CALLBACK_RESIZE, + size, + // this is safe because + callback_handler: PassthroughCallback, + _phantom: PhantomData, + } + } +} + +impl<'ui, 'p, T: TextCallbackHandler> InputTextMultiline<'ui, 'p, T> { + impl_text_flags!(InputText); + + /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes + /// for `InputText` and `InputTextMultiline`. + /// + /// If, for some reason, you don't want this, you can run this function to prevent this. + /// In that case, edits which would cause a resize will not occur. + #[inline] + pub fn do_not_resize(mut self) -> Self { + self.flags.remove(InputTextFlags::CALLBACK_RESIZE); + self + } + + #[inline] + pub fn callback( + mut self, + callbacks: InputTextMultilineCallback, + callback: T, + ) -> InputTextMultiline<'ui, 'p, T> { + if callbacks.contains(InputTextMultilineCallback::COMPLETION) { + self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); + } + if callbacks.contains(InputTextMultilineCallback::ALWAYS) { + self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); + } + if callbacks.contains(InputTextMultilineCallback::CHAR_FILTER) { + self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); + } + if callbacks.contains(InputTextMultilineCallback::EDIT) { + self.flags.insert(InputTextFlags::CALLBACK_EDIT); + } + self.callback_handler = callback; + self + } + + pub fn build(self) -> bool { + let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); + + let mut data = UserData { + container: self.buf, + cback_handler: self.callback_handler, + }; + let data = &mut data as *mut _ as *mut c_void; + + unsafe { + let result = sys::igInputTextMultiline( + self.label.as_ptr(), + ptr, + capacity, + self.size.into(), + self.flags.bits() as i32, + Some(callback::), + data, + ); + self.buf.refresh_len(); + result + } + } +} + +#[must_use] +pub struct InputInt<'ui, 'p> { + label: &'p ImStr, + value: &'p mut i32, + step: i32, + step_fast: i32, + flags: InputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui, 'p> InputInt<'ui, 'p> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut i32) -> Self { + InputInt { + label, + value, + step: 1, + step_fast: 100, + flags: InputTextFlags::empty(), + _phantom: PhantomData, + } + } + + pub fn build(self) -> bool { + unsafe { + sys::igInputInt( + self.label.as_ptr(), + self.value as *mut i32, + self.step, + self.step_fast, + self.flags.bits() as i32, + ) + } + } + + impl_step_params!(InputInt, i32); + impl_text_flags!(InputInt); +} + +#[must_use] +pub struct InputFloat<'ui, 'p> { + label: &'p ImStr, + value: &'p mut f32, + step: f32, + step_fast: f32, + flags: InputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, +} + +impl<'ui, 'p> InputFloat<'ui, 'p> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut f32) -> Self { + InputFloat { + label, + value, + step: 0.0, + step_fast: 0.0, + flags: InputTextFlags::empty(), + _phantom: PhantomData, + } + } + + pub fn build(self) -> bool { + unsafe { + sys::igInputFloat( + self.label.as_ptr(), + self.value as *mut f32, + self.step, + self.step_fast, + b"%.3f\0".as_ptr() as *const _, + self.flags.bits() as i32, + ) + } + } + + impl_step_params!(InputFloat, f32); + impl_text_flags!(InputFloat); +} + +macro_rules! impl_input_floatn { + ($InputFloatN:ident, $N:expr, $igInputFloatN:ident) => { + #[must_use] + pub struct $InputFloatN<'ui, 'p> { + label: &'p ImStr, + value: &'p mut [f32; $N], + flags: InputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, + } + + impl<'ui, 'p> $InputFloatN<'ui, 'p> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut [f32; $N]) -> Self { + $InputFloatN { + label, + value, + flags: InputTextFlags::empty(), + _phantom: PhantomData, + } + } + + pub fn build(self) -> bool { + unsafe { + sys::$igInputFloatN( + self.label.as_ptr(), + self.value.as_mut_ptr(), + b"%.3f\0".as_ptr() as *const _, + self.flags.bits() as i32, + ) + } + } + + impl_text_flags!($InputFloatN); + } + }; +} + +impl_input_floatn!(InputFloat2, 2, igInputFloat2); +impl_input_floatn!(InputFloat3, 3, igInputFloat3); +impl_input_floatn!(InputFloat4, 4, igInputFloat4); + +macro_rules! impl_input_intn { + ($InputIntN:ident, $N:expr, $igInputIntN:ident) => { + #[must_use] + pub struct $InputIntN<'ui, 'p> { + label: &'p ImStr, + value: &'p mut [i32; $N], + flags: InputTextFlags, + _phantom: PhantomData<&'ui Ui<'ui>>, + } + + impl<'ui, 'p> $InputIntN<'ui, 'p> { + pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut [i32; $N]) -> Self { + $InputIntN { + label, + value, + flags: InputTextFlags::empty(), + _phantom: PhantomData, + } + } + + pub fn build(self) -> bool { + unsafe { + sys::$igInputIntN( + self.label.as_ptr(), + self.value.as_mut_ptr(), + self.flags.bits() as i32, + ) + } + } + + impl_text_flags!($InputIntN); + } + }; +} + +impl_input_intn!(InputInt2, 2, igInputInt2); +impl_input_intn!(InputInt3, 3, igInputInt3); +impl_input_intn!(InputInt4, 4, igInputInt4); + +bitflags!( + /// Callback flags for an `InputText` widget. These correspond to + /// the general textflags. + pub struct InputTextCallback: u32 { + /// Call user function on pressing TAB (for completion handling) + const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; + /// Call user function on pressing Up/Down arrows (for history handling) + const HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; + /// Call user function every time. User code may query cursor position, modify text buffer. + const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; + /// Call user function to filter character. + const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; + /// Callback on buffer edit (note that InputText already returns true on edit, the + /// callback is useful mainly to manipulate the underlying buffer while focus is active) + const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; + } +); + +bitflags!( + /// Callback flags for an `InputTextMultiline` widget. These correspond to the + /// general textflags. + pub struct InputTextMultilineCallback: u32 { + /// Call user function on pressing TAB (for completion handling) + const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; + /// Call user function every time. User code may query cursor position, modify text buffer. + const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; + /// Call user function to filter character. + const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; + /// Callback on buffer edit (note that InputText already returns true on edit, the + /// callback is useful mainly to manipulate the underlying buffer while focus is active) + const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; + } +); + +/// This trait provides an interface which ImGui will call on `InputText` +/// and `InputTextMultiline` callbacks. +/// +/// Each method is called *if and only if* the corresponding flag for each +/// method is passed to ImGui in the `callback` builder. +/// +/// Each method here lists the flag required to call it, and this module begins +/// with an example of callbacks being used. pub trait TextCallbackHandler { /// Filters a char -- returning a `None` means that the char is removed, /// and returning another char substitutes it out. /// /// Because of upstream ImGui choices, you do not have access to the buffer /// during this callback (for some reason). + /// + /// To make ImGui run this callback, use [InputTextCallback::CHAR_FILTER] or + /// [InputTextMultilineCallback::CALLBACK]. fn char_filter(&mut self, c: char) -> Option { Some(c) } /// Allows one to perform autocompletion work when the Tab key has been pressed. - fn on_completion(&mut self, _: TextCallbackBuffer<'_>) {} + /// + /// To make ImGui run this callback, use [InputTextCallback::COMPLETION] or + /// [InputTextMultilineCallback::COMPLETION]. + fn on_completion(&mut self, _: TextCallbackData<'_>) {} /// Allows one to edit the inner buffer whenever the buffer has been changed. - fn on_edit(&mut self, _: TextCallbackBuffer<'_>) {} + /// + /// To make ImGui run this callback, use [InputTextCallback::EDIT] or + /// [InputTextMultilineCallback::EDIT]. + fn on_edit(&mut self, _: TextCallbackData<'_>) {} /// A callback when one of the direction keys have been pressed. - fn on_history(&mut self, _: EventDirection, _: TextCallbackBuffer<'_>) {} + /// + /// To make ImGui run this callback, use [InputTextCallback::HISTORY]. It appears + /// that this callback will not be ran in a multiline input widget at all. + fn on_history(&mut self, _: HistoryDirection, _: TextCallbackData<'_>) {} /// A callback which will always fire, each tick. - fn on_always(&mut self, _: TextCallbackBuffer<'_>) {} + /// + /// To make ImGui run this callback, use [InputTextCallback::ALWAYS] or + /// [InputTextMultilineCallback::ALWAYS]. + fn on_always(&mut self, _: TextCallbackData<'_>) {} } +/// The arrow key a user pressed to trigger the `on_history` callback. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum EventDirection { +pub enum HistoryDirection { Up, Down, } -pub struct TextCallbackBuffer<'a>(&'a mut sys::ImGuiInputTextCallbackData); +/// This struct provides methods to edit the underlying text buffer that +/// Dear ImGui manipulates. Primarily, it gives [remove_chars], [insert_chars], +/// and mutable access to what text is selected. +pub struct TextCallbackData<'a>(&'a mut sys::ImGuiInputTextCallbackData); -impl<'a> TextCallbackBuffer<'a> { +impl<'a> TextCallbackData<'a> { /// Creates the buffer. unsafe fn new(data: &'a mut sys::ImGuiInputTextCallbackData) -> Self { Self(data) @@ -310,39 +785,40 @@ impl<'a> TextCallbackBuffer<'a> { } #[repr(C)] -struct UserData<'a> { +struct UserData<'a, T> { container: &'a mut ImString, - cback_handler: &'a mut dyn TextCallbackHandler, + cback_handler: T, } -/// Currently, this might contain UB. We may be holdling two mutable pointers to the same -/// data. Not sure yet though. -extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { - struct CallbackData<'a> { +/// This is our default callback. +extern "C" fn callback( + data: *mut sys::ImGuiInputTextCallbackData, +) -> c_int { + struct CallbackData<'a, T> { event_flag: InputTextFlags, - user_data: &'a mut UserData<'a>, + user_data: &'a mut UserData<'a, T>, } let callback_data = unsafe { CallbackData { event_flag: InputTextFlags::from_bits((*data).EventFlag as u32).unwrap(), - user_data: &mut *((*data).UserData as *mut UserData), + user_data: &mut *((*data).UserData as *mut UserData), } }; // check this callback. match callback_data.event_flag { InputTextFlags::CALLBACK_ALWAYS => { - let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; + let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data.user_data.cback_handler.on_always(text_info); } InputTextFlags::CALLBACK_EDIT => { - let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; + let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data.user_data.cback_handler.on_edit(text_info); } InputTextFlags::CALLBACK_COMPLETION => { - let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; + let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data .user_data .cback_handler @@ -378,12 +854,12 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { let key = unsafe { let key = (*data).EventKey as u32; match key { - sys::ImGuiKey_UpArrow => EventDirection::Up, - sys::ImGuiKey_DownArrow => EventDirection::Down, + sys::ImGuiKey_UpArrow => HistoryDirection::Up, + sys::ImGuiKey_DownArrow => HistoryDirection::Down, _ => panic!("Unexpected key"), } }; - let text_info = unsafe { TextCallbackBuffer::new(&mut *data) }; + let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data .user_data @@ -397,454 +873,9 @@ extern "C" fn callback(data: *mut sys::ImGuiInputTextCallbackData) -> c_int { 0 } -macro_rules! impl_text_flags { - ($InputType:ident) => { - #[inline] - pub fn flags(mut self, flags: InputTextFlags) -> Self { - self.flags = flags; - self - } - - #[inline] - pub fn chars_decimal(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CHARS_DECIMAL, value); - self - } - - #[inline] - pub fn chars_hexadecimal(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CHARS_HEXADECIMAL, value); - self - } - - #[inline] - pub fn chars_uppercase(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CHARS_UPPERCASE, value); - self - } - - #[inline] - pub fn chars_noblank(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::CHARS_NO_BLANK, value); - self - } - - #[inline] - pub fn auto_select_all(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::AUTO_SELECT_ALL, value); - self - } - - #[inline] - pub fn enter_returns_true(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::ENTER_RETURNS_TRUE, value); - self - } - - #[inline] - pub fn allow_tab_input(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::ALLOW_TAB_INPUT, value); - self - } - - #[inline] - pub fn no_horizontal_scroll(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::NO_HORIZONTAL_SCROLL, value); - self - } - - /// Note: this is equivalent to `always_overwrite` - #[inline] - pub fn always_insert_mode(self, value: bool) -> Self { - self.always_overwrite(value) - } - - #[inline] - #[allow(deprecated)] - pub fn always_overwrite(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::ALWAYS_OVERWRITE, value); - self - } - - #[inline] - pub fn read_only(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::READ_ONLY, value); - self - } - - #[inline] - pub fn password(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::PASSWORD, value); - self - } - - #[inline] - pub fn no_undo_redo(mut self, value: bool) -> Self { - self.flags.set(InputTextFlags::NO_UNDO_REDO, value); - self - } - }; -} - -macro_rules! impl_step_params { - ($InputType:ident, $Value:ty) => { - #[inline] - pub fn step(mut self, value: $Value) -> Self { - self.step = value; - self - } - - #[inline] - pub fn step_fast(mut self, value: $Value) -> Self { - self.step_fast = value; - self - } - }; -} - -static mut PASSTHROUGH_CALLBACK: PassthroughCallback = PassthroughCallback; +/// This is a Zst which implements TextCallbackHandler as a passthrough. +/// +/// If you do not set a callback handler, this will be used (but will never +/// actually run, since you will not have pass imgui any flags). pub struct PassthroughCallback; impl TextCallbackHandler for PassthroughCallback {} - -#[must_use] -pub struct InputText<'ui, 'p> { - label: &'p ImStr, - hint: Option<&'p ImStr>, - buf: &'p mut ImString, - callback_handler: &'p mut dyn TextCallbackHandler, - flags: InputTextFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> InputText<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, buf: &'p mut ImString) -> Self { - InputText { - label, - hint: None, - // this is fine because no one else has access to this and imgui is single threaded. - callback_handler: unsafe { &mut PASSTHROUGH_CALLBACK }, - buf, - flags: InputTextFlags::CALLBACK_RESIZE, - _phantom: PhantomData, - } - } - - /// Sets the hint displayed in the input text background. - #[inline] - pub fn hint(mut self, hint: &'p ImStr) -> Self { - self.hint = Some(hint); - self - } - - impl_text_flags!(InputText); - - /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes - /// for `InputText` and `InputTextMultiline`. - /// - /// If, for some reason, you don't want this, you can run this function to prevent this. - /// In that case, edits which would cause a resize will not occur. - #[inline] - pub fn do_not_resize(mut self) -> Self { - self.flags.remove(InputTextFlags::CALLBACK_RESIZE); - self - } - - #[inline] - pub fn callback( - mut self, - callbacks: InputTextCallback, - callback: &'p mut dyn TextCallbackHandler, - ) -> Self { - if callbacks.contains(InputTextCallback::COMPLETION) { - self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); - } - if callbacks.contains(InputTextCallback::HISTORY) { - self.flags.insert(InputTextFlags::CALLBACK_HISTORY); - } - if callbacks.contains(InputTextCallback::ALWAYS) { - self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); - } - if callbacks.contains(InputTextCallback::CHAR_FILTER) { - self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); - } - if callbacks.contains(InputTextCallback::EDIT) { - self.flags.insert(InputTextFlags::CALLBACK_EDIT); - } - self.callback_handler = callback; - self - } - - pub fn build(self) -> bool { - let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); - - let mut data = UserData { - container: self.buf, - cback_handler: self.callback_handler, - }; - let data = &mut data as *mut _ as *mut c_void; - - unsafe { - let result = if let Some(hint) = self.hint { - sys::igInputTextWithHint( - self.label.as_ptr(), - hint.as_ptr(), - ptr, - capacity, - self.flags.bits() as i32, - Some(callback), - data, - ) - } else { - sys::igInputText( - self.label.as_ptr(), - ptr, - capacity, - self.flags.bits() as i32, - Some(callback), - data, - ) - }; - self.buf.refresh_len(); - result - } - } -} - -#[must_use] -pub struct InputTextMultiline<'ui, 'p> { - label: &'p ImStr, - buf: &'p mut ImString, - flags: InputTextFlags, - size: [f32; 2], - callback_handler: &'p mut dyn TextCallbackHandler, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> InputTextMultiline<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, buf: &'p mut ImString, size: [f32; 2]) -> Self { - InputTextMultiline { - label, - buf, - flags: InputTextFlags::CALLBACK_RESIZE, - size, - // this is safe because - callback_handler: unsafe { &mut PASSTHROUGH_CALLBACK }, - _phantom: PhantomData, - } - } - - impl_text_flags!(InputText); - - /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes - /// for `InputText` and `InputTextMultiline`. - /// - /// If, for some reason, you don't want this, you can run this function to prevent this. - /// In that case, edits which would cause a resize will not occur. - #[inline] - pub fn do_not_resize(mut self) -> Self { - self.flags.remove(InputTextFlags::CALLBACK_RESIZE); - self - } - - #[inline] - pub fn callback( - mut self, - callbacks: InputTextMultilineCallback, - callback: &'p mut dyn TextCallbackHandler, - ) -> Self { - if callbacks.contains(InputTextMultilineCallback::COMPLETION) { - self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); - } - if callbacks.contains(InputTextMultilineCallback::ALWAYS) { - self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); - } - if callbacks.contains(InputTextMultilineCallback::CHAR_FILTER) { - self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); - } - if callbacks.contains(InputTextMultilineCallback::EDIT) { - self.flags.insert(InputTextFlags::CALLBACK_EDIT); - } - self.callback_handler = callback; - self - } - - pub fn build(self) -> bool { - let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity_with_nul()); - - let mut data = UserData { - container: self.buf, - cback_handler: self.callback_handler, - }; - let data = &mut data as *mut _ as *mut c_void; - - unsafe { - let result = sys::igInputTextMultiline( - self.label.as_ptr(), - ptr, - capacity, - self.size.into(), - self.flags.bits() as i32, - Some(callback), - data, - ); - self.buf.refresh_len(); - result - } - } -} - -#[must_use] -pub struct InputInt<'ui, 'p> { - label: &'p ImStr, - value: &'p mut i32, - step: i32, - step_fast: i32, - flags: InputTextFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> InputInt<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut i32) -> Self { - InputInt { - label, - value, - step: 1, - step_fast: 100, - flags: InputTextFlags::empty(), - _phantom: PhantomData, - } - } - - pub fn build(self) -> bool { - unsafe { - sys::igInputInt( - self.label.as_ptr(), - self.value as *mut i32, - self.step, - self.step_fast, - self.flags.bits() as i32, - ) - } - } - - impl_step_params!(InputInt, i32); - impl_text_flags!(InputInt); -} - -#[must_use] -pub struct InputFloat<'ui, 'p> { - label: &'p ImStr, - value: &'p mut f32, - step: f32, - step_fast: f32, - flags: InputTextFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, -} - -impl<'ui, 'p> InputFloat<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut f32) -> Self { - InputFloat { - label, - value, - step: 0.0, - step_fast: 0.0, - flags: InputTextFlags::empty(), - _phantom: PhantomData, - } - } - - pub fn build(self) -> bool { - unsafe { - sys::igInputFloat( - self.label.as_ptr(), - self.value as *mut f32, - self.step, - self.step_fast, - b"%.3f\0".as_ptr() as *const _, - self.flags.bits() as i32, - ) - } - } - - impl_step_params!(InputFloat, f32); - impl_text_flags!(InputFloat); -} - -macro_rules! impl_input_floatn { - ($InputFloatN:ident, $N:expr, $igInputFloatN:ident) => { - #[must_use] - pub struct $InputFloatN<'ui, 'p> { - label: &'p ImStr, - value: &'p mut [f32; $N], - flags: InputTextFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, - } - - impl<'ui, 'p> $InputFloatN<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut [f32; $N]) -> Self { - $InputFloatN { - label, - value, - flags: InputTextFlags::empty(), - _phantom: PhantomData, - } - } - - pub fn build(self) -> bool { - unsafe { - sys::$igInputFloatN( - self.label.as_ptr(), - self.value.as_mut_ptr(), - b"%.3f\0".as_ptr() as *const _, - self.flags.bits() as i32, - ) - } - } - - impl_text_flags!($InputFloatN); - } - }; -} - -impl_input_floatn!(InputFloat2, 2, igInputFloat2); -impl_input_floatn!(InputFloat3, 3, igInputFloat3); -impl_input_floatn!(InputFloat4, 4, igInputFloat4); - -macro_rules! impl_input_intn { - ($InputIntN:ident, $N:expr, $igInputIntN:ident) => { - #[must_use] - pub struct $InputIntN<'ui, 'p> { - label: &'p ImStr, - value: &'p mut [i32; $N], - flags: InputTextFlags, - _phantom: PhantomData<&'ui Ui<'ui>>, - } - - impl<'ui, 'p> $InputIntN<'ui, 'p> { - pub fn new(_: &Ui<'ui>, label: &'p ImStr, value: &'p mut [i32; $N]) -> Self { - $InputIntN { - label, - value, - flags: InputTextFlags::empty(), - _phantom: PhantomData, - } - } - - pub fn build(self) -> bool { - unsafe { - sys::$igInputIntN( - self.label.as_ptr(), - self.value.as_mut_ptr(), - self.flags.bits() as i32, - ) - } - } - - impl_text_flags!($InputIntN); - } - }; -} - -impl_input_intn!(InputInt2, 2, igInputInt2); -impl_input_intn!(InputInt3, 3, igInputInt3); -impl_input_intn!(InputInt4, 4, igInputInt4); From 805f5e93e78e98b5dfb84f70741e52dc8a0ca748 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 10:36:32 -0700 Subject: [PATCH 12/15] updated doc comments to fix intra-doc breakages --- imgui/src/input/keyboard.rs | 9 +++---- imgui/src/input_widget.rs | 47 +++++++++++++++++++++---------------- imgui/src/popups.rs | 6 ++--- imgui/src/widget/tree.rs | 5 ++-- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/imgui/src/input/keyboard.rs b/imgui/src/input/keyboard.rs index 204e435..bab12f4 100644 --- a/imgui/src/input/keyboard.rs +++ b/imgui/src/input/keyboard.rs @@ -110,7 +110,7 @@ impl<'ui> Ui<'ui> { self.is_key_index_down(key_index) } - /// Same as [`is_key_down`] but takes a key index. The meaning of + /// Same as [`is_key_down`](Self::is_key_down) but takes a key index. The meaning of /// index is defined by your backend implementation. #[inline] #[doc(alias = "IsKeyDown")] @@ -128,7 +128,7 @@ impl<'ui> Ui<'ui> { self.is_key_index_pressed(key_index) } - /// Same as [`is_key_pressed`] but takes a key index. + /// Same as [`is_key_pressed`](Self::is_key_pressed) but takes a key index. /// /// The meaning of index is defined by your backend /// implementation. @@ -148,7 +148,8 @@ impl<'ui> Ui<'ui> { self.is_key_index_pressed_no_repeat(key_index) } - /// Same as [`is_key_pressed_no_repeat`] but takes a key index. + /// Same as [`is_key_pressed_no_repeat`](Self::is_key_pressed_no_repeat) + /// but takes a key index. /// /// The meaning of index is defined by your backend /// implementation. @@ -166,7 +167,7 @@ impl<'ui> Ui<'ui> { self.is_key_index_released(key_index) } - /// Same as [`is_key_released`] but takes a key index. + /// Same as [`is_key_released`](Self::is_key_released) but takes a key index. /// /// The meaning of index is defined by your backend /// implementation. diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 9969558..764bdbb 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -552,7 +552,7 @@ pub trait TextCallbackHandler { /// during this callback (for some reason). /// /// To make ImGui run this callback, use [InputTextCallback::CHAR_FILTER] or - /// [InputTextMultilineCallback::CALLBACK]. + /// [InputTextMultilineCallback::CHAR_FILTER]. fn char_filter(&mut self, c: char) -> Option { Some(c) } @@ -590,8 +590,8 @@ pub enum HistoryDirection { } /// This struct provides methods to edit the underlying text buffer that -/// Dear ImGui manipulates. Primarily, it gives [remove_chars], [insert_chars], -/// and mutable access to what text is selected. +/// Dear ImGui manipulates. Primarily, it gives [remove_chars](Self::remove_chars), +/// [insert_chars](Self::insert_chars), and mutable access to what text is selected. pub struct TextCallbackData<'a>(&'a mut sys::ImGuiInputTextCallbackData); impl<'a> TextCallbackData<'a> { @@ -625,6 +625,11 @@ impl<'a> TextCallbackData<'a> { /// /// This function should have highly limited usage, but could be for /// editing certain characters in the buffer based on some external condition. + /// + /// [remove_chars]: Self::remove_chars + /// [set_dirty]: Self::set_dirty + /// [insert_chars]: Self::insert_chars + /// [push_str]: Self::push_str pub unsafe fn str_as_bytes_mut(&mut self) -> &mut [u8] { let str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut( self.0.Buf as *const _ as *mut _, @@ -638,19 +643,23 @@ impl<'a> TextCallbackData<'a> { /// Sets the dirty flag on the text to imgui, indicating that /// it should reapply this string to its internal state. /// - /// **NB:** You only need to use this method if you're using `[buf_mut]`. + /// **NB:** You only need to use this method if you're using `[str_as_bytes_mut]`. /// If you use the helper methods [remove_chars] and [insert_chars], /// this will be set for you. However, this is no downside to setting /// the dirty flag spuriously except the minor CPU time imgui will spend. + /// + /// [str_as_bytes_mut]: Self::str_as_bytes_mut + /// [remove_chars]: Self::remove_chars + /// [insert_chars]: Self::insert_chars pub fn set_dirty(&mut self) { self.0.BufDirty = true; } - /// Gets a range of the selected text. See [selection_start_mut] and - /// [selection_end_mut] to mutably edit these values. + /// Gets a range of the selected text. See [selection_start_mut](Self::selection_start_mut) + /// and [selection_end_mut](Self::selection_end_mut) to mutably edit these values. /// /// This Range is given in `usize` so that it might be used in indexing - /// operations more easily. To quickly grab the selected text, use [selected]. + /// operations more easily. To quickly grab the selected text, use [selected](Self::selected). pub fn selection(&self) -> Range { self.0.SelectionStart as usize..self.0.SelectionEnd as usize } @@ -661,16 +670,14 @@ impl<'a> TextCallbackData<'a> { &self.str()[self.selection()] } - /// Sets the cursor to select all. This is always a valid operation, - /// and so it takes an `&self`. + /// Sets the cursor to select all. pub fn select_all(&mut self) { unsafe { sys::ImGuiInputTextCallbackData_SelectAll(self.0); } } - /// Clears the selection. This is always a valid operation, - /// and so it takes an `&self`. + /// Clears the selection. pub fn clear_selection(&mut self) { unsafe { sys::ImGuiInputTextCallbackData_ClearSelection(self.0); @@ -683,8 +690,8 @@ impl<'a> TextCallbackData<'a> { } /// Pushes the given str to the end of this buffer. If this - /// would require the String to resize, it will be resized by calling the - /// `CALLBACK_RESIZE` callback. This is automatically handled. + /// would require the String to resize, it will be resized. + /// This is automatically handled. pub fn push_str(&mut self, s: &str) { // this is safe because the ench of a self.str is a char_boundary. unsafe { @@ -693,8 +700,8 @@ impl<'a> TextCallbackData<'a> { } /// Inserts the given string at the given position. If this - /// would require the String to resize, it will be resized by calling the - /// `CALLBACK_RESIZE` callback. This is automatically handled. + /// would require the String to resize, it will be resized + /// automatically. /// /// ## Panics /// Panics if the `pos` is not a char_boundary. @@ -706,13 +713,13 @@ impl<'a> TextCallbackData<'a> { } /// Inserts the given string at the given position, unsafely. If this - /// would require the String to resize, it will be resized by calling the - /// Callback_Resize callback. This is automatically handled. + /// would require the String to resize, it will be resized automatically. /// /// ## Safety /// /// It is up to the caller to confirm that the `pos` is a valid byte - /// position, or use [insert_chars] which will panic if it isn't. + /// position, or use [insert_chars](Self::insert_chars) which will panic + /// if it isn't. pub unsafe fn insert_chars_unsafe(&mut self, pos: usize, s: &str) { let start = s.as_ptr(); let end = start.add(s.len()); @@ -752,8 +759,8 @@ impl<'a> TextCallbackData<'a> { } /// Removes the given number of bytes from the string starting - /// at some byte pos, without checking for utf8 validity. Use [remove_chars] - /// for a safe variant. + /// at some byte pos, without checking for utf8 validity. Use + /// [remove_chars](Self::remove_chars) for a safe variant. /// /// ## Safety /// diff --git a/imgui/src/popups.rs b/imgui/src/popups.rs index 701cc3a..ca30b10 100644 --- a/imgui/src/popups.rs +++ b/imgui/src/popups.rs @@ -134,8 +134,8 @@ impl<'p> PopupModal<'p> { /// Consume and draw the PopupModal. /// Construct a popup that can have any kind of content. /// - /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* - /// when you want to actual create the popup. + /// This should be called *per frame*, whereas [`Ui::open_popup`] + /// should be called *once* when you want to actual create the popup. #[doc(alias = "BeginPopupModal")] pub fn begin_popup<'ui>(self, ui: &Ui<'ui>) -> Option> { let render = unsafe { @@ -159,7 +159,7 @@ impl<'p> PopupModal<'p> { // Widgets: Popups impl<'ui> Ui<'ui> { /// Instructs ImGui to open a popup, which must be began with either [`begin_popup`](Self::begin_popup) - /// or [`popup`](Self::popup). You also use this function to begin [ModalPopups]. + /// or [`popup`](Self::popup). You also use this function to begin [PopupModal]. /// /// The confusing aspect to popups is that ImGui holds "control" over the popup fundamentally, so that ImGui /// can also force close a popup when a user clicks outside a popup. If you do not want users to be diff --git a/imgui/src/widget/tree.rs b/imgui/src/widget/tree.rs index 37037f1..1c42ebc 100644 --- a/imgui/src/widget/tree.rs +++ b/imgui/src/widget/tree.rs @@ -391,7 +391,7 @@ impl<'a> CollapsingHeader<'a> { /// /// Returns true if the collapsing header is open and content should be rendered. /// - /// This is the same as [build] but is provided for consistent naming. + /// This is the same as [build](Self::build) but is provided for consistent naming. #[must_use] pub fn begin(self, ui: &Ui) -> bool { self.build(ui) @@ -401,7 +401,8 @@ impl<'a> CollapsingHeader<'a> { /// /// Returns true if the collapsing header is open and content should be rendered. /// - /// This is the same as [build_with_close_button] but is provided for consistent naming. + /// This is the same as [build_with_close_button](Self::build_with_close_button) + /// but is provided for consistent naming. #[must_use] pub fn begin_with_close_button(self, ui: &Ui, opened: &mut bool) -> bool { self.build_with_close_button(ui, opened) From a0d3cbb7e4f2f570f83ae610d0be8b4d08cf2349 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 11:50:14 -0700 Subject: [PATCH 13/15] tons of updates, added examples --- imgui-examples/examples/keyboard.rs | 3 +- imgui-examples/examples/text_callbacks.rs | 151 ++++++++++++++++++++++ imgui/src/input_widget.rs | 37 ++++-- 3 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 imgui-examples/examples/text_callbacks.rs diff --git a/imgui-examples/examples/keyboard.rs b/imgui-examples/examples/keyboard.rs index 73cd38a..bf09785 100644 --- a/imgui-examples/examples/keyboard.rs +++ b/imgui-examples/examples/keyboard.rs @@ -11,8 +11,7 @@ fn main() { let mut uncaptured_counter = 0u32; let mut home_counter = 0u32; let mut f1_release_count = 0u32; - let mut text_buffer = ImString::new("with some buffer"); - text_buffer.reserve(100); + let mut text_buffer = ImString::new(""); system.main_loop(move |_, ui| { Window::new(im_str!("Means of accessing key state")) diff --git a/imgui-examples/examples/text_callbacks.rs b/imgui-examples/examples/text_callbacks.rs new file mode 100644 index 0000000..43c4ec3 --- /dev/null +++ b/imgui-examples/examples/text_callbacks.rs @@ -0,0 +1,151 @@ +use imgui::*; + +mod support; + +fn main() { + let system = support::init(file!()); + let mut buffers = vec![ + ImString::default(), + ImString::default(), + ImString::default(), + ]; + + system.main_loop(move |_, ui| { + Window::new(im_str!("Input text callbacks")) + .size([500.0, 300.0], Condition::FirstUseEver) + .build(ui, || { + ui.text("You can make a variety of buffer callbacks on an Input Text"); + ui.text( + "or on an InputTextMultiline. In this example, we'll use \ + InputText primarily.", + ); + ui.text( + "The only difference is that InputTextMultiline doesn't get \ + the `History` callback,", + ); + ui.text("since, of course, you need the up/down keys to navigate."); + + ui.separator(); + + ui.text("No callbacks:"); + ui.input_text(im_str!("buf0"), &mut buffers[0]).build(); + ui.input_text(im_str!("buf0"), &mut buffers[1]).build(); + ui.input_text(im_str!("buf0"), &mut buffers[2]).build(); + + ui.separator(); + + ui.text("Here's a callback which printlns when each is ran."); + + struct AllCallback; + impl InputTextCallbackHandler for AllCallback { + fn char_filter(&mut self, c: char) -> Option { + println!("Char filter fired! This means a char was inputted."); + Some(c) + } + fn on_completion(&mut self, _: TextCallbackData<'_>) { + println!("Completion request fired! This means the tab key was hit."); + } + + fn on_edit(&mut self, _: TextCallbackData<'_>) { + println!("Edit was fired! Any edit will cause this to fire.") + } + + fn on_history(&mut self, dir: HistoryDirection, _: TextCallbackData<'_>) { + println!("History was fired by pressing {:?}", dir); + } + + fn on_always(&mut self, _: TextCallbackData<'_>) { + // We don't actually print this out because it will flood your log a lot! + // println!("The always callback fired! It always fires."); + } + } + + ui.input_text( + im_str!("All Callbacks logging"), + buffers.get_mut(0).unwrap(), + ) + .callback(InputTextCallback::all(), AllCallback) + .build(); + + ui.separator(); + + ui.text("You can also define a callback on structs with data."); + ui.text("Here we implement the callback handler on a wrapper around &mut ImString"); + ui.text("to duplicate edits to buf0 on buf1"); + + struct Wrapper<'a>(&'a mut ImString); + impl<'a> InputTextCallbackHandler for Wrapper<'a> { + fn on_always(&mut self, data: TextCallbackData<'_>) { + *self.0 = im_str!("{}", data.str()); + } + } + + let (buf0, brwchk_dance) = buffers.split_first_mut().unwrap(); + let buf1 = Wrapper(&mut brwchk_dance[0]); + + ui.input_text(im_str!("Edits copied to buf1"), buf0) + .callback(InputTextCallback::ALWAYS, buf1) + .build(); + + ui.separator(); + + ui.text("Finally, we'll do some whacky history to show inserting and removing"); + ui.text("characters from the buffer."); + ui.text( + "Here, pressing UP (while editing the below widget) will remove the\n\ + first and last character from buf2", + ); + ui.text("and pressing DOWN will prepend the first char from buf0 AND"); + ui.text("append the last char from buf1"); + + let (buf0, brwchk_dance) = buffers.split_first_mut().unwrap(); + let (buf1, buf2_dance) = brwchk_dance.split_first_mut().unwrap(); + let buf2 = &mut buf2_dance[0]; + + struct Wrapper2<'a>(&'a str, &'a str); + + impl<'a> InputTextCallbackHandler for Wrapper2<'a> { + fn on_history( + &mut self, + dir: HistoryDirection, + mut data: TextCallbackData<'_>, + ) { + match dir { + HistoryDirection::Up => { + // remove first char... + if !data.str().is_empty() { + data.remove_chars(0, 1); + + if let Some((idx, _)) = data.str().char_indices().rev().next() { + data.remove_chars(idx, 1); + } + } + } + HistoryDirection::Down => { + // insert first char... + if let Some(first_char) = self.0.get(0..1) { + data.insert_chars(0, first_char); + } + + // insert last char + if let Some((idx, _)) = self.1.char_indices().rev().next() { + data.push_str(&self.1[idx..]); + } + } + } + } + } + + ui.input_text(im_str!("Wild buf2 editor"), buf2) + .callback( + InputTextCallback::HISTORY, + Wrapper2(buf0.to_str(), buf1.to_str()), + ) + .build(); + + ui.text( + "For more examples on how to use callbacks non-chaotically, check the demo", + ); + }); + }); +} diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 764bdbb..e89eb59 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -183,7 +183,7 @@ impl<'ui, 'p> InputText<'ui, 'p, PassthroughCallback> { } } -impl<'ui, 'p, T: TextCallbackHandler> InputText<'ui, 'p, T> { +impl<'ui, 'p, T: InputTextCallbackHandler> InputText<'ui, 'p, T> { /// Sets the hint displayed in the input text background. #[inline] pub fn hint(mut self, hint: &'p ImStr) -> Self { @@ -194,7 +194,7 @@ impl<'ui, 'p, T: TextCallbackHandler> InputText<'ui, 'p, T> { impl_text_flags!(InputText); /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes - /// for `InputText` and `InputTextMultiline`. + /// for [InputText] and [InputTextMultiline]. /// /// If, for some reason, you don't want this, you can run this function to prevent this. /// In that case, edits which would cause a resize will not occur. @@ -205,7 +205,11 @@ impl<'ui, 'p, T: TextCallbackHandler> InputText<'ui, 'p, T> { } #[inline] - pub fn callback(mut self, callbacks: InputTextCallback, callback: T) -> InputText<'ui, 'p, T> { + pub fn callback( + mut self, + callbacks: InputTextCallback, + callback: T2, + ) -> InputText<'ui, 'p, T2> { if callbacks.contains(InputTextCallback::COMPLETION) { self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); } @@ -221,8 +225,14 @@ impl<'ui, 'p, T: TextCallbackHandler> InputText<'ui, 'p, T> { if callbacks.contains(InputTextCallback::EDIT) { self.flags.insert(InputTextFlags::CALLBACK_EDIT); } - self.callback_handler = callback; - self + InputText { + callback_handler: callback, + label: self.label, + hint: self.hint, + buf: self.buf, + flags: self.flags, + _phantom: self._phantom, + } } pub fn build(self) -> bool { @@ -285,11 +295,11 @@ impl<'ui, 'p> InputTextMultiline<'ui, 'p, PassthroughCallback> { } } -impl<'ui, 'p, T: TextCallbackHandler> InputTextMultiline<'ui, 'p, T> { +impl<'ui, 'p, T: InputTextCallbackHandler> InputTextMultiline<'ui, 'p, T> { impl_text_flags!(InputText); /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes - /// for `InputText` and `InputTextMultiline`. + /// for [InputText] and [InputTextMultiline]. /// /// If, for some reason, you don't want this, you can run this function to prevent this. /// In that case, edits which would cause a resize will not occur. @@ -544,7 +554,7 @@ bitflags!( /// /// Each method here lists the flag required to call it, and this module begins /// with an example of callbacks being used. -pub trait TextCallbackHandler { +pub trait InputTextCallbackHandler { /// Filters a char -- returning a `None` means that the char is removed, /// and returning another char substitutes it out. /// @@ -743,15 +753,14 @@ impl<'a> TextCallbackData<'a> { /// at some byte pos. /// /// ## Panics - /// Panics if the `pos` is not a char boundary or if - /// there are not enough chars remaining. + /// Panics if the `pos` is not a char boundary. pub fn remove_chars(&mut self, pos: usize, char_count: usize) { let inner = &self.str()[pos..]; let byte_count = inner .char_indices() .nth(char_count) - .expect("not enough characters in string") - .0; + .map(|v| v.0) + .unwrap_or(inner.len()); unsafe { self.remove_chars_unchecked(pos, byte_count); @@ -798,7 +807,7 @@ struct UserData<'a, T> { } /// This is our default callback. -extern "C" fn callback( +extern "C" fn callback( data: *mut sys::ImGuiInputTextCallbackData, ) -> c_int { struct CallbackData<'a, T> { @@ -885,4 +894,4 @@ extern "C" fn callback( /// If you do not set a callback handler, this will be used (but will never /// actually run, since you will not have pass imgui any flags). pub struct PassthroughCallback; -impl TextCallbackHandler for PassthroughCallback {} +impl InputTextCallbackHandler for PassthroughCallback {} From 3d8d5115cf81c54d7d866e2f6923618a52a107b9 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 12:36:25 -0700 Subject: [PATCH 14/15] annoyance --- imgui/src/input_widget.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index e89eb59..753c20a 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -760,7 +760,7 @@ impl<'a> TextCallbackData<'a> { .char_indices() .nth(char_count) .map(|v| v.0) - .unwrap_or(inner.len()); + .unwrap_or_else(|| inner.len()); unsafe { self.remove_chars_unchecked(pos, byte_count); From 818cd60b36fd7558de16ed4842a5bf21a3e3651d Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 14:25:24 -0700 Subject: [PATCH 15/15] borkaborka --- imgui/src/input_widget.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 753c20a..5dd6290 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -310,11 +310,11 @@ impl<'ui, 'p, T: InputTextCallbackHandler> InputTextMultiline<'ui, 'p, T> { } #[inline] - pub fn callback( + pub fn callback( mut self, callbacks: InputTextMultilineCallback, - callback: T, - ) -> InputTextMultiline<'ui, 'p, T> { + callback_handler: T2, + ) -> InputTextMultiline<'ui, 'p, T2> { if callbacks.contains(InputTextMultilineCallback::COMPLETION) { self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); } @@ -327,8 +327,15 @@ impl<'ui, 'p, T: InputTextCallbackHandler> InputTextMultiline<'ui, 'p, T> { if callbacks.contains(InputTextMultilineCallback::EDIT) { self.flags.insert(InputTextFlags::CALLBACK_EDIT); } - self.callback_handler = callback; - self + + InputTextMultiline { + label: self.label, + buf: self.buf, + flags: self.flags, + size: self.size, + callback_handler, + _phantom: self._phantom, + } } pub fn build(self) -> bool {