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