From 3eaee3359d063fe5dbcb743ec01cf41b28632992 Mon Sep 17 00:00:00 2001 From: Jack Mac Date: Sun, 12 Sep 2021 02:01:07 -0400 Subject: [PATCH] imstr and imstring have been removed. fixed some instability and added a hacky shim to input_text functions. We're looking okay so far, but more testing will be needed --- imgui-examples/examples/support/clipboard.rs | 2 +- imgui-examples/examples/text_callbacks.rs | 24 ++++---- imgui-glow-renderer/src/lib.rs | 7 ++- imgui/src/clipboard.rs | 23 ++++++++ imgui/src/context.rs | 20 ++++--- imgui/src/input_widget.rs | 59 ++++++++++++++----- imgui/src/lib.rs | 60 ++++++++++++++++++-- imgui/src/plotlines.rs | 57 +++++++++++-------- imgui/src/popups.rs | 51 ++++++++--------- imgui/src/string.rs | 2 +- 10 files changed, 208 insertions(+), 97 deletions(-) diff --git a/imgui-examples/examples/support/clipboard.rs b/imgui-examples/examples/support/clipboard.rs index 297a696..613dbb5 100644 --- a/imgui-examples/examples/support/clipboard.rs +++ b/imgui-examples/examples/support/clipboard.rs @@ -1,7 +1,7 @@ use clipboard::{ClipboardContext, ClipboardProvider}; use imgui::ClipboardBackend; -pub struct ClipboardSupport(ClipboardContext); +pub struct ClipboardSupport(pub ClipboardContext); pub fn init() -> Option { ClipboardContext::new().ok().map(ClipboardSupport) diff --git a/imgui-examples/examples/text_callbacks.rs b/imgui-examples/examples/text_callbacks.rs index 0f92955..7c19523 100644 --- a/imgui-examples/examples/text_callbacks.rs +++ b/imgui-examples/examples/text_callbacks.rs @@ -7,7 +7,7 @@ fn main() { let mut buffers = vec![String::default(), String::default(), String::default()]; system.main_loop(move |_, ui| { - Window::new(im_str!("Input text callbacks")) + Window::new("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"); @@ -24,9 +24,10 @@ fn main() { 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.input_text("buf0", &mut buffers[0]).build(); + ui.input_text("buf1", &mut buffers[1]).build(); + ui.input_text("buf2", &mut buffers[2]).build(); ui.separator(); @@ -50,18 +51,15 @@ fn main() { println!("History was fired by pressing {:?}", dir); } - fn on_always(&mut self, _: TextCallbackData<'_>) { + fn on_always(&mut self, txt: 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.input_text("All Callbacks logging", buffers.get_mut(0).unwrap()) + .callback(InputTextCallback::all(), AllCallback) + .build(); ui.separator(); @@ -79,7 +77,7 @@ fn main() { 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) + ui.input_text("Edits copied to buf1", buf0) .callback(InputTextCallback::ALWAYS, buf1) .build(); @@ -132,7 +130,7 @@ fn main() { } } - ui.input_text(im_str!("Wild buf2 editor"), buf2) + ui.input_text("Wild buf2 editor", buf2) .callback(InputTextCallback::HISTORY, Wrapper2(buf0, buf1)) .build(); diff --git a/imgui-glow-renderer/src/lib.rs b/imgui-glow-renderer/src/lib.rs index 4379d16..f6d1814 100644 --- a/imgui-glow-renderer/src/lib.rs +++ b/imgui-glow-renderer/src/lib.rs @@ -513,9 +513,10 @@ impl Renderer { } fn configure_imgui_context(&self, imgui_context: &mut imgui::Context) { - imgui_context.set_renderer_name(Some( - format!("imgui-rs-glow-render {}", env!("CARGO_PKG_VERSION")).into(), - )); + imgui_context.set_renderer_name(Some(format!( + "imgui-rs-glow-render {}", + env!("CARGO_PKG_VERSION") + ))); #[cfg(feature = "vertex_offset_support")] if self.gl_version.vertex_offset_support() { diff --git a/imgui/src/clipboard.rs b/imgui/src/clipboard.rs index 2c9ac9c..7be4043 100644 --- a/imgui/src/clipboard.rs +++ b/imgui/src/clipboard.rs @@ -32,6 +32,24 @@ impl ClipboardContext { last_value: CString::default(), } } + + pub fn dummy() -> ClipboardContext { + Self { + backend: Box::new(DummyClipboardContext), + last_value: CString::default(), + } + } +} + +pub struct DummyClipboardContext; +impl ClipboardBackend for DummyClipboardContext { + fn get(&mut self) -> Option { + None + } + + fn set(&mut self, _: &str) { + // empty + } } impl fmt::Debug for ClipboardContext { @@ -46,9 +64,14 @@ impl fmt::Debug for ClipboardContext { pub(crate) unsafe extern "C" fn get_clipboard_text(user_data: *mut c_void) -> *const c_char { let result = catch_unwind(|| { + println!("gettin!"); let ctx = &mut *(user_data as *mut ClipboardContext); + println!("gettin!"); + match ctx.backend.get() { Some(text) => { + println!("gettin!"); + ctx.last_value = CString::new(text).unwrap(); ctx.last_value.as_ptr() } diff --git a/imgui/src/context.rs b/imgui/src/context.rs index 11b8cca..8139b3c 100644 --- a/imgui/src/context.rs +++ b/imgui/src/context.rs @@ -1,5 +1,5 @@ use parking_lot::ReentrantMutex; -use std::cell::RefCell; +use std::cell::{RefCell, UnsafeCell}; use std::ffi::{CStr, CString}; use std::ops::Drop; use std::path::PathBuf; @@ -55,7 +55,11 @@ pub struct Context { log_filename: Option, platform_name: Option, renderer_name: Option, - clipboard_ctx: Option, + // we need to box this because we hand imgui a pointer to it, + // and we don't want to deal with finding `clipboard_ctx`. + // we also put it in an unsafecell since we're going to give + // imgui a mutable pointer to it. + clipboard_ctx: Box>, } // This mutex needs to be used to guard all public functions that can affect the underlying @@ -203,13 +207,13 @@ impl Context { } /// Sets the clipboard backend used for clipboard operations pub fn set_clipboard_backend(&mut self, backend: T) { - use std::borrow::BorrowMut; - let mut clipboard_ctx = ClipboardContext::new(backend); + let clipboard_ctx: Box> = Box::new(ClipboardContext::new(backend).into()); let io = self.io_mut(); io.set_clipboard_text_fn = Some(crate::clipboard::set_clipboard_text); io.get_clipboard_text_fn = Some(crate::clipboard::get_clipboard_text); - io.clipboard_user_data = clipboard_ctx.borrow_mut() as *mut ClipboardContext as *mut _; - self.clipboard_ctx.replace(clipboard_ctx); + + io.clipboard_user_data = clipboard_ctx.get() as *mut _; + self.clipboard_ctx = clipboard_ctx; } fn create_internal(shared_font_atlas: Option>>) -> Self { let _guard = CTX_MUTEX.lock(); @@ -236,7 +240,7 @@ impl Context { log_filename: None, platform_name: None, renderer_name: None, - clipboard_ctx: None, + clipboard_ctx: Box::new(ClipboardContext::dummy().into()), } } fn is_current_context(&self) -> bool { @@ -317,7 +321,7 @@ impl SuspendedContext { log_filename: None, platform_name: None, renderer_name: None, - clipboard_ctx: None, + clipboard_ctx: Box::new(ClipboardContext::dummy().into()), }; if ctx.is_current_context() { // Oops, the context was activated -> deactivate diff --git a/imgui/src/input_widget.rs b/imgui/src/input_widget.rs index 7ceed36..9c99169 100644 --- a/imgui/src/input_widget.rs +++ b/imgui/src/input_widget.rs @@ -208,8 +208,14 @@ where /// /// 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. + /// + /// # Safety + /// Importantly, we silently push and pop a `\0` to the string given here. + /// If you do not want mutable access (ie, do not want that string to resize), + /// you **must** make sure to null-terminate it yourself. This is janky, but ImGui + /// expects a null termination, and we didn't want to re-allocate an entire string per call. #[inline] - pub fn do_not_resize(mut self) -> Self { + pub unsafe fn do_not_resize(mut self) -> Self { self.flags.remove(InputTextFlags::CALLBACK_RESIZE); self } @@ -246,6 +252,8 @@ where } pub fn build(self) -> bool { + // needs to be null-terminated! + self.buf.push('\0'); let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity()); let mut data = UserData { @@ -254,7 +262,7 @@ where }; let data = &mut data as *mut _ as *mut c_void; - unsafe { + let o = unsafe { if let Some(hint) = self.hint { let (label, hint) = self.ui.scratch_txt_two(self.label, hint); sys::igInputTextWithHint( @@ -278,7 +286,14 @@ where data, ) } + }; + + // it should always end with this \0. + if self.buf.ends_with('\0') { + self.buf.pop(); } + + o } } @@ -349,6 +364,8 @@ impl<'ui, 'p, T: InputTextCallbackHandler, L: AsRef> InputTextMultiline<'ui } pub fn build(self) -> bool { + // needs to be null-terminated! + self.buf.push('\0'); let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity()); let mut data = UserData { @@ -357,7 +374,7 @@ impl<'ui, 'p, T: InputTextCallbackHandler, L: AsRef> InputTextMultiline<'ui }; let data = &mut data as *mut _ as *mut c_void; - unsafe { + let o = unsafe { sys::igInputTextMultiline( self.ui.scratch_txt(self.label), ptr as *mut sys::cty::c_char, @@ -367,7 +384,14 @@ impl<'ui, 'p, T: InputTextCallbackHandler, L: AsRef> InputTextMultiline<'ui Some(callback::), data, ) + }; + + // it should always end with this \0. + if self.buf.ends_with('\0') { + self.buf.pop(); } + + o } } @@ -857,17 +881,24 @@ extern "C" fn callback( } InputTextFlags::CALLBACK_RESIZE => { unsafe { - let requested_size = (*data).BufSize as usize; - let buffer = &mut callback_data.user_data.container; - todo!() - // 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; - // } + let requested_size = (*data).BufTextLen as usize; + + // just confirm that we ARE working with our string. + debug_assert_eq!( + callback_data.user_data.container.as_ptr() as *const _, + (*data).Buf + ); + + if requested_size > callback_data.user_data.container.capacity() { + // reserve more data... + callback_data + .user_data + .container + .reserve(requested_size - callback_data.user_data.container.capacity()); + + (*data).Buf = callback_data.user_data.container.as_mut_ptr() as *mut _; + (*data).BufDirty = true; + } } } InputTextFlags::CALLBACK_CHAR_FILTER => { diff --git a/imgui/src/lib.rs b/imgui/src/lib.rs index 581f701..9ed9e24 100644 --- a/imgui/src/lib.rs +++ b/imgui/src/lib.rs @@ -438,18 +438,27 @@ impl<'ui> Ui<'ui> { // Widgets: ListBox impl<'ui> Ui<'ui> { #[doc(alias = "ListBox")] - pub fn list_box<'p, StringType: AsRef + ?Sized>( + pub fn list_box<'p, StringType: AsRef + ?Sized>( &self, - label: &'p ImStr, + label: impl AsRef, current_item: &mut i32, items: &'p [&'p StringType], height_in_items: i32, ) -> bool { - let items_inner: Vec<*const c_char> = - items.iter().map(|item| item.as_ref().as_ptr()).collect(); + let (label_ptr, items_inner) = unsafe { + let handle = &mut *self.buffer.get(); + + handle.refresh_buffer(); + let label_ptr = handle.push(label); + + let items_inner: Vec<_> = items.iter().map(|&v| handle.push(v)).collect(); + + (label_ptr, items_inner) + }; + unsafe { sys::igListBoxStr_arr( - label.as_ptr(), + label_ptr, current_item, items_inner.as_ptr() as *mut *const c_char, items_inner.len() as i32, @@ -457,11 +466,50 @@ impl<'ui> Ui<'ui> { ) } } + + // written out for the future times... + // #[doc(alias = "ListBox")] + // pub fn list_box_const<'p, StringType: AsRef + ?Sized, const N: usize>( + // &self, + // label: impl AsRef, + // current_item: &mut i32, + // items: [&'p StringType; N], + // height_in_items: i32, + // ) -> bool { + // let (label_ptr, items_inner) = unsafe { + // let handle = &mut *self.buffer.get(); + + // handle.refresh_buffer(); + // let label_ptr = handle.push(label); + + // let mut items_inner: [*const i8; N] = [std::ptr::null(); N]; + + // for (i, item) in items.iter().enumerate() { + // items_inner[i] = handle.push(item); + // } + + // (label_ptr, items_inner) + // }; + + // unsafe { + // sys::igListBoxStr_arr( + // label_ptr, + // current_item, + // items_inner.as_ptr() as *mut *const c_char, + // items_inner.len() as i32, + // height_in_items, + // ) + // } + // } } impl<'ui> Ui<'ui> { #[doc(alias = "PlotLines")] - pub fn plot_lines<'p>(&self, label: &'p ImStr, values: &'p [f32]) -> PlotLines<'ui, 'p> { + pub fn plot_lines<'p, Label: AsRef>( + &'ui self, + label: Label, + values: &'p [f32], + ) -> PlotLines<'ui, 'p, Label> { PlotLines::new(self, label, values) } } diff --git a/imgui/src/plotlines.rs b/imgui/src/plotlines.rs index 9f7f4c1..2390b3e 100644 --- a/imgui/src/plotlines.rs +++ b/imgui/src/plotlines.rs @@ -1,23 +1,22 @@ -use std::marker::PhantomData; use std::os::raw::c_float; -use std::{f32, mem, ptr}; +use std::{f32, mem}; -use super::{ImStr, Ui}; +use super::Ui; #[must_use] -pub struct PlotLines<'ui, 'p> { - label: &'p ImStr, +pub struct PlotLines<'ui, 'p, Label, Overlay = &'static str> { + label: Label, values: &'p [f32], values_offset: usize, - overlay_text: Option<&'p ImStr>, + overlay_text: Option, scale_min: f32, scale_max: f32, graph_size: [f32; 2], - _phantom: PhantomData<&'ui Ui<'ui>>, + ui: &'ui Ui<'ui>, } -impl<'ui, 'p> PlotLines<'ui, 'p> { - pub const fn new(_: &Ui<'ui>, label: &'p ImStr, values: &'p [f32]) -> Self { +impl<'ui, 'p, Label: AsRef> PlotLines<'ui, 'p, Label> { + pub fn new(ui: &'ui Ui<'ui>, label: Label, values: &'p [f32]) -> Self { PlotLines { label, values, @@ -26,48 +25,58 @@ impl<'ui, 'p> PlotLines<'ui, 'p> { scale_min: f32::MAX, scale_max: f32::MAX, graph_size: [0.0, 0.0], - _phantom: PhantomData, + ui, } } +} - #[inline] - pub const fn values_offset(mut self, values_offset: usize) -> Self { +impl<'ui, 'p, Label: AsRef, Overlay: AsRef> PlotLines<'ui, 'p, Label, Overlay> { + pub fn values_offset(mut self, values_offset: usize) -> Self { self.values_offset = values_offset; self } - #[inline] - pub const fn overlay_text(mut self, overlay_text: &'p ImStr) -> Self { - self.overlay_text = Some(overlay_text); - self + pub fn overlay_text>( + self, + overlay_text: Overlay2, + ) -> PlotLines<'ui, 'p, Label, Overlay2> { + PlotLines { + label: self.label, + values: self.values, + values_offset: self.values_offset, + overlay_text: Some(overlay_text), + scale_min: self.scale_min, + scale_max: self.scale_max, + graph_size: self.graph_size, + ui: self.ui, + } } - #[inline] - pub const fn scale_min(mut self, scale_min: f32) -> Self { + pub fn scale_min(mut self, scale_min: f32) -> Self { self.scale_min = scale_min; self } - #[inline] - pub const fn scale_max(mut self, scale_max: f32) -> Self { + pub fn scale_max(mut self, scale_max: f32) -> Self { self.scale_max = scale_max; self } - #[inline] - pub const fn graph_size(mut self, graph_size: [f32; 2]) -> Self { + pub fn graph_size(mut self, graph_size: [f32; 2]) -> Self { self.graph_size = graph_size; self } pub fn build(self) { unsafe { + let (label, overlay) = self.ui.scratch_txt_with_opt(self.label, self.overlay_text); + sys::igPlotLinesFloatPtr( - self.label.as_ptr(), + label, self.values.as_ptr() as *const c_float, self.values.len() as i32, self.values_offset as i32, - self.overlay_text.map(|x| x.as_ptr()).unwrap_or(ptr::null()), + overlay, self.scale_min, self.scale_max, self.graph_size.into(), diff --git a/imgui/src/popups.rs b/imgui/src/popups.rs index ca30b10..dc6f9aa 100644 --- a/imgui/src/popups.rs +++ b/imgui/src/popups.rs @@ -2,16 +2,7 @@ use std::ptr; use crate::sys; use crate::window::WindowFlags; -use crate::{ImStr, Ui}; - -create_token!( - /// Tracks a popup token that can be ended with `end` or by dropping. - pub struct PopupToken<'ui>; - - /// Drops the popup token manually. You can also just allow this token - /// to drop on its own. - drop { sys::igEndPopup() } -); +use crate::Ui; /// Create a modal pop-up. /// @@ -31,14 +22,14 @@ create_token!( /// }; /// ``` #[must_use] -pub struct PopupModal<'p> { - label: &'p ImStr, +pub struct PopupModal<'p, Label> { + label: Label, opened: Option<&'p mut bool>, flags: WindowFlags, } -impl<'p> PopupModal<'p> { - pub fn new(label: &'p ImStr) -> Self { +impl<'p, Label: AsRef> PopupModal<'p, Label> { + pub fn new(label: Label) -> Self { PopupModal { label, opened: None, @@ -140,7 +131,7 @@ impl<'p> PopupModal<'p> { pub fn begin_popup<'ui>(self, ui: &Ui<'ui>) -> Option> { let render = unsafe { sys::igBeginPopupModal( - self.label.as_ptr(), + ui.scratch_txt(self.label), self.opened .map(|x| x as *mut bool) .unwrap_or(ptr::null_mut()), @@ -165,8 +156,8 @@ impl<'ui> Ui<'ui> { /// can also force close a popup when a user clicks outside a popup. If you do not want users to be /// able to close a popup without selected an option, use [`PopupModal`]. #[doc(alias = "OpenPopup")] - pub fn open_popup(&self, str_id: &ImStr) { - unsafe { sys::igOpenPopup(str_id.as_ptr(), 0) }; + pub fn open_popup(&self, str_id: impl AsRef) { + unsafe { sys::igOpenPopup(self.scratch_txt(str_id), 0) }; } /// Construct a popup that can have any kind of content. @@ -174,9 +165,10 @@ impl<'ui> Ui<'ui> { /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* /// when you want to actual create the popup. #[doc(alias = "BeginPopup")] - pub fn begin_popup(&self, str_id: &ImStr) -> Option> { - let render = - unsafe { sys::igBeginPopup(str_id.as_ptr(), WindowFlags::empty().bits() as i32) }; + pub fn begin_popup(&self, str_id: impl AsRef) -> Option> { + let render = unsafe { + sys::igBeginPopup(self.scratch_txt(str_id), WindowFlags::empty().bits() as i32) + }; if render { Some(PopupToken::new(self)) @@ -190,21 +182,17 @@ impl<'ui> Ui<'ui> { /// This should be called *per frame*, whereas [`open_popup`](Self::open_popup) should be called *once* /// when you want to actual create the popup. #[doc(alias = "BeginPopup")] - pub fn popup(&self, str_id: &ImStr, f: F) + pub fn popup(&self, str_id: impl AsRef, f: F) where F: FnOnce(), { - let render = - unsafe { sys::igBeginPopup(str_id.as_ptr(), WindowFlags::empty().bits() as i32) }; - if render { + if let Some(_t) = self.begin_popup(str_id) { f(); - unsafe { sys::igEndPopup() }; } } /// Creates a PopupModal directly. - #[deprecated = "Please use PopupModal to create a modal popup."] - pub fn popup_modal<'p>(&self, str_id: &'p ImStr) -> PopupModal<'p> { + pub fn popup_modal<'p, Label: AsRef>(&self, str_id: Label) -> PopupModal<'p, Label> { PopupModal::new(str_id) } @@ -215,3 +203,12 @@ impl<'ui> Ui<'ui> { unsafe { sys::igCloseCurrentPopup() }; } } + +create_token!( + /// Tracks a popup token that can be ended with `end` or by dropping. + pub struct PopupToken<'ui>; + + /// Drops the popup token manually. You can also just allow this token + /// to drop on its own. + drop { sys::igEndPopup() } +); diff --git a/imgui/src/string.rs b/imgui/src/string.rs index a345ce7..98e3297 100644 --- a/imgui/src/string.rs +++ b/imgui/src/string.rs @@ -74,6 +74,7 @@ impl UiBuffer { } #[macro_export] +#[deprecated = "all functions take AsRef now -- use inline strings or `format` instead"] macro_rules! im_str { ($e:literal $(,)?) => {{ const __INPUT: &str = concat!($e, "\0"); @@ -345,7 +346,6 @@ impl fmt::Write for ImString { /// A UTF-8 encoded, implicitly nul-terminated string slice. #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] #[repr(transparent)] -#[deprecated] pub struct ImStr([u8]); impl<'a> Default for &'a ImStr {