1#![allow(
28 clippy::cast_possible_truncation,
29 clippy::cast_sign_loss,
30 clippy::as_conversions
31)]
32use 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
161impl Ui {
163 #[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 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 #[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 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 #[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 #[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 #[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 pub fn input_int_config<'ui>(&'ui self, label: impl Into<Cow<'ui, str>>) -> InputInt<'ui> {
253 InputInt::new(self, label)
254 }
255
256 pub fn input_float_config<'ui>(&'ui self, label: impl Into<Cow<'ui, str>>) -> InputFloat<'ui> {
258 InputFloat::new(self, label)
259 }
260
261 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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#[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#[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 unsafe { self.buf.refresh_len() };
488 result
489 }
490}
491impl<'ui, 'p> InputText<'ui, 'p, Cow<'ui, str>, Cow<'ui, str>, PassthroughCallback> {
492 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 pub fn flags(mut self, flags: InputTextFlags) -> Self {
510 self.flags = flags;
511 self
512 }
513
514 pub fn capacity_hint(mut self, cap: usize) -> Self {
516 self.capacity_hint = Some(cap);
517 self
518 }
519
520 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 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 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 pub fn read_only(mut self, read_only: bool) -> Self {
559 self.flags.set(InputTextFlags::READ_ONLY, read_only);
560 self
561 }
562
563 pub fn password(mut self, password: bool) -> Self {
565 self.flags.set(InputTextFlags::PASSWORD, password);
566 self
567 }
568
569 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 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 pub fn chars_decimal(mut self, decimal: bool) -> Self {
584 self.flags.set(InputTextFlags::CHARS_DECIMAL, decimal);
585 self
586 }
587
588 pub fn chars_hexadecimal(mut self, hex: bool) -> Self {
590 self.flags.set(InputTextFlags::CHARS_HEXADECIMAL, hex);
591 self
592 }
593
594 pub fn chars_uppercase(mut self, uppercase: bool) -> Self {
596 self.flags.set(InputTextFlags::CHARS_UPPERCASE, uppercase);
597 self
598 }
599
600 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
607impl<'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 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#[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#[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 unsafe { self.buf.refresh_len() };
840 result
841 }
842}
843impl<'ui, 'p> InputTextMultiline<'ui, 'p> {
844 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 pub fn flags(mut self, flags: InputTextFlags) -> Self {
863 self.flags = flags;
864 self
865 }
866
867 pub fn capacity_hint(mut self, cap: usize) -> Self {
869 self.capacity_hint = Some(cap);
870 self
871 }
872
873 pub fn read_only(mut self, read_only: bool) -> Self {
875 self.flags.set(InputTextFlags::READ_ONLY, read_only);
876 self
877 }
878
879 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 pub fn callback<T2: InputTextCallbackHandler>(
953 mut self,
954 callbacks: InputTextCallback,
955 handler: T2,
956 ) -> InputTextMultilineWithCb<'ui, 'p, T2> {
957 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
981pub 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}