dear_imgui/
string.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::ops::{Deref, Index, RangeFull};
4use std::os::raw::c_char;
5use std::str;
6
7/// Internal buffer for UI string operations
8#[derive(Debug)]
9pub struct UiBuffer {
10    pub buffer: Vec<u8>,
11    pub max_len: usize,
12}
13
14impl UiBuffer {
15    /// Creates a new buffer with the specified capacity
16    pub const fn new(max_len: usize) -> Self {
17        Self {
18            buffer: Vec::new(),
19            max_len,
20        }
21    }
22
23    /// Internal method to push a single text to our scratch buffer.
24    pub fn scratch_txt(&mut self, txt: impl AsRef<str>) -> *const std::os::raw::c_char {
25        self.refresh_buffer();
26
27        let start_of_substr = self.push(txt);
28        unsafe { self.offset(start_of_substr) }
29    }
30
31    /// Internal method to push an option text to our scratch buffer.
32    pub fn scratch_txt_opt(&mut self, txt: Option<impl AsRef<str>>) -> *const std::os::raw::c_char {
33        match txt {
34            Some(v) => self.scratch_txt(v),
35            None => std::ptr::null(),
36        }
37    }
38
39    /// Helper method, same as [`Self::scratch_txt`] but for two strings
40    pub fn scratch_txt_two(
41        &mut self,
42        txt_0: impl AsRef<str>,
43        txt_1: impl AsRef<str>,
44    ) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
45        self.refresh_buffer();
46
47        let first_offset = self.push(txt_0);
48        let second_offset = self.push(txt_1);
49
50        unsafe { (self.offset(first_offset), self.offset(second_offset)) }
51    }
52
53    /// Helper method, same as [`Self::scratch_txt`] but with one optional value
54    pub fn scratch_txt_with_opt(
55        &mut self,
56        txt_0: impl AsRef<str>,
57        txt_1: Option<impl AsRef<str>>,
58    ) -> (*const std::os::raw::c_char, *const std::os::raw::c_char) {
59        match txt_1 {
60            Some(value) => self.scratch_txt_two(txt_0, value),
61            None => (self.scratch_txt(txt_0), std::ptr::null()),
62        }
63    }
64
65    /// Attempts to clear the buffer if it's over the maximum length allowed.
66    /// This is to prevent us from making a giant vec over time.
67    pub fn refresh_buffer(&mut self) {
68        if self.buffer.len() > self.max_len {
69            self.buffer.clear();
70        }
71    }
72
73    /// Given a position, gives an offset from the start of the scratch buffer.
74    ///
75    /// # Safety
76    /// This can return a pointer to undefined data if given a `pos >= self.buffer.len()`.
77    /// This is marked as unsafe to reflect that.
78    pub unsafe fn offset(&self, pos: usize) -> *const std::os::raw::c_char {
79        unsafe { self.buffer.as_ptr().add(pos) as *const _ }
80    }
81
82    /// Pushes a new scratch sheet text and return the byte index where the sub-string
83    /// starts.
84    pub fn push(&mut self, txt: impl AsRef<str>) -> usize {
85        let len = self.buffer.len();
86        self.buffer.extend(txt.as_ref().as_bytes());
87        self.buffer.push(b'\0');
88
89        len
90    }
91}
92
93/// A UTF-8 encoded, growable, implicitly nul-terminated string.
94#[derive(Clone, Hash, Ord, Eq, PartialOrd, PartialEq)]
95pub struct ImString(pub(crate) Vec<u8>);
96
97impl ImString {
98    /// Creates a new `ImString` from an existing string.
99    pub fn new<T: Into<String>>(value: T) -> ImString {
100        unsafe {
101            let mut s = ImString::from_utf8_unchecked(value.into().into_bytes());
102            s.refresh_len();
103            s
104        }
105    }
106
107    /// Creates a new empty `ImString` with a particular capacity
108    #[inline]
109    pub fn with_capacity(capacity: usize) -> ImString {
110        let mut v = Vec::with_capacity(capacity + 1);
111        v.push(b'\0');
112        ImString(v)
113    }
114
115    /// Converts a vector of bytes to a `ImString` without checking that the string contains valid
116    /// UTF-8
117    ///
118    /// # Safety
119    ///
120    /// It is up to the caller to guarantee the vector contains valid UTF-8 and no null terminator.
121    #[inline]
122    pub unsafe fn from_utf8_unchecked(mut v: Vec<u8>) -> ImString {
123        v.push(b'\0');
124        ImString(v)
125    }
126
127    /// Converts a vector of bytes to a `ImString` without checking that the string contains valid
128    /// UTF-8
129    ///
130    /// # Safety
131    ///
132    /// It is up to the caller to guarantee the vector contains valid UTF-8 and a null terminator.
133    #[inline]
134    pub unsafe fn from_utf8_with_nul_unchecked(v: Vec<u8>) -> ImString {
135        ImString(v)
136    }
137
138    /// Truncates this `ImString`, removing all contents
139    #[inline]
140    pub fn clear(&mut self) {
141        self.0.clear();
142        self.0.push(b'\0');
143    }
144
145    /// Appends the given character to the end of this `ImString`
146    #[inline]
147    pub fn push(&mut self, ch: char) {
148        let mut buf = [0; 4];
149        self.push_str(ch.encode_utf8(&mut buf));
150    }
151
152    /// Appends a given string slice to the end of this `ImString`
153    #[inline]
154    pub fn push_str(&mut self, string: &str) {
155        self.0.pop();
156        self.0.extend(string.bytes());
157        self.0.push(b'\0');
158        unsafe {
159            self.refresh_len();
160        }
161    }
162
163    /// Returns the capacity of this `ImString` in bytes
164    #[inline]
165    pub fn capacity(&self) -> usize {
166        self.0.capacity() - 1
167    }
168
169    /// Returns the capacity of this `ImString` in bytes, including the implicit null byte
170    #[inline]
171    pub fn capacity_with_nul(&self) -> usize {
172        self.0.capacity()
173    }
174
175    /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the
176    /// current length.
177    ///
178    /// The capacity may be increased by more than `additional` bytes.
179    pub fn reserve(&mut self, additional: usize) {
180        self.0.reserve(additional);
181    }
182
183    /// Ensures that the capacity of this `ImString` is at least `additional` bytes larger than the
184    /// current length
185    pub fn reserve_exact(&mut self, additional: usize) {
186        self.0.reserve_exact(additional);
187    }
188
189    /// Returns a raw pointer to the underlying buffer
190    #[inline]
191    pub fn as_ptr(&self) -> *const c_char {
192        self.0.as_ptr() as *const c_char
193    }
194
195    /// Returns a raw mutable pointer to the underlying buffer.
196    ///
197    /// If the underlying data is modified, `refresh_len` *must* be called afterwards.
198    #[inline]
199    pub fn as_mut_ptr(&mut self) -> *mut c_char {
200        self.0.as_mut_ptr() as *mut c_char
201    }
202
203    /// Refreshes the length of the string by searching for the null terminator
204    ///
205    /// # Safety
206    ///
207    /// This function is unsafe because it assumes the buffer contains valid UTF-8
208    /// and has a null terminator somewhere within the allocated capacity.
209    pub unsafe fn refresh_len(&mut self) {
210        unsafe {
211            // For now, we'll use a simple implementation without libc
212            // In a real implementation, you'd want to use libc::strlen or similar
213            let mut len = 0;
214            let ptr = self.as_ptr() as *const u8;
215            while *ptr.add(len) != 0 {
216                len += 1;
217            }
218            self.0.set_len(len + 1);
219        }
220    }
221
222    /// Returns the length of this `ImString` in bytes, excluding the null terminator
223    pub fn len(&self) -> usize {
224        self.0.len().saturating_sub(1)
225    }
226
227    /// Returns true if this `ImString` is empty
228    pub fn is_empty(&self) -> bool {
229        self.len() == 0
230    }
231
232    /// Converts to a string slice
233    pub fn to_str(&self) -> &str {
234        unsafe { str::from_utf8_unchecked(&self.0[..self.len()]) }
235    }
236}
237
238impl Default for ImString {
239    fn default() -> Self {
240        ImString::with_capacity(0)
241    }
242}
243
244impl fmt::Display for ImString {
245    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246        fmt::Display::fmt(self.to_str(), f)
247    }
248}
249
250impl fmt::Debug for ImString {
251    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252        fmt::Debug::fmt(self.to_str(), f)
253    }
254}
255
256impl Deref for ImString {
257    type Target = str;
258    fn deref(&self) -> &str {
259        self.to_str()
260    }
261}
262
263impl AsRef<str> for ImString {
264    fn as_ref(&self) -> &str {
265        self.to_str()
266    }
267}
268
269impl From<String> for ImString {
270    fn from(s: String) -> ImString {
271        ImString::new(s)
272    }
273}
274
275impl From<&str> for ImString {
276    fn from(s: &str) -> ImString {
277        ImString::new(s)
278    }
279}
280
281impl Index<RangeFull> for ImString {
282    type Output = str;
283    fn index(&self, _index: RangeFull) -> &str {
284        self.to_str()
285    }
286}
287
288/// Represents a borrowed string that can be either a Rust string slice or an ImString
289pub type ImStr<'a> = Cow<'a, str>;
290
291/// Creates an ImString from a string literal at compile time
292#[macro_export]
293macro_rules! im_str {
294    ($e:expr) => {{ $crate::ImString::new($e) }};
295}