use bitflags::bitflags; use std::ops::Range; use std::os::raw::{c_char, c_int, c_void}; use std::ptr; use crate::internal::DataTypeKind; use crate::math::*; use crate::sys; use crate::Ui; bitflags!( /// Flags for text inputs #[repr(C)] pub struct InputTextFlags: u32 { /// Allow 0123456789.+-*/ const CHARS_DECIMAL = sys::ImGuiInputTextFlags_CharsDecimal; /// Allow 0123456789ABCDEFabcdef const CHARS_HEXADECIMAL = sys::ImGuiInputTextFlags_CharsHexadecimal; /// Turn a..z into A..Z const CHARS_UPPERCASE = sys::ImGuiInputTextFlags_CharsUppercase; /// Filter out spaces, tabs const CHARS_NO_BLANK = sys::ImGuiInputTextFlags_CharsNoBlank; /// Select entire text when first taking mouse focus const AUTO_SELECT_ALL = sys::ImGuiInputTextFlags_AutoSelectAll; /// Return 'true' when Enter is pressed (as opposed to when the value was modified) const ENTER_RETURNS_TRUE = sys::ImGuiInputTextFlags_EnterReturnsTrue; /// Call user function on pressing TAB (for completion handling) const CALLBACK_COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; /// Call user function on pressing Up/Down arrows (for history handling) const CALLBACK_HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; /// Callback on any edit (note that InputText() already returns true on edit, the callback /// is useful mainly to manipulate the underlying buffer while focus is active) const CALLBACK_EDIT = sys::ImGuiInputTextFlags_CallbackEdit; /// Call user function every time. User code may query cursor position, modify text buffer. const CALLBACK_ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; /// Call user function to filter character. const CALLBACK_CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; /// Pressing TAB input a '\t' character into the text field const ALLOW_TAB_INPUT = sys::ImGuiInputTextFlags_AllowTabInput; /// In multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is /// opposite: unfocus with Ctrl+Enter, add line with Enter). const CTRL_ENTER_FOR_NEW_LINE = sys::ImGuiInputTextFlags_CtrlEnterForNewLine; /// Disable following the cursor horizontally const NO_HORIZONTAL_SCROLL = sys::ImGuiInputTextFlags_NoHorizontalScroll; /// Always overwrite (aka "insert mode"). const ALWAYS_OVERWRITE = sys::ImGuiInputTextFlags_AlwaysOverwrite; /// Read-only mode const READ_ONLY = sys::ImGuiInputTextFlags_ReadOnly; /// Password mode, display all characters as '*' const PASSWORD = sys::ImGuiInputTextFlags_Password; /// Disable undo/redo. const NO_UNDO_REDO = sys::ImGuiInputTextFlags_NoUndoRedo; /// Allow 0123456789.+-*/eE (Scientific notation input) const CHARS_SCIENTIFIC = sys::ImGuiInputTextFlags_CharsScientific; /// Allow buffer capacity resize + notify when the string wants to be resized const CALLBACK_RESIZE = sys::ImGuiInputTextFlags_CallbackResize; } ); macro_rules! impl_text_flags { ($InputType:ident) => { /// Override all existing flags with specified flags. Note /// some widgets may have flags set by default, so this should /// be used with care. #[inline] pub fn flags(mut self, flags: InputTextFlags) -> Self { self.flags = flags; self } #[inline] pub fn chars_decimal(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::CHARS_DECIMAL, value); self } #[inline] pub fn chars_hexadecimal(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::CHARS_HEXADECIMAL, value); self } #[inline] pub fn chars_uppercase(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::CHARS_UPPERCASE, value); self } #[inline] pub fn chars_noblank(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::CHARS_NO_BLANK, value); self } #[inline] pub fn auto_select_all(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::AUTO_SELECT_ALL, value); self } #[inline] pub fn enter_returns_true(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::ENTER_RETURNS_TRUE, value); self } #[inline] pub fn allow_tab_input(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::ALLOW_TAB_INPUT, value); self } #[inline] pub fn no_horizontal_scroll(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::NO_HORIZONTAL_SCROLL, value); self } /// Note: this is equivalent to `always_overwrite` #[inline] pub fn always_insert_mode(self, value: bool) -> Self { self.always_overwrite(value) } #[inline] #[allow(deprecated)] pub fn always_overwrite(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::ALWAYS_OVERWRITE, value); self } #[inline] pub fn read_only(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::READ_ONLY, value); self } #[inline] pub fn password(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::PASSWORD, value); self } #[inline] pub fn no_undo_redo(mut self, value: bool) -> Self { self.flags.set(InputTextFlags::NO_UNDO_REDO, value); self } }; } macro_rules! impl_step_params { ($InputType:ident, $Value:ty) => { #[inline] pub fn step(mut self, value: $Value) -> Self { self.step = value; self } #[inline] pub fn step_fast(mut self, value: $Value) -> Self { self.step_fast = value; self } }; } #[must_use] pub struct InputText<'ui, 'p, L, H = &'static str, T = PassthroughCallback> { label: L, hint: Option, buf: &'p mut String, callback_handler: T, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef> InputText<'ui, 'p, L> { /// Creates a new input text widget to edit the given string. /// /// # String Editing /// /// Please note, ImGui requires this string to be null-terminated. We accomplish this /// by appending and then removing a null terminator (`\0`) from the String you pass in. /// This has several consequences: /// 1. The string's backing buffer may be resized and relocated even without edits as result /// of this pushed char. /// 2. **The string will appear truncated if the string contains `\0` inside it.** This will not /// cause memory *unsafety*, but it will limit your usage. If that's the case, please pre-process /// your string. /// 3. Truncations by ImGui appear to be done primarily by insertions of `\0` to the truncation point. /// We will handle this for you and edit the string "properly" too, but this might show up in callbacks. pub fn new(ui: &'ui Ui, label: L, buf: &'p mut String) -> Self { InputText { label, hint: None, // this is fine because no one else has access to this and imgui is single threaded. callback_handler: PassthroughCallback, buf, flags: InputTextFlags::CALLBACK_RESIZE, ui, } } } impl<'ui, 'p, T, L, H> InputText<'ui, 'p, L, H, T> where L: AsRef, H: AsRef, T: InputTextCallbackHandler, { /// Sets the hint displayed in the input text background. #[inline] pub fn hint>(self, hint: H2) -> InputText<'ui, 'p, L, H2, T> { InputText { label: self.label, hint: Some(hint), buf: self.buf, callback_handler: self.callback_handler, flags: self.flags, ui: self.ui, } } impl_text_flags!(InputText); // I am commenting this ability out for now -- because we need to push `\0` for imgui, // we may resize the buffer no matter what, and we must do that. // The solution for this will be, I suspect, to build a second api channel that takes // an `&mut CStr`, which is ugly! I suspect few to none will want no-resizing, so I'm deferring // fixing the problem. -- sanbox-irl 09/15/2021, see #523 for more // // /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes // /// for [InputText] and [InputTextMultiline]. // /// // /// If, for some reason, you don't want this, you can run this function to prevent this. // /// In that case, edits which would cause a resize will not occur. // /// #[inline] // pub unsafe fn do_not_resize(mut self) -> Self { // self.flags.remove(InputTextFlags::CALLBACK_RESIZE); // self // } #[inline] pub fn callback( mut self, callbacks: InputTextCallback, callback: T2, ) -> InputText<'ui, 'p, L, H, T2> { if callbacks.contains(InputTextCallback::COMPLETION) { self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); } if callbacks.contains(InputTextCallback::HISTORY) { self.flags.insert(InputTextFlags::CALLBACK_HISTORY); } if callbacks.contains(InputTextCallback::ALWAYS) { self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); } if callbacks.contains(InputTextCallback::CHAR_FILTER) { self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); } if callbacks.contains(InputTextCallback::EDIT) { self.flags.insert(InputTextFlags::CALLBACK_EDIT); } InputText { callback_handler: callback, label: self.label, hint: self.hint, buf: self.buf, flags: self.flags, ui: self.ui, } } /// Builds the string editor, performing string editing operations. /// /// # String Editing /// /// Please note, ImGui requires this string to be null-terminated. We accomplish this /// by appending and then removing a null terminator (`\0`) from the String you pass in. /// This has several consequences: /// 1. The string's backing buffer may be resized and relocated even without edits as result /// of this pushed char. /// 2. **The string will appear truncated if the string contains `\0` inside it.** This will not /// cause memory *unsafety*, but it will limit your usage. If that's the case, please pre-process /// your string. /// 3. Truncations by ImGui appear to be done primarily by insertions of `\0` to the truncation point. /// We will handle this for you and edit the string "properly" too, but this might show up in callbacks. pub fn build(self) -> bool { // needs to be null-terminated! this is a hack! self.buf.push('\0'); let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity()); let mut data = UserData { container: self.buf, cback_handler: self.callback_handler, }; let data = &mut data as *mut _ as *mut c_void; let o = unsafe { if let Some(hint) = self.hint { let (label, hint) = self.ui.scratch_txt_two(self.label, hint); sys::igInputTextWithHint( label, hint, ptr as *mut sys::cty::c_char, capacity, self.flags.bits() as i32, Some(callback::), data, ) } else { let label = self.ui.scratch_txt(self.label); sys::igInputText( label, ptr as *mut sys::cty::c_char, capacity, self.flags.bits() as i32, Some(callback::), data, ) } }; let cap = self.buf.capacity(); // SAFETY: this slice is simply a view into the underlying buffer // of a String. We MAY be holding onto a view of uninitialized memory, // however, since we're holding this as a u8 slice, I think it should be // alright... // additionally, we can go over the bytes directly, rather than char indices, // because NUL will never appear in any UTF8 outside the NUL character (ie, within // a char). let buf = unsafe { std::slice::from_raw_parts(self.buf.as_ptr(), cap) }; if let Some(len) = buf.iter().position(|x| *x == b'\0') { // `len` is the position of the first `\0` byte in the String unsafe { self.buf.as_mut_vec().set_len(len); } } else { // There is no null terminator, the best we can do is to not // update the string length. } o } } #[must_use] pub struct InputTextMultiline<'ui, 'p, L, T = PassthroughCallback> { label: L, buf: &'p mut String, flags: InputTextFlags, size: [f32; 2], callback_handler: T, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef> InputTextMultiline<'ui, 'p, L, PassthroughCallback> { /// Creates a new input text widget to edit the given string. /// /// # String Editing /// /// Please note, ImGui requires this string to be null-terminated. We accomplish this /// by appending and then removing a null terminator (`\0`) from the String you pass in. /// This has several consequences: /// 1. The string's backing buffer may be resized and relocated even without edits as result /// of this pushed char. /// 2. **The string will appear truncated if the string contains `\0` inside it.** This will not /// cause memory *unsafety*, but it will limit your usage. If that's the case, please pre-process /// your string. /// 3. Truncations by ImGui appear to be done primarily by insertions of `\0` to the truncation point. /// We will handle this for you and edit the string "properly" too, but this might show up in callbacks. pub fn new(ui: &'ui Ui, label: L, buf: &'p mut String, size: impl Into) -> Self { InputTextMultiline { label, buf, flags: InputTextFlags::CALLBACK_RESIZE, size: size.into().into(), callback_handler: PassthroughCallback, ui, } } } impl<'ui, 'p, T: InputTextCallbackHandler, L: AsRef> InputTextMultiline<'ui, 'p, L, T> { impl_text_flags!(InputText); // I am commenting this ability out for now -- because we need to push `\0` for imgui, // we may resize the buffer no matter what, and we must do that. // The solution for this will be, I suspect, to build a second api channel that takes // an `&mut CStr`, which is ugly! I suspect few to none will want no-resizing, so I'm deferring // fixing the problem. -- sanbox-irl 09/15/2021, see #523 for more // /// By default (as of 0.8.0), imgui-rs will automatically handle string resizes // /// for [InputText] and [InputTextMultiline]. // /// // /// If, for some reason, you don't want this, you can run this function to prevent this. // /// In that case, edits which would cause a resize will not occur. // #[inline] // pub fn do_not_resize(mut self) -> Self { // self.flags.remove(InputTextFlags::CALLBACK_RESIZE); // self // } #[inline] pub fn callback( mut self, callbacks: InputTextMultilineCallback, callback_handler: T2, ) -> InputTextMultiline<'ui, 'p, L, T2> { if callbacks.contains(InputTextMultilineCallback::COMPLETION) { self.flags.insert(InputTextFlags::CALLBACK_COMPLETION); } if callbacks.contains(InputTextMultilineCallback::ALWAYS) { self.flags.insert(InputTextFlags::CALLBACK_ALWAYS); } if callbacks.contains(InputTextMultilineCallback::CHAR_FILTER) { self.flags.insert(InputTextFlags::CALLBACK_CHAR_FILTER); } if callbacks.contains(InputTextMultilineCallback::EDIT) { self.flags.insert(InputTextFlags::CALLBACK_EDIT); } InputTextMultiline { label: self.label, buf: self.buf, flags: self.flags, size: self.size, callback_handler, ui: self.ui, } } /// Builds the string editor, performing string editing operations. /// /// # String Editing /// /// Please note, ImGui requires this string to be null-terminated. We accomplish this /// by appending and then removing a null terminator (`\0`) from the String you pass in. /// This has several consequences: /// 1. The string's backing buffer may be resized and relocated even without edits as result /// of this pushed char. /// 2. **The string will appear truncated if the string contains `\0` inside it.** This will not /// cause memory *unsafety*, but it will limit your usage. If that's the case, please pre-process /// your string. /// 3. Truncations by ImGui appear to be done primarily by insertions of `\0` to the truncation point. /// We will handle this for you and edit the string "properly" too, but this might show up in callbacks. pub fn build(self) -> bool { // needs to be null-terminated! this is a hack! self.buf.push('\0'); let (ptr, capacity) = (self.buf.as_mut_ptr(), self.buf.capacity()); let mut data = UserData { container: self.buf, cback_handler: self.callback_handler, }; let data = &mut data as *mut _ as *mut c_void; let o = unsafe { sys::igInputTextMultiline( self.ui.scratch_txt(self.label), ptr as *mut sys::cty::c_char, capacity, self.size.into(), self.flags.bits() as i32, Some(callback::), data, ) }; let cap = self.buf.capacity(); // SAFETY: this slice is simply a view into the underlying buffer // of a String. We MAY be holding onto a view of uninitialized memory, // however, since we're holding this as a u8 slice, I think it should be // alright... // additionally, we can go over the bytes directly, rather than char indices, // because NUL will never appear in any UTF8 outside the NUL character (ie, within // a char). let buf = unsafe { std::slice::from_raw_parts(self.buf.as_ptr(), cap) }; if let Some(len) = buf.iter().position(|x| *x == b'\0') { // `len` is the position of the first `\0` byte in the String unsafe { self.buf.as_mut_vec().set_len(len); } } else { // There is no null terminator, the best we can do is to not // update the string length. } o } } #[must_use] pub struct InputInt<'ui, 'p, L> { label: L, value: &'p mut i32, step: i32, step_fast: i32, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef> InputInt<'ui, 'p, L> { #[deprecated( since = "0.9.0", note = "use `ui.input_int` or `ui.input_scalar` instead" )] pub fn new(ui: &'ui Ui, label: L, value: &'p mut i32) -> Self { InputInt { label, value, step: 1, step_fast: 100, flags: InputTextFlags::empty(), ui, } } pub fn build(self) -> bool { unsafe { sys::igInputInt( self.ui.scratch_txt(self.label), self.value as *mut i32, self.step, self.step_fast, self.flags.bits() as i32, ) } } impl_step_params!(InputInt, i32); impl_text_flags!(InputInt); } #[must_use] pub struct InputFloat<'ui, 'p, L, F = &'static str> { label: L, value: &'p mut f32, step: f32, step_fast: f32, display_format: Option, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef> InputFloat<'ui, 'p, L> { #[deprecated( since = "0.9.0", note = "use `ui.input_float` or `ui.input_scalar` instead" )] pub fn new(ui: &'ui Ui, label: L, value: &'p mut f32) -> Self { InputFloat { label, value, step: 0.0, step_fast: 0.0, display_format: None, flags: InputTextFlags::empty(), ui, } } pub fn display_format>(self, display_format: F2) -> InputFloat<'ui, 'p, L, F2> { InputFloat { value: self.value, label: self.label, step: self.step, step_fast: self.step_fast, display_format: Some(display_format), flags: self.flags, ui: self.ui, } } pub fn build(self) -> bool { let (one, two) = self .ui .scratch_txt_with_opt(self.label, self.display_format); unsafe { sys::igInputFloat( one, self.value as *mut f32, self.step, self.step_fast, two, self.flags.bits() as i32, ) } } impl_step_params!(InputFloat, f32); impl_text_flags!(InputFloat); } macro_rules! impl_input_floatn { ($InputFloatN:ident, $MINT_TARGET:ty, $N:expr, $igInputFloatN:ident) => { #[must_use] pub struct $InputFloatN<'ui, 'p, L, T, F = &'static str> { label: L, value: &'p mut T, display_format: Option, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L, T> $InputFloatN<'ui, 'p, L, T> where L: AsRef, T: Copy + Into<$MINT_TARGET>, $MINT_TARGET: Into + Into<[f32; $N]>, { pub fn new(ui: &'ui Ui, label: L, value: &'p mut T) -> Self { $InputFloatN { label, value, display_format: None, flags: InputTextFlags::empty(), ui, } } pub fn display_format>( self, display_format: F2, ) -> $InputFloatN<'ui, 'p, L, T, F2> { $InputFloatN { label: self.label, value: self.value, display_format: Some(display_format), flags: self.flags, ui: self.ui, } } pub fn build(self) -> bool { let value: $MINT_TARGET = (*self.value).into(); let mut value: [f32; $N] = value.into(); let (one, two) = self .ui .scratch_txt_with_opt(self.label, self.display_format); let changed = unsafe { sys::$igInputFloatN(one, value.as_mut_ptr(), two, self.flags.bits() as i32) }; if changed { let value: $MINT_TARGET = value.into(); *self.value = value.into(); } changed } impl_text_flags!($InputFloatN); } }; } impl_input_floatn!(InputFloat2, MintVec2, 2, igInputFloat2); impl_input_floatn!(InputFloat3, MintVec3, 3, igInputFloat3); impl_input_floatn!(InputFloat4, MintVec4, 4, igInputFloat4); macro_rules! impl_input_intn { ($InputIntN:ident, $MINT_TARGET:ident, $N:expr, $igInputIntN:ident) => { #[must_use] pub struct $InputIntN<'ui, 'p, L, T> { label: L, value: &'p mut T, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L, T> $InputIntN<'ui, 'p, L, T> where L: AsRef, T: Copy + Into<$MINT_TARGET>, $MINT_TARGET: Into + Into<[i32; $N]>, { pub fn new(ui: &'ui Ui, label: L, value: &'p mut T) -> Self { $InputIntN { label, value, flags: InputTextFlags::empty(), ui, } } pub fn build(self) -> bool { let value: $MINT_TARGET = (*self.value).into(); let mut value: [i32; $N] = value.into(); let changed = unsafe { sys::$igInputIntN( self.ui.scratch_txt(self.label), value.as_mut_ptr(), self.flags.bits() as i32, ) }; if changed { let value: $MINT_TARGET = value.into(); *self.value = value.into(); } changed } impl_text_flags!($InputIntN); } }; } impl_input_intn!(InputInt2, MintIVec2, 2, igInputInt2); impl_input_intn!(InputInt3, MintIVec3, 3, igInputInt3); impl_input_intn!(InputInt4, MintIVec4, 4, igInputInt4); /// Builder for an input scalar widget. #[must_use] pub struct InputScalar<'ui, 'p, T, L, F = &'static str> { value: &'p mut T, label: L, step: Option, step_fast: Option, display_format: Option, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef, T: DataTypeKind> InputScalar<'ui, 'p, T, L> { /// Constructs a new input scalar builder. #[doc(alias = "InputScalar", alias = "InputScalarN")] pub fn new(ui: &'ui Ui, label: L, value: &'p mut T) -> Self { InputScalar { value, label, step: None, step_fast: None, display_format: None, flags: InputTextFlags::empty(), ui, } } } impl<'ui, 'p, L: AsRef, T: DataTypeKind, F: AsRef> InputScalar<'ui, 'p, T, L, F> { /// Sets the display format using *a C-style printf string* pub fn display_format>( self, display_format: F2, ) -> InputScalar<'ui, 'p, T, L, F2> { InputScalar { value: self.value, label: self.label, step: self.step, step_fast: self.step_fast, display_format: Some(display_format), flags: self.flags, ui: self.ui, } } /// Builds an input scalar that is bound to the given value. /// /// Returns true if the value was changed. pub fn build(self) -> bool { unsafe { let (one, two) = self .ui .scratch_txt_with_opt(self.label, self.display_format); sys::igInputScalar( one, T::KIND as i32, self.value as *mut T as *mut c_void, self.step .as_ref() .map(|step| step as *const T) .unwrap_or(ptr::null()) as *const c_void, self.step_fast .as_ref() .map(|step| step as *const T) .unwrap_or(ptr::null()) as *const c_void, two, self.flags.bits() as i32, ) } } #[inline] pub fn step(mut self, value: T) -> Self { self.step = Some(value); self } #[inline] pub fn step_fast(mut self, value: T) -> Self { self.step_fast = Some(value); self } impl_text_flags!(InputScalar); } /// Builder for an input scalar widget. #[must_use] pub struct InputScalarN<'ui, 'p, T, L, F = &'static str> { values: &'p mut [T], label: L, step: Option, step_fast: Option, display_format: Option, flags: InputTextFlags, ui: &'ui Ui, } impl<'ui, 'p, L: AsRef, T: DataTypeKind> InputScalarN<'ui, 'p, T, L> { /// Constructs a new input scalar builder. #[doc(alias = "InputScalarN")] pub fn new(ui: &'ui Ui, label: L, values: &'p mut [T]) -> Self { InputScalarN { values, label, step: None, step_fast: None, display_format: None, flags: InputTextFlags::empty(), ui, } } } impl<'ui, 'p, L: AsRef, T: DataTypeKind, F: AsRef> InputScalarN<'ui, 'p, T, L, F> { /// Sets the display format using *a C-style printf string* pub fn display_format>( self, display_format: F2, ) -> InputScalarN<'ui, 'p, T, L, F2> { InputScalarN { values: self.values, label: self.label, step: self.step, step_fast: self.step_fast, display_format: Some(display_format), flags: self.flags, ui: self.ui, } } /// Builds a horizontal array of multiple input scalars attached to the given slice. /// /// Returns true if any value was changed. pub fn build(self) -> bool { unsafe { let (one, two) = self .ui .scratch_txt_with_opt(self.label, self.display_format); sys::igInputScalarN( one, T::KIND as i32, self.values.as_mut_ptr() as *mut c_void, self.values.len() as i32, self.step .as_ref() .map(|step| step as *const T) .unwrap_or(ptr::null()) as *const c_void, self.step_fast .as_ref() .map(|step| step as *const T) .unwrap_or(ptr::null()) as *const c_void, two, self.flags.bits() as i32, ) } } #[inline] pub fn step(mut self, value: T) -> Self { self.step = Some(value); self } #[inline] pub fn step_fast(mut self, value: T) -> Self { self.step_fast = Some(value); self } impl_text_flags!(InputScalar); } bitflags!( /// Callback flags for an `InputText` widget. These correspond to /// the general textflags. pub struct InputTextCallback: u32 { /// Call user function on pressing TAB (for completion handling) const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; /// Call user function on pressing Up/Down arrows (for history handling) const HISTORY = sys::ImGuiInputTextFlags_CallbackHistory; /// Call user function every time. User code may query cursor position, modify text buffer. const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; /// Call user function to filter character. const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; /// Callback on buffer edit (note that InputText already returns true on edit, the /// callback is useful mainly to manipulate the underlying buffer while focus is active) const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; } ); bitflags!( /// Callback flags for an `InputTextMultiline` widget. These correspond to the /// general textflags. pub struct InputTextMultilineCallback: u32 { /// Call user function on pressing TAB (for completion handling) const COMPLETION = sys::ImGuiInputTextFlags_CallbackCompletion; /// Call user function every time. User code may query cursor position, modify text buffer. const ALWAYS = sys::ImGuiInputTextFlags_CallbackAlways; /// Call user function to filter character. const CHAR_FILTER = sys::ImGuiInputTextFlags_CallbackCharFilter; /// Callback on buffer edit (note that InputText already returns true on edit, the /// callback is useful mainly to manipulate the underlying buffer while focus is active) const EDIT = sys::ImGuiInputTextFlags_CallbackEdit; } ); /// This trait provides an interface which ImGui will call on `InputText` /// and `InputTextMultiline` callbacks. /// /// Each method is called *if and only if* the corresponding flag for each /// method is passed to ImGui in the `callback` builder. /// /// Each method here lists the flag required to call it, and this module begins /// with an example of callbacks being used. pub trait InputTextCallbackHandler { /// Filters a char -- returning a `None` means that the char is removed, /// and returning another char substitutes it out. /// /// Because of upstream ImGui choices, you do not have access to the buffer /// during this callback (for some reason). /// /// To make ImGui run this callback, use [InputTextCallback::CHAR_FILTER] or /// [InputTextMultilineCallback::CHAR_FILTER]. fn char_filter(&mut self, c: char) -> Option { Some(c) } /// Allows one to perform autocompletion work when the Tab key has been pressed. /// /// To make ImGui run this callback, use [InputTextCallback::COMPLETION] or /// [InputTextMultilineCallback::COMPLETION]. fn on_completion(&mut self, _: TextCallbackData) {} /// Allows one to edit the inner buffer whenever the buffer has been changed. /// /// To make ImGui run this callback, use [InputTextCallback::EDIT] or /// [InputTextMultilineCallback::EDIT]. fn on_edit(&mut self, _: TextCallbackData) {} /// A callback when one of the direction keys have been pressed. /// /// To make ImGui run this callback, use [InputTextCallback::HISTORY]. It appears /// that this callback will not be ran in a multiline input widget at all. fn on_history(&mut self, _: HistoryDirection, _: TextCallbackData) {} /// A callback which will always fire, each tick. /// /// To make ImGui run this callback, use [InputTextCallback::ALWAYS] or /// [InputTextMultilineCallback::ALWAYS]. fn on_always(&mut self, _: TextCallbackData) {} } /// The arrow key a user pressed to trigger the `on_history` callback. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum HistoryDirection { Up, Down, } /// This struct provides methods to edit the underlying text buffer that /// Dear ImGui manipulates. Primarily, it gives [remove_chars](Self::remove_chars), /// [insert_chars](Self::insert_chars), and mutable access to what text is selected. pub struct TextCallbackData(*mut sys::ImGuiInputTextCallbackData); impl TextCallbackData { /// Creates the buffer. unsafe fn new(data: *mut sys::ImGuiInputTextCallbackData) -> Self { Self(data) } /// Get a reference to the text callback buffer's str. pub fn str(&self) -> &str { unsafe { std::str::from_utf8(std::slice::from_raw_parts( (*(self.0)).Buf as *const _, (*(self.0)).BufTextLen as usize, )) .expect("internal imgui error -- it boofed a utf8") } } /// Gives access to the underlying byte array MUTABLY. /// /// ## Safety /// /// This is very unsafe, and the following invariants must be /// upheld: /// 1. Keep the data utf8 valid. /// 2. After editing the string, call [set_dirty]. /// /// To truncate the string, please use [remove_chars]. To extend /// the string, please use [insert_chars] and [push_str]. /// /// This function should have highly limited usage, but could be for /// editing certain characters in the buffer based on some external condition. /// /// [remove_chars]: Self::remove_chars /// [set_dirty]: Self::set_dirty /// [insert_chars]: Self::insert_chars /// [push_str]: Self::push_str pub unsafe fn str_as_bytes_mut(&mut self) -> &mut [u8] { let str = std::str::from_utf8_mut(std::slice::from_raw_parts_mut( (*(self.0)).Buf as *const _ as *mut _, (*(self.0)).BufTextLen as usize, )) .expect("internal imgui error -- it boofed a utf8"); str.as_bytes_mut() } /// Sets the dirty flag on the text to imgui, indicating that /// it should reapply this string to its internal state. /// /// **NB:** You only need to use this method if you're using `[str_as_bytes_mut]`. /// If you use the helper methods [remove_chars] and [insert_chars], /// this will be set for you. However, this is no downside to setting /// the dirty flag spuriously except the minor CPU time imgui will spend. /// /// [str_as_bytes_mut]: Self::str_as_bytes_mut /// [remove_chars]: Self::remove_chars /// [insert_chars]: Self::insert_chars pub fn set_dirty(&mut self) { unsafe { (*(self.0)).BufDirty = true; } } /// Gets a range of the selected text. See [selection_start_mut](Self::selection_start_mut) /// and [selection_end_mut](Self::selection_end_mut) to mutably edit these values. /// /// This Range is given in `usize` so that it might be used in indexing /// operations more easily. To quickly grab the selected text, use [selected](Self::selected). pub fn selection(&self) -> Range { let (start, end) = unsafe { ( (*(self.0)).SelectionStart as usize, (*(self.0)).SelectionEnd as usize, ) }; // Avoid returning a range with start > end, which would be problematic. For example, it // would cause panics when used to index the string buffer and would also cause Self::has_selection // to return a false negative. if start < end { start..end } else { end..start } } /// Returns the selected text directly. Note that if no text is selected, /// an empty str slice will be returned. pub fn selected(&self) -> &str { &self.str()[self.selection()] } /// Sets the cursor to select all. pub fn select_all(&mut self) { unsafe { sys::ImGuiInputTextCallbackData_SelectAll(self.0); } } /// Clears the selection. pub fn clear_selection(&mut self) { unsafe { sys::ImGuiInputTextCallbackData_ClearSelection(self.0); } } /// Checks if there is a selection within the text. pub fn has_selection(&self) -> bool { !self.selection().is_empty() } /// Pushes the given str to the end of this buffer. If this /// would require the String to resize, it will be resized. /// This is automatically handled. pub fn push_str(&mut self, s: &str) { // this is safe because the ench of a self.str is a char_boundary. unsafe { self.insert_chars_unsafe((*self.0).BufTextLen as usize, s); } } /// Inserts the given string at the given position. If this /// would require the String to resize, it will be resized /// automatically. /// /// ## Panics /// Panics if the `pos` is not a char_boundary. pub fn insert_chars(&mut self, pos: usize, s: &str) { assert!(self.str().is_char_boundary(pos)); unsafe { self.insert_chars_unsafe(pos, s); } } /// Inserts the given string at the given position, unsafely. If this /// would require the String to resize, it will be resized automatically. /// /// ## Safety /// /// It is up to the caller to confirm that the `pos` is a valid byte /// position, or use [insert_chars](Self::insert_chars) which will panic /// if it isn't. pub unsafe fn insert_chars_unsafe(&mut self, pos: usize, s: &str) { let start = s.as_ptr(); let end = start.add(s.len()); sys::ImGuiInputTextCallbackData_InsertChars( self.0, pos as i32, start as *const c_char, end as *const c_char, ); } /// Clears the string to an empty buffer. pub fn clear(&mut self) { unsafe { self.remove_chars_unchecked(0, (*self.0).BufTextLen as usize); } } /// Removes the given number of characters from the string starting /// at some byte pos. /// /// ## Panics /// Panics if the `pos` is not a char boundary. pub fn remove_chars(&mut self, pos: usize, char_count: usize) { let inner = &self.str()[pos..]; let byte_count = inner .char_indices() .nth(char_count) .map(|v| v.0) .unwrap_or_else(|| inner.len()); unsafe { self.remove_chars_unchecked(pos, byte_count); } } /// Removes the given number of bytes from the string starting /// at some byte pos, without checking for utf8 validity. Use /// [remove_chars](Self::remove_chars) for a safe variant. /// /// ## Safety /// /// It is up to the caller to ensure that the position is at a valid utf8 char_boundary /// and that there are enough bytes within the string remaining. pub unsafe fn remove_chars_unchecked(&mut self, pos: usize, byte_count: usize) { sys::ImGuiInputTextCallbackData_DeleteChars(self.0, pos as i32, byte_count as i32); } /// Get a reference to the text callback buffer's cursor pos. pub fn cursor_pos(&self) -> usize { unsafe { (*self.0).CursorPos as usize } } /// Set the text callback buffer's cursor pos. pub fn set_cursor_pos(&mut self, cursor_pos: usize) { unsafe { (*self.0).CursorPos = cursor_pos as i32; } } /// Get a mutable reference to the text callback buffer's selection start. pub fn selection_start_mut(&mut self) -> &mut i32 { unsafe { &mut (*self.0).SelectionStart } } /// Get a mutable reference to the text callback buffer's selection end.. pub fn selection_end_mut(&mut self) -> &mut i32 { unsafe { &mut (*self.0).SelectionEnd } } } #[repr(C)] struct UserData { container: *mut String, cback_handler: T, } /// This is our default callback. extern "C" fn callback( data: *mut sys::ImGuiInputTextCallbackData, ) -> c_int { struct CallbackData<'a, T> { event_flag: InputTextFlags, user_data: &'a mut UserData, } let callback_data = unsafe { CallbackData { event_flag: InputTextFlags::from_bits((*data).EventFlag as u32).unwrap(), user_data: &mut *((*data).UserData as *mut UserData), } }; // check this callback. match callback_data.event_flag { InputTextFlags::CALLBACK_ALWAYS => { let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data.user_data.cback_handler.on_always(text_info); } InputTextFlags::CALLBACK_EDIT => { let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data.user_data.cback_handler.on_edit(text_info); } InputTextFlags::CALLBACK_COMPLETION => { let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data .user_data .cback_handler .on_completion(text_info); } InputTextFlags::CALLBACK_RESIZE => { unsafe { let requested_size = (*data).BufSize as usize; let buffer = &mut *callback_data.user_data.container; // just confirm that we ARE working with our string. debug_assert_eq!(buffer.as_ptr() as *const _, (*data).Buf); if requested_size > buffer.capacity() { let additional_bytes = requested_size - buffer.len(); // reserve more data... buffer.reserve(additional_bytes); (*data).Buf = buffer.as_mut_ptr() as *mut _; (*data).BufDirty = true; } } } InputTextFlags::CALLBACK_CHAR_FILTER => { let chr = unsafe { std::char::from_u32((*data).EventChar).unwrap() }; let new_data = match callback_data.user_data.cback_handler.char_filter(chr) { Some(value) => u32::from(value), // 0 means "do not use this char" in imgui docs None => 0, }; // set the new char... unsafe { (*data).EventChar = new_data; } } InputTextFlags::CALLBACK_HISTORY => { let key = unsafe { let key = (*data).EventKey; match key { sys::ImGuiKey_UpArrow => HistoryDirection::Up, sys::ImGuiKey_DownArrow => HistoryDirection::Down, _ => panic!("Unexpected key"), } }; let text_info = unsafe { TextCallbackData::new(&mut *data) }; callback_data .user_data .cback_handler .on_history(key, text_info); } _ => {} } 0 } /// This is a Zst which implements TextCallbackHandler as a passthrough. /// /// If you do not set a callback handler, this will be used (but will never /// actually run, since you will not have pass imgui any flags). pub struct PassthroughCallback; impl InputTextCallbackHandler for PassthroughCallback {}