diff --git a/imgui-glium-examples/examples/test_window_impl.rs b/imgui-glium-examples/examples/test_window_impl.rs index 244310e..ed07b3a 100644 --- a/imgui-glium-examples/examples/test_window_impl.rs +++ b/imgui-glium-examples/examples/test_window_impl.rs @@ -371,7 +371,7 @@ fn show_test_window(ui: &Ui, state: &mut State, opened: &mut bool) { if ui.collapsing_header(im_str!("Widgets")).build() { ui.tree_node(im_str!("Tree")).build(|| { for i in 0..5 { - ui.tree_node(im_str!("Child {}", i)).build(|| { + ui.tree_node(&im_str!("Child {}", i)).build(|| { ui.text(im_str!("blah blah")); ui.same_line(0.0); if ui.small_button(im_str!("print")) { @@ -774,7 +774,7 @@ fn show_example_menu_file<'a>(ui: &Ui<'a>, state: &mut FileMenuState) { }); ui.menu(im_str!("Colors")).build(|| { for &col in StyleColor::VARIANTS.iter() { - ui.menu_item(im_str!("{:?}", col)).build(); + ui.menu_item(&im_str!("{:?}", col)).build(); } }); ui.menu(im_str!("Disabled")).enabled(false).build(|| { @@ -849,7 +849,7 @@ My title is the same as window 1, but my identifier is unique.", let ch_idx = (ui.imgui().get_time() / 0.25) as usize & 3; let num = ui.imgui().get_frame_count(); // The C++ version uses rand() here let title = im_str!("Animated title {} {}###AnimatedTitle", chars[ch_idx], num); - ui.window(title) + ui.window(&title) .position((100.0, 300.0), Condition::FirstUseEver) .build(|| ui.text("This window has a changing title")); } diff --git a/src/lib.rs b/src/lib.rs index 064a1af..a369ec3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,21 +70,6 @@ pub struct ImGui { log_filename: Option, } -#[macro_export] -macro_rules! im_str { - ($e:tt) => ({ - unsafe { - $crate::ImStr::from_utf8_with_nul_unchecked(concat!($e, "\0").as_bytes()) - } - }); - ($e:tt, $($arg:tt)*) => ({ - unsafe { - &$crate::ImString::from_utf8_with_nul_unchecked( - format!(concat!($e, "\0"), $($arg)*).into_bytes()) - } - }) -} - pub struct TextureHandle<'a> { pub width: u32, pub height: u32, diff --git a/src/string.rs b/src/string.rs index ecf77e8..d808f92 100644 --- a/src/string.rs +++ b/src/string.rs @@ -1,51 +1,83 @@ -use std::borrow::Borrow; +use std::borrow::{Borrow, Cow}; use std::ffi::CStr; use std::fmt; use std::ops::{Deref, Index, RangeFull}; use std::os::raw::c_char; use std::str; +#[macro_export] +macro_rules! im_str { + ($e:tt) => ({ + unsafe { + $crate::ImStr::from_utf8_with_nul_unchecked(concat!($e, "\0").as_bytes()) + } + }); + ($e:tt, $($arg:tt)*) => ({ + unsafe { + $crate::ImString::from_utf8_with_nul_unchecked(format!(concat!($e, "\0"), $($arg)*).into_bytes()) + } + }) +} + +/// A UTF-8 encoded, growable, implicitly null-terminated string. #[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)] pub struct ImString(pub(crate) Vec); impl ImString { + /// Creates a new `ImString` from an existing string. pub fn new>(value: T) -> ImString { unsafe { ImString::from_utf8_unchecked(value.into().into_bytes()) } } + /// Creates a new empty `ImString` with a particular capacity pub fn with_capacity(capacity: usize) -> ImString { let mut v = Vec::with_capacity(capacity + 1); v.push(b'\0'); ImString(v) } + /// Converts a vector of bytes to a `ImString` without checking that the string contains valid + /// UTF-8 pub unsafe fn from_utf8_unchecked(mut v: Vec) -> ImString { v.push(b'\0'); ImString(v) } + /// Converts a vector of bytes to a `ImString` without checking that the string contains valid + /// UTF-8 pub unsafe fn from_utf8_with_nul_unchecked(v: Vec) -> ImString { ImString(v) } + /// Truncates this `ImString`, removing all contents pub fn clear(&mut self) { self.0.clear(); self.0.push(b'\0'); } + /// Appends the given character to the end of this `ImString` pub fn push(&mut self, ch: char) { let mut buf = [0; 4]; self.push_str(ch.encode_utf8(&mut buf)); } + /// Appends a given string slice to the end of this `ImString` pub fn push_str(&mut self, string: &str) { self.refresh_len(); self.0.extend_from_slice(string.as_bytes()); self.0.push(b'\0'); } + /// Returns the capacity of this `ImString` in bytes pub fn capacity(&self) -> usize { self.0.capacity() - 1 } + /// Returns the capacity of this `ImString` in bytes, including the implicit null byte pub fn capacity_with_nul(&self) -> usize { self.0.capacity() } + /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the + /// current length. + /// + /// The capacity may be increased by more than `additional` bytes. pub fn reserve(&mut self, additional: usize) { self.0.reserve(additional); } + /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the + /// current length pub fn reserve_exact(&mut self, additional: usize) { self.0.reserve_exact(additional); } @@ -55,7 +87,6 @@ impl ImString { pub fn as_mut_ptr(&mut self) -> *mut c_char { self.0.as_mut_ptr() as *mut _ } - /// Updates the buffer length based on the current contents. /// /// Dear imgui accesses pointers directly, so the length doesn't get updated when the contents @@ -71,13 +102,25 @@ impl ImString { impl<'a> Default for ImString { fn default() -> ImString { - unsafe { ImString::from_utf8_with_nul_unchecked(vec![0]) } + ImString(vec![b'\0']) } } impl From for ImString { fn from(s: String) -> ImString { - ImString::new(s) + unsafe { ImString::from_utf8_unchecked(s.into_bytes()) } + } +} + +impl<'a> From for Cow<'a, ImStr> { + fn from(s: ImString) -> Cow<'a, ImStr> { + Cow::Owned(s) + } +} + +impl<'a> From<&'a ImString> for Cow<'a, ImStr> { + fn from(s: &'a ImString) -> Cow<'a, ImStr> { + Cow::Borrowed(s) } } @@ -124,6 +167,12 @@ impl fmt::Debug for ImString { } } +impl fmt::Display for ImString { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self.to_str(), f) + } +} + impl Deref for ImString { type Target = ImStr; fn deref(&self) -> &ImStr { @@ -135,6 +184,7 @@ impl Deref for ImString { } } +/// A UTF-8 encoded, implicitly null-terminated string slice. #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ImStr(CStr); @@ -151,19 +201,39 @@ impl fmt::Debug for ImStr { } } -impl ImStr { - pub fn new + ?Sized>(s: &S) -> &ImStr { - s.as_ref() +impl fmt::Display for ImStr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self.to_str(), f) } +} + +impl ImStr { + /// Wraps a raw UTF-8 encoded C string + pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a ImStr { + ImStr::from_cstr_unchecked(CStr::from_ptr(ptr)) + } + /// Converts a slice of bytes to an imgui-rs string slice without checking for valid UTF-8 or + /// null termination. pub unsafe fn from_utf8_with_nul_unchecked(bytes: &[u8]) -> &ImStr { &*(bytes as *const [u8] as *const ImStr) } + /// Converts a CStr reference to an imgui-rs string slice without checking for valid UTF-8. + pub unsafe fn from_cstr_unchecked(value: &CStr) -> &ImStr { + &*(value as *const CStr as *const ImStr) + } + /// Converts an imgui-rs string slice to a raw pointer pub fn as_ptr(&self) -> *const c_char { self.0.as_ptr() } + /// Converts an imgui-rs string slice to a normal string slice pub fn to_str(&self) -> &str { + // CStr::to_bytes does *not* include the null terminator unsafe { str::from_utf8_unchecked(self.0.to_bytes()) } } + /// Returns true if the imgui-rs string slice is empty + pub fn is_empty(&self) -> bool { + self.0.to_bytes().is_empty() + } } impl AsRef for ImStr { @@ -184,6 +254,12 @@ impl AsRef for ImStr { } } +impl<'a> From<&'a ImStr> for Cow<'a, ImStr> { + fn from(s: &'a ImStr) -> Cow<'a, ImStr> { + Cow::Borrowed(s) + } +} + impl ToOwned for ImStr { type Owned = ImString; fn to_owned(&self) -> ImString {