Skip to main content

dear_imgui_rs/widget/
input.rs

1//! Text and scalar inputs
2//!
3//! Single-line and multi-line text inputs backed by `String` or `ImString`
4//! (zero-copy), plus number input helpers. Builders provide flags and
5//! callback hooks for validation and behavior tweaks.
6//!
7//! Quick examples:
8//! ```no_run
9//! # use dear_imgui_rs::*;
10//! # let mut ctx = Context::create();
11//! # let ui = ctx.frame();
12//! // Text (String)
13//! let mut s = String::from("hello");
14//! ui.input_text("Name", &mut s).build();
15//!
16//! // Text (ImString, zero-copy)
17//! let mut im = ImString::with_capacity(64);
18//! ui.input_text_imstr("ImStr", &mut im).build();
19//!
20//! // Numbers
21//! let mut i = 0i32;
22//! let mut f = 1.0f32;
23//! ui.input_int("Count", &mut i);
24//! ui.input_float("Scale", &mut f);
25//! ```
26//!
27#![allow(
28    clippy::cast_possible_truncation,
29    clippy::cast_sign_loss,
30    clippy::as_conversions
31)]
32// NOTE: Keep explicit `as i32`/`as u32` casts when bridging bindgen-generated flags into the
33// Dear ImGui C ABI. Bindgen may represent the same C enum/typedef with different Rust integer
34// types across platforms/toolchains; our wrappers intentionally pin the expected width/sign at
35// the FFI call sites.
36use crate::InputTextFlags;
37use crate::internal::DataTypeKind;
38use crate::string::ImString;
39use crate::sys;
40use crate::ui::Ui;
41use std::borrow::Cow;
42use std::ffi::{c_int, c_void};
43use std::marker::PhantomData;
44use std::ptr;
45
46mod callbacks;
47mod numeric;
48
49pub use callbacks::*;
50pub use numeric::*;
51
52pub(super) fn zero_string_spare_capacity(buf: &mut String) {
53    let len = buf.len();
54    let cap = buf.capacity();
55    if cap > len {
56        unsafe {
57            let dst = buf.as_mut_ptr().add(len);
58            ptr::write_bytes(dst, 0, cap - len);
59        }
60    }
61}
62
63pub(super) fn zero_string_new_capacity(buf: &mut String, old_cap: usize) {
64    let new_cap = buf.capacity();
65    if new_cap > old_cap {
66        unsafe {
67            let dst = buf.as_mut_ptr().add(old_cap);
68            ptr::write_bytes(dst, 0, new_cap - old_cap);
69        }
70    }
71}
72
73/// # Input Widgets
74impl Ui {
75    /// Creates a single-line text input widget builder.
76    ///
77    /// # Examples
78    ///
79    /// ```no_run
80    /// # use dear_imgui_rs::*;
81    /// # let mut ctx = Context::create();
82    /// # let ui = ctx.frame();
83    /// let mut text = String::new();
84    /// if ui.input_text("Label", &mut text).build() {
85    ///     println!("Text changed: {}", text);
86    /// }
87    /// ```
88    #[doc(alias = "InputText", alias = "InputTextWithHint")]
89    pub fn input_text<'ui, 'p>(
90        &'ui self,
91        label: impl Into<Cow<'ui, str>>,
92        buf: &'p mut String,
93    ) -> InputText<'ui, 'p> {
94        InputText::new(self, label, buf)
95    }
96
97    /// Creates a single-line text input backed by ImString (zero-copy)
98    pub fn input_text_imstr<'ui, 'p>(
99        &'ui self,
100        label: impl Into<Cow<'ui, str>>,
101        buf: &'p mut ImString,
102    ) -> InputTextImStr<'ui, 'p> {
103        InputTextImStr::new(self, label, buf)
104    }
105
106    /// Creates a multi-line text input widget builder.
107    ///
108    /// # Examples
109    ///
110    /// ```no_run
111    /// # use dear_imgui_rs::*;
112    /// # let mut ctx = Context::create();
113    /// # let ui = ctx.frame();
114    /// let mut text = String::new();
115    /// if ui.input_text_multiline("Label", &mut text, [200.0, 100.0]).build() {
116    ///     println!("Text changed: {}", text);
117    /// }
118    /// ```
119    #[doc(alias = "InputTextMultiline")]
120    pub fn input_text_multiline<'ui, 'p>(
121        &'ui self,
122        label: impl Into<Cow<'ui, str>>,
123        buf: &'p mut String,
124        size: impl Into<[f32; 2]>,
125    ) -> InputTextMultiline<'ui, 'p> {
126        InputTextMultiline::new(self, label, buf, size)
127    }
128
129    /// Creates a multi-line text input backed by ImString (zero-copy)
130    pub fn input_text_multiline_imstr<'ui, 'p>(
131        &'ui self,
132        label: impl Into<Cow<'ui, str>>,
133        buf: &'p mut ImString,
134        size: impl Into<[f32; 2]>,
135    ) -> InputTextMultilineImStr<'ui, 'p> {
136        InputTextMultilineImStr::new(self, label, buf, size)
137    }
138
139    /// Creates an integer input widget.
140    ///
141    /// Returns true if the value was edited.
142    #[doc(alias = "InputInt")]
143    pub fn input_int(&self, label: impl AsRef<str>, value: &mut i32) -> bool {
144        self.input_int_config(label.as_ref()).build(value)
145    }
146
147    /// Creates a float input widget.
148    ///
149    /// Returns true if the value was edited.
150    #[doc(alias = "InputFloat")]
151    pub fn input_float(&self, label: impl AsRef<str>, value: &mut f32) -> bool {
152        self.input_float_config(label.as_ref()).build(value)
153    }
154
155    /// Creates a double input widget.
156    ///
157    /// Returns true if the value was edited.
158    #[doc(alias = "InputDouble")]
159    pub fn input_double(&self, label: impl AsRef<str>, value: &mut f64) -> bool {
160        self.input_double_config(label.as_ref()).build(value)
161    }
162
163    /// Creates an integer input builder
164    pub fn input_int_config<'ui>(&'ui self, label: impl Into<Cow<'ui, str>>) -> InputInt<'ui> {
165        InputInt::new(self, label)
166    }
167
168    /// Creates a float input builder
169    pub fn input_float_config<'ui>(&'ui self, label: impl Into<Cow<'ui, str>>) -> InputFloat<'ui> {
170        InputFloat::new(self, label)
171    }
172
173    /// Creates a double input builder
174    pub fn input_double_config<'ui>(
175        &'ui self,
176        label: impl Into<Cow<'ui, str>>,
177    ) -> InputDouble<'ui> {
178        InputDouble::new(self, label)
179    }
180
181    /// Shows an input field for a scalar value. This is not limited to `f32` and `i32` and can be used with
182    /// any primitive scalar type e.g. `u8` and `f64`.
183    #[doc(alias = "InputScalar")]
184    pub fn input_scalar<'p, L, T>(&self, label: L, value: &'p mut T) -> InputScalar<'_, 'p, T, L>
185    where
186        L: AsRef<str>,
187        T: DataTypeKind,
188    {
189        InputScalar::new(self, label, value)
190    }
191
192    /// Shows a horizontal array of scalar value input fields. See [`input_scalar`].
193    ///
194    /// [`input_scalar`]: Self::input_scalar
195    #[doc(alias = "InputScalarN")]
196    pub fn input_scalar_n<'p, L, T>(
197        &self,
198        label: L,
199        values: &'p mut [T],
200    ) -> InputScalarN<'_, 'p, T, L>
201    where
202        L: AsRef<str>,
203        T: DataTypeKind,
204    {
205        InputScalarN::new(self, label, values)
206    }
207
208    /// Widget to edit two floats
209    #[doc(alias = "InputFloat2")]
210    pub fn input_float2<'p, L>(&self, label: L, value: &'p mut [f32; 2]) -> InputFloat2<'_, 'p, L>
211    where
212        L: AsRef<str>,
213    {
214        InputFloat2::new(self, label, value)
215    }
216
217    /// Widget to edit three floats
218    #[doc(alias = "InputFloat3")]
219    pub fn input_float3<'p, L>(&self, label: L, value: &'p mut [f32; 3]) -> InputFloat3<'_, 'p, L>
220    where
221        L: AsRef<str>,
222    {
223        InputFloat3::new(self, label, value)
224    }
225
226    /// Widget to edit four floats
227    #[doc(alias = "InputFloat4")]
228    pub fn input_float4<'p, L>(&self, label: L, value: &'p mut [f32; 4]) -> InputFloat4<'_, 'p, L>
229    where
230        L: AsRef<str>,
231    {
232        InputFloat4::new(self, label, value)
233    }
234
235    /// Widget to edit two integers
236    #[doc(alias = "InputInt2")]
237    pub fn input_int2<'p, L>(&self, label: L, value: &'p mut [i32; 2]) -> InputInt2<'_, 'p, L>
238    where
239        L: AsRef<str>,
240    {
241        InputInt2::new(self, label, value)
242    }
243
244    /// Widget to edit three integers
245    #[doc(alias = "InputInt3")]
246    pub fn input_int3<'p, L>(&self, label: L, value: &'p mut [i32; 3]) -> InputInt3<'_, 'p, L>
247    where
248        L: AsRef<str>,
249    {
250        InputInt3::new(self, label, value)
251    }
252
253    /// Widget to edit four integers
254    #[doc(alias = "InputInt4")]
255    pub fn input_int4<'p, L>(&self, label: L, value: &'p mut [i32; 4]) -> InputInt4<'_, 'p, L>
256    where
257        L: AsRef<str>,
258    {
259        InputInt4::new(self, label, value)
260    }
261}
262
263/// Builder for a text input widget
264#[must_use]
265pub struct InputText<'ui, 'p, L = Cow<'ui, str>, H = Cow<'ui, str>, T = PassthroughCallback> {
266    ui: &'ui Ui,
267    label: L,
268    buf: &'p mut String,
269    flags: InputTextFlags,
270    capacity_hint: Option<usize>,
271    hint: Option<H>,
272    callback_handler: T,
273    _phantom: PhantomData<&'ui ()>,
274}
275
276/// Builder for a text input backed by ImString (zero-copy)
277#[must_use]
278pub struct InputTextImStr<'ui, 'p, L = Cow<'ui, str>, H = Cow<'ui, str>, T = PassthroughCallback> {
279    ui: &'ui Ui,
280    label: L,
281    buf: &'p mut ImString,
282    flags: InputTextFlags,
283    hint: Option<H>,
284    callback_handler: T,
285    _phantom: PhantomData<&'ui ()>,
286}
287
288impl<'ui, 'p> InputTextImStr<'ui, 'p, Cow<'ui, str>, Cow<'ui, str>, PassthroughCallback> {
289    pub fn new(ui: &'ui Ui, label: impl Into<Cow<'ui, str>>, buf: &'p mut ImString) -> Self {
290        Self {
291            ui,
292            label: label.into(),
293            buf,
294            flags: InputTextFlags::empty(),
295            hint: None,
296            callback_handler: PassthroughCallback,
297            _phantom: PhantomData,
298        }
299    }
300}
301
302impl<'ui, 'p, L: AsRef<str>, H: AsRef<str>, T> InputTextImStr<'ui, 'p, L, H, T> {
303    pub fn flags(mut self, flags: InputTextFlags) -> Self {
304        self.flags = flags;
305        self
306    }
307    pub fn hint<H2: AsRef<str>>(self, hint: H2) -> InputTextImStr<'ui, 'p, L, H2, T> {
308        InputTextImStr {
309            ui: self.ui,
310            label: self.label,
311            buf: self.buf,
312            flags: self.flags,
313            hint: Some(hint),
314            callback_handler: self.callback_handler,
315            _phantom: PhantomData,
316        }
317    }
318    pub fn read_only(mut self, ro: bool) -> Self {
319        self.flags.set(InputTextFlags::READ_ONLY, ro);
320        self
321    }
322    pub fn password(mut self, pw: bool) -> Self {
323        self.flags.set(InputTextFlags::PASSWORD, pw);
324        self
325    }
326    pub fn auto_select_all(mut self, v: bool) -> Self {
327        self.flags.set(InputTextFlags::AUTO_SELECT_ALL, v);
328        self
329    }
330    pub fn enter_returns_true(mut self, v: bool) -> Self {
331        self.flags.set(InputTextFlags::ENTER_RETURNS_TRUE, v);
332        self
333    }
334
335    pub fn build(self) -> bool {
336        let (label_ptr, hint_ptr) = self.ui.scratch_txt_with_opt(
337            self.label.as_ref(),
338            self.hint.as_ref().map(|hint| hint.as_ref()),
339        );
340        let buf_size = self.buf.capacity_with_nul().max(1);
341        self.buf.ensure_buf_size(buf_size);
342        let buf_ptr = self.buf.as_mut_ptr();
343        let user_ptr = self.buf as *mut ImString as *mut c_void;
344
345        extern "C" fn resize_cb_imstr(data: *mut sys::ImGuiInputTextCallbackData) -> c_int {
346            if data.is_null() {
347                return 0;
348            }
349            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
350                if ((*data).EventFlag as i32) == (sys::ImGuiInputTextFlags_CallbackResize as i32) {
351                    let user_data = (*data).UserData as *mut ImString;
352                    if user_data.is_null() {
353                        return;
354                    }
355
356                    let im = &mut *user_data;
357                    let requested_i32 = (*data).BufSize;
358                    if requested_i32 < 0 {
359                        return;
360                    }
361                    let requested = requested_i32 as usize;
362                    im.ensure_buf_size(requested);
363                    (*data).Buf = im.as_mut_ptr();
364                    (*data).BufDirty = true;
365                }
366            }));
367            if res.is_err() {
368                eprintln!("dear-imgui-rs: panic in ImString resize callback");
369                std::process::abort();
370            }
371            0
372        }
373
374        let flags = self.flags | InputTextFlags::CALLBACK_RESIZE;
375        let result = unsafe {
376            if hint_ptr.is_null() {
377                sys::igInputText(
378                    label_ptr,
379                    buf_ptr,
380                    buf_size,
381                    flags.raw(),
382                    Some(resize_cb_imstr),
383                    user_ptr,
384                )
385            } else {
386                sys::igInputTextWithHint(
387                    label_ptr,
388                    hint_ptr,
389                    buf_ptr,
390                    buf_size,
391                    flags.raw(),
392                    Some(resize_cb_imstr),
393                    user_ptr,
394                )
395            }
396        };
397        // Ensure ImString logical length reflects actual text (scan to NUL)
398        unsafe { self.buf.refresh_len() };
399        result
400    }
401}
402impl<'ui, 'p> InputText<'ui, 'p, Cow<'ui, str>, Cow<'ui, str>, PassthroughCallback> {
403    /// Creates a new text input builder
404    pub fn new(ui: &'ui Ui, label: impl Into<Cow<'ui, str>>, buf: &'p mut String) -> Self {
405        Self {
406            ui,
407            label: label.into(),
408            buf,
409            flags: InputTextFlags::NONE,
410            capacity_hint: None,
411            hint: None,
412            callback_handler: PassthroughCallback,
413            _phantom: PhantomData,
414        }
415    }
416}
417
418impl<'ui, 'p, L, H, T> InputText<'ui, 'p, L, H, T> {
419    /// Sets the flags for the input
420    pub fn flags(mut self, flags: InputTextFlags) -> Self {
421        self.flags = flags;
422        self
423    }
424
425    /// Hint a minimum buffer capacity to reduce reallocations for large fields
426    pub fn capacity_hint(mut self, cap: usize) -> Self {
427        self.capacity_hint = Some(cap);
428        self
429    }
430
431    /// Sets a hint text
432    pub fn hint(self, hint: impl Into<Cow<'ui, str>>) -> InputText<'ui, 'p, L, Cow<'ui, str>, T> {
433        InputText {
434            ui: self.ui,
435            label: self.label,
436            buf: self.buf,
437            flags: self.flags,
438            capacity_hint: self.capacity_hint,
439            hint: Some(hint.into()),
440            callback_handler: self.callback_handler,
441            _phantom: PhantomData,
442        }
443    }
444
445    /// Sets a callback handler for the input text
446    pub fn callback<T2: InputTextCallbackHandler>(
447        self,
448        callback_handler: T2,
449    ) -> InputText<'ui, 'p, L, H, T2> {
450        InputText {
451            ui: self.ui,
452            label: self.label,
453            buf: self.buf,
454            flags: self.flags,
455            capacity_hint: self.capacity_hint,
456            hint: self.hint,
457            callback_handler,
458            _phantom: PhantomData,
459        }
460    }
461
462    /// Sets callback flags for the input text
463    pub fn callback_flags(mut self, callback_flags: InputTextCallback) -> Self {
464        self.flags |= InputTextFlags::from_bits_truncate(callback_flags.bits() as i32);
465        self
466    }
467
468    /// Makes the input read-only
469    pub fn read_only(mut self, read_only: bool) -> Self {
470        self.flags.set(InputTextFlags::READ_ONLY, read_only);
471        self
472    }
473
474    /// Enables password mode
475    pub fn password(mut self, password: bool) -> Self {
476        self.flags.set(InputTextFlags::PASSWORD, password);
477        self
478    }
479
480    /// Enables auto-select all when first taking focus
481    pub fn auto_select_all(mut self, auto_select: bool) -> Self {
482        self.flags.set(InputTextFlags::AUTO_SELECT_ALL, auto_select);
483        self
484    }
485
486    /// Makes Enter key return true instead of adding new line
487    pub fn enter_returns_true(mut self, enter_returns: bool) -> Self {
488        self.flags
489            .set(InputTextFlags::ENTER_RETURNS_TRUE, enter_returns);
490        self
491    }
492
493    /// Allows only decimal characters (0123456789.+-*/)
494    pub fn chars_decimal(mut self, decimal: bool) -> Self {
495        self.flags.set(InputTextFlags::CHARS_DECIMAL, decimal);
496        self
497    }
498
499    /// Allows only hexadecimal characters (0123456789ABCDEFabcdef)
500    pub fn chars_hexadecimal(mut self, hex: bool) -> Self {
501        self.flags.set(InputTextFlags::CHARS_HEXADECIMAL, hex);
502        self
503    }
504
505    /// Turns a..z into A..Z
506    pub fn chars_uppercase(mut self, uppercase: bool) -> Self {
507        self.flags.set(InputTextFlags::CHARS_UPPERCASE, uppercase);
508        self
509    }
510
511    /// Filters out spaces and tabs
512    pub fn chars_no_blank(mut self, no_blank: bool) -> Self {
513        self.flags.set(InputTextFlags::CHARS_NO_BLANK, no_blank);
514        self
515    }
516}
517
518// Implementation for all InputText types
519impl<'ui, 'p, L, H, T> InputText<'ui, 'p, L, H, T>
520where
521    L: AsRef<str>,
522    H: AsRef<str>,
523    T: InputTextCallbackHandler,
524{
525    /// Builds the text input widget
526    pub fn build(self) -> bool {
527        let (label_ptr, hint_ptr) = self.ui.scratch_txt_with_opt(
528            self.label.as_ref(),
529            self.hint.as_ref().map(|hint| hint.as_ref()),
530        );
531
532        if let Some(extra) = self.capacity_hint {
533            let needed = extra.saturating_sub(self.buf.capacity().saturating_sub(self.buf.len()));
534            if needed > 0 {
535                self.buf.reserve(needed);
536            }
537        }
538
539        // Ensure temporary NUL terminator
540        self.buf.push('\0');
541        // Ensure any uninitialized bytes are set to NUL so trimming does not read UB.
542        zero_string_spare_capacity(self.buf);
543        let capacity = self.buf.capacity();
544        let buf_ptr = self.buf.as_mut_ptr() as *mut std::os::raw::c_char;
545
546        #[repr(C)]
547        struct UserData<T> {
548            container: *mut String,
549            handler: T,
550        }
551
552        extern "C" fn callback_router<T: InputTextCallbackHandler>(
553            data: *mut sys::ImGuiInputTextCallbackData,
554        ) -> c_int {
555            if data.is_null() {
556                return 0;
557            }
558
559            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
560                let user_ptr = unsafe { (*data).UserData as *mut UserData<T> };
561                if user_ptr.is_null() {
562                    return 0;
563                }
564                let user = unsafe { &mut *user_ptr };
565                if user.container.is_null() {
566                    return 0;
567                }
568
569                let event_flag =
570                    unsafe { InputTextFlags::from_bits_truncate((*data).EventFlag as i32) };
571                match event_flag {
572                    InputTextFlags::CALLBACK_RESIZE => unsafe {
573                        let requested_i32 = (*data).BufSize;
574                        if requested_i32 < 0 {
575                            return 0;
576                        }
577                        let requested = requested_i32 as usize;
578                        let s = &mut *user.container;
579                        debug_assert_eq!(s.as_ptr() as *const _, (*data).Buf);
580                        if requested > s.capacity() {
581                            let old_cap = s.capacity();
582                            let additional = requested.saturating_sub(s.len());
583                            s.reserve(additional);
584                            zero_string_new_capacity(s, old_cap);
585                            (*data).Buf = s.as_mut_ptr() as *mut _;
586                            (*data).BufDirty = true;
587                        }
588                        0
589                    },
590                    InputTextFlags::CALLBACK_COMPLETION => {
591                        let info = unsafe { TextCallbackData::new(data) };
592                        user.handler.on_completion(info);
593                        0
594                    }
595                    InputTextFlags::CALLBACK_HISTORY => {
596                        let key = unsafe { (*data).EventKey };
597                        let dir = if key == sys::ImGuiKey_UpArrow {
598                            HistoryDirection::Up
599                        } else {
600                            HistoryDirection::Down
601                        };
602                        let info = unsafe { TextCallbackData::new(data) };
603                        user.handler.on_history(dir, info);
604                        0
605                    }
606                    InputTextFlags::CALLBACK_ALWAYS => {
607                        let info = unsafe { TextCallbackData::new(data) };
608                        user.handler.on_always(info);
609                        0
610                    }
611                    InputTextFlags::CALLBACK_EDIT => {
612                        let info = unsafe { TextCallbackData::new(data) };
613                        user.handler.on_edit(info);
614                        0
615                    }
616                    InputTextFlags::CALLBACK_CHAR_FILTER => {
617                        let ch = unsafe {
618                            std::char::from_u32((*data).EventChar as u32).unwrap_or('\0')
619                        };
620                        let new_ch = user.handler.char_filter(ch).map(|c| c as u32).unwrap_or(0);
621                        unsafe {
622                            (*data).EventChar =
623                                sys::ImWchar::try_from(new_ch).unwrap_or(0 as sys::ImWchar);
624                        }
625                        0
626                    }
627                    _ => 0,
628                }
629            }));
630
631            match res {
632                Ok(v) => v,
633                Err(_) => {
634                    eprintln!("dear-imgui-rs: panic in InputText callback");
635                    std::process::abort();
636                }
637            }
638        }
639
640        let mut user_data = UserData {
641            container: self.buf as *mut String,
642            handler: self.callback_handler,
643        };
644        let user_ptr = &mut user_data as *mut _ as *mut c_void;
645
646        let flags = self.flags | InputTextFlags::CALLBACK_RESIZE;
647        let result = unsafe {
648            if hint_ptr.is_null() {
649                sys::igInputText(
650                    label_ptr,
651                    buf_ptr,
652                    capacity,
653                    flags.raw(),
654                    Some(callback_router::<T>),
655                    user_ptr,
656                )
657            } else {
658                sys::igInputTextWithHint(
659                    label_ptr,
660                    hint_ptr,
661                    buf_ptr,
662                    capacity,
663                    flags.raw(),
664                    Some(callback_router::<T>),
665                    user_ptr,
666                )
667            }
668        };
669
670        // Trim to first NUL (remove pushed terminator)
671        let cap = unsafe { (&*user_data.container).capacity() };
672        let slice = unsafe { std::slice::from_raw_parts((&*user_data.container).as_ptr(), cap) };
673        if let Some(len) = slice.iter().position(|&b| b == 0) {
674            unsafe { (&mut *user_data.container).as_mut_vec().set_len(len) };
675        }
676        result
677    }
678}
679
680/// Builder for multiline text input widget
681#[derive(Debug)]
682#[must_use]
683pub struct InputTextMultiline<'ui, 'p> {
684    ui: &'ui Ui,
685    label: Cow<'ui, str>,
686    buf: &'p mut String,
687    size: [f32; 2],
688    flags: InputTextFlags,
689    capacity_hint: Option<usize>,
690}
691
692/// Builder for multiline text input backed by ImString (zero-copy)
693#[derive(Debug)]
694#[must_use]
695pub struct InputTextMultilineImStr<'ui, 'p> {
696    ui: &'ui Ui,
697    label: Cow<'ui, str>,
698    buf: &'p mut ImString,
699    size: [f32; 2],
700    flags: InputTextFlags,
701}
702
703impl<'ui, 'p> InputTextMultilineImStr<'ui, 'p> {
704    pub fn new(
705        ui: &'ui Ui,
706        label: impl Into<Cow<'ui, str>>,
707        buf: &'p mut ImString,
708        size: impl Into<[f32; 2]>,
709    ) -> Self {
710        Self {
711            ui,
712            label: label.into(),
713            buf,
714            size: size.into(),
715            flags: InputTextFlags::NONE,
716        }
717    }
718    pub fn flags(mut self, flags: InputTextFlags) -> Self {
719        self.flags = flags;
720        self
721    }
722    pub fn read_only(mut self, v: bool) -> Self {
723        self.flags.set(InputTextFlags::READ_ONLY, v);
724        self
725    }
726    pub fn build(self) -> bool {
727        let label_ptr = self.ui.scratch_txt(self.label.as_ref());
728        let buf_size = self.buf.capacity_with_nul().max(1);
729        self.buf.ensure_buf_size(buf_size);
730        let buf_ptr = self.buf.as_mut_ptr();
731        let user_ptr = self.buf as *mut ImString as *mut c_void;
732        let size_vec: sys::ImVec2 = self.size.into();
733
734        extern "C" fn resize_cb_imstr(data: *mut sys::ImGuiInputTextCallbackData) -> c_int {
735            if data.is_null() {
736                return 0;
737            }
738            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
739                if ((*data).EventFlag as i32) == (sys::ImGuiInputTextFlags_CallbackResize as i32) {
740                    let user_data = (*data).UserData as *mut ImString;
741                    if user_data.is_null() {
742                        return;
743                    }
744
745                    let im = &mut *user_data;
746                    let requested_i32 = (*data).BufSize;
747                    if requested_i32 < 0 {
748                        return;
749                    }
750                    let requested = requested_i32 as usize;
751                    im.ensure_buf_size(requested);
752                    (*data).Buf = im.as_mut_ptr();
753                    (*data).BufDirty = true;
754                }
755            }));
756            if res.is_err() {
757                eprintln!("dear-imgui-rs: panic in ImString multiline resize callback");
758                std::process::abort();
759            }
760            0
761        }
762
763        let flags = self.flags | InputTextFlags::CALLBACK_RESIZE;
764        let result = unsafe {
765            sys::igInputTextMultiline(
766                label_ptr,
767                buf_ptr,
768                buf_size,
769                size_vec,
770                flags.raw(),
771                Some(resize_cb_imstr),
772                user_ptr,
773            )
774        };
775        // Ensure ImString logical length reflects actual text (scan to NUL)
776        unsafe { self.buf.refresh_len() };
777        result
778    }
779}
780impl<'ui, 'p> InputTextMultiline<'ui, 'p> {
781    /// Creates a new multiline text input builder
782    pub fn new(
783        ui: &'ui Ui,
784        label: impl Into<Cow<'ui, str>>,
785        buf: &'p mut String,
786        size: impl Into<[f32; 2]>,
787    ) -> Self {
788        Self {
789            ui,
790            label: label.into(),
791            buf,
792            size: size.into(),
793            flags: InputTextFlags::NONE,
794            capacity_hint: None,
795        }
796    }
797
798    /// Sets the flags for the input
799    pub fn flags(mut self, flags: InputTextFlags) -> Self {
800        self.flags = flags;
801        self
802    }
803
804    /// Hint a minimum buffer capacity to reduce reallocations for large fields
805    pub fn capacity_hint(mut self, cap: usize) -> Self {
806        self.capacity_hint = Some(cap);
807        self
808    }
809
810    /// Makes the input read-only
811    pub fn read_only(mut self, read_only: bool) -> Self {
812        self.flags.set(InputTextFlags::READ_ONLY, read_only);
813        self
814    }
815
816    /// Builds the multiline text input widget
817    pub fn build(self) -> bool {
818        let label_ptr = self.ui.scratch_txt(self.label.as_ref());
819
820        // Optional pre-reserve
821        if let Some(extra) = self.capacity_hint {
822            let needed = extra.saturating_sub(self.buf.capacity().saturating_sub(self.buf.len()));
823            if needed > 0 {
824                self.buf.reserve(needed);
825            }
826        }
827
828        // Ensure a NUL terminator and use String's capacity directly
829        self.buf.push('\0');
830        // Ensure any uninitialized bytes are set to NUL so trimming does not read UB.
831        zero_string_spare_capacity(self.buf);
832        let capacity = self.buf.capacity();
833        let buf_ptr = self.buf.as_mut_ptr() as *mut std::os::raw::c_char;
834
835        #[repr(C)]
836        struct UserData {
837            container: *mut String,
838        }
839
840        extern "C" fn callback_router(data: *mut sys::ImGuiInputTextCallbackData) -> c_int {
841            if data.is_null() {
842                return 0;
843            }
844
845            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
846                let event_flag = InputTextFlags::from_bits_truncate((*data).EventFlag as i32);
847                match event_flag {
848                    InputTextFlags::CALLBACK_RESIZE => {
849                        let user_ptr = (*data).UserData as *mut UserData;
850                        if user_ptr.is_null() {
851                            return 0;
852                        }
853                        let requested_i32 = (*data).BufSize;
854                        if requested_i32 < 0 {
855                            return 0;
856                        }
857                        let requested = requested_i32 as usize;
858
859                        let user = &mut *user_ptr;
860                        if user.container.is_null() {
861                            return 0;
862                        }
863                        let s = &mut *user.container;
864                        debug_assert_eq!(s.as_ptr() as *const _, (*data).Buf);
865                        if requested > s.capacity() {
866                            let old_cap = s.capacity();
867                            let additional = requested.saturating_sub(s.len());
868                            s.reserve(additional);
869                            zero_string_new_capacity(s, old_cap);
870                            (*data).Buf = s.as_mut_ptr() as *mut _;
871                            (*data).BufDirty = true;
872                        }
873                        0
874                    }
875                    _ => 0,
876                }
877            }));
878
879            match res {
880                Ok(v) => v,
881                Err(_) => {
882                    eprintln!("dear-imgui-rs: panic in multiline InputText resize callback");
883                    std::process::abort();
884                }
885            }
886        }
887
888        let mut user_data = UserData {
889            container: self.buf as *mut String,
890        };
891        let user_ptr = &mut user_data as *mut _ as *mut c_void;
892
893        let size_vec: sys::ImVec2 = self.size.into();
894        let flags = self.flags | InputTextFlags::CALLBACK_RESIZE;
895        let result = unsafe {
896            sys::igInputTextMultiline(
897                label_ptr,
898                buf_ptr,
899                capacity,
900                size_vec,
901                flags.raw(),
902                Some(callback_router),
903                user_ptr,
904            )
905        };
906
907        // Trim at NUL to restore real length
908        let cap = unsafe { (&*user_data.container).capacity() };
909        let slice = unsafe { std::slice::from_raw_parts((&*user_data.container).as_ptr(), cap) };
910        if let Some(len) = slice.iter().position(|&b| b == 0) {
911            unsafe { (&mut *user_data.container).as_mut_vec().set_len(len) };
912        }
913        result
914    }
915
916    /// Enable ImGui callbacks for this multiline input and attach a handler.
917    pub fn callback<T2: InputTextCallbackHandler>(
918        mut self,
919        callbacks: InputTextCallback,
920        handler: T2,
921    ) -> InputTextMultilineWithCb<'ui, 'p, T2> {
922        // Note: ImGui forbids CallbackHistory/Completion with Multiline.
923        // We intentionally do NOT enable them here to avoid assertions.
924        if callbacks.contains(InputTextCallback::ALWAYS) {
925            self.flags.insert(InputTextFlags::CALLBACK_ALWAYS);
926        }
927        if callbacks.contains(InputTextCallback::CHAR_FILTER) {
928            self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER);
929        }
930        if callbacks.contains(InputTextCallback::EDIT) {
931            self.flags.insert(InputTextFlags::CALLBACK_EDIT);
932        }
933
934        InputTextMultilineWithCb {
935            ui: self.ui,
936            label: self.label,
937            buf: self.buf,
938            size: self.size,
939            flags: self.flags,
940            capacity_hint: self.capacity_hint,
941            handler,
942        }
943    }
944}
945
946/// Multiline InputText with attached callback handler
947pub struct InputTextMultilineWithCb<'ui, 'p, T> {
948    ui: &'ui Ui,
949    label: Cow<'ui, str>,
950    buf: &'p mut String,
951    size: [f32; 2],
952    flags: InputTextFlags,
953    capacity_hint: Option<usize>,
954    handler: T,
955}
956
957impl<'ui, 'p, T: InputTextCallbackHandler> InputTextMultilineWithCb<'ui, 'p, T> {
958    pub fn build(self) -> bool {
959        let label_ptr = self.ui.scratch_txt(self.label.as_ref());
960
961        if let Some(extra) = self.capacity_hint {
962            let needed = extra.saturating_sub(self.buf.capacity().saturating_sub(self.buf.len()));
963            if needed > 0 {
964                self.buf.reserve(needed);
965            }
966        }
967
968        // Ensure NUL terminator
969        self.buf.push('\0');
970        // Ensure any uninitialized bytes are set to NUL so trimming does not read UB.
971        zero_string_spare_capacity(self.buf);
972        let capacity = self.buf.capacity();
973        let buf_ptr = self.buf.as_mut_ptr() as *mut std::os::raw::c_char;
974
975        #[repr(C)]
976        struct UserData<T> {
977            container: *mut String,
978            handler: T,
979        }
980
981        extern "C" fn callback_router<T: InputTextCallbackHandler>(
982            data: *mut sys::ImGuiInputTextCallbackData,
983        ) -> c_int {
984            if data.is_null() {
985                return 0;
986            }
987
988            let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
989                let user_ptr = unsafe { (*data).UserData as *mut UserData<T> };
990                if user_ptr.is_null() {
991                    return 0;
992                }
993                let user = unsafe { &mut *user_ptr };
994                if user.container.is_null() {
995                    return 0;
996                }
997
998                let event_flag =
999                    unsafe { InputTextFlags::from_bits_truncate((*data).EventFlag as i32) };
1000                match event_flag {
1001                    InputTextFlags::CALLBACK_RESIZE => unsafe {
1002                        let requested_i32 = (*data).BufSize;
1003                        if requested_i32 < 0 {
1004                            return 0;
1005                        }
1006                        let requested = requested_i32 as usize;
1007                        let s = &mut *user.container;
1008                        debug_assert_eq!(s.as_ptr() as *const _, (*data).Buf);
1009                        if requested > s.capacity() {
1010                            let old_cap = s.capacity();
1011                            let additional = requested.saturating_sub(s.len());
1012                            s.reserve(additional);
1013                            zero_string_new_capacity(s, old_cap);
1014                            (*data).Buf = s.as_mut_ptr() as *mut _;
1015                            (*data).BufDirty = true;
1016                        }
1017                        0
1018                    },
1019                    InputTextFlags::CALLBACK_COMPLETION => {
1020                        let info = unsafe { TextCallbackData::new(data) };
1021                        user.handler.on_completion(info);
1022                        0
1023                    }
1024                    InputTextFlags::CALLBACK_HISTORY => {
1025                        let key = unsafe { (*data).EventKey };
1026                        let dir = if key == sys::ImGuiKey_UpArrow {
1027                            HistoryDirection::Up
1028                        } else {
1029                            HistoryDirection::Down
1030                        };
1031                        let info = unsafe { TextCallbackData::new(data) };
1032                        user.handler.on_history(dir, info);
1033                        0
1034                    }
1035                    InputTextFlags::CALLBACK_ALWAYS => {
1036                        let info = unsafe { TextCallbackData::new(data) };
1037                        user.handler.on_always(info);
1038                        0
1039                    }
1040                    InputTextFlags::CALLBACK_EDIT => {
1041                        let info = unsafe { TextCallbackData::new(data) };
1042                        user.handler.on_edit(info);
1043                        0
1044                    }
1045                    InputTextFlags::CALLBACK_CHAR_FILTER => {
1046                        let ch = unsafe {
1047                            std::char::from_u32((*data).EventChar as u32).unwrap_or('\0')
1048                        };
1049                        let new_ch = user.handler.char_filter(ch).map(|c| c as u32).unwrap_or(0);
1050                        unsafe {
1051                            (*data).EventChar =
1052                                sys::ImWchar::try_from(new_ch).unwrap_or(0 as sys::ImWchar);
1053                        }
1054                        0
1055                    }
1056                    _ => 0,
1057                }
1058            }));
1059
1060            match res {
1061                Ok(v) => v,
1062                Err(_) => {
1063                    eprintln!("dear-imgui-rs: panic in InputText multiline callback");
1064                    std::process::abort();
1065                }
1066            }
1067        }
1068
1069        let mut user_data = UserData {
1070            container: self.buf as *mut String,
1071            handler: self.handler,
1072        };
1073        let user_ptr = &mut user_data as *mut _ as *mut c_void;
1074
1075        let size_vec: sys::ImVec2 = self.size.into();
1076        let flags = self.flags | InputTextFlags::CALLBACK_RESIZE;
1077        let result = unsafe {
1078            sys::igInputTextMultiline(
1079                label_ptr,
1080                buf_ptr,
1081                capacity,
1082                size_vec,
1083                flags.raw(),
1084                Some(callback_router::<T>),
1085                user_ptr,
1086            )
1087        };
1088
1089        // Trim at NUL
1090        let cap = unsafe { (&*user_data.container).capacity() };
1091        let slice = unsafe { std::slice::from_raw_parts((&*user_data.container).as_ptr(), cap) };
1092        if let Some(len) = slice.iter().position(|&b| b == 0) {
1093            unsafe { (&mut *user_data.container).as_mut_vec().set_len(len) };
1094        }
1095        result
1096    }
1097}