From a0d3cbb7e4f2f570f83ae610d0be8b4d08cf2349 Mon Sep 17 00:00:00 2001 From: Jack Spira Date: Wed, 8 Sep 2021 11:50:14 -0700 Subject: [PATCH] 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 {}