@ -894,10 +894,6 @@ static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sort
static ImRect GetViewportRect ( ) ;
static bool InputTextFilterCharacter ( unsigned int * p_char , ImGuiInputTextFlags flags , ImGuiInputTextCallback callback , void * user_data ) ;
static int InputTextCalcTextLenAndLineCount ( const char * text_begin , const char * * out_text_end ) ;
static ImVec2 InputTextCalcTextSizeW ( const ImWchar * text_begin , const ImWchar * text_end , const ImWchar * * remaining = NULL , ImVec2 * out_offset = NULL , bool stop_on_new_line = false ) ;
static inline int DataTypeFormatString ( char * buf , int buf_size , ImGuiDataType data_type , const void * data_ptr , const char * format ) ;
static void DataTypeApplyOp ( ImGuiDataType data_type , int op , void * output , void * arg_1 , const void * arg_2 ) ;
static bool DataTypeApplyOpFromText ( const char * buf , const char * initial_value_buf , ImGuiDataType data_type , void * data_ptr , const char * format ) ;
@ -9065,973 +9061,6 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_
return value_changed ;
}
static int InputTextCalcTextLenAndLineCount ( const char * text_begin , const char * * out_text_end )
{
int line_count = 0 ;
const char * s = text_begin ;
while ( char c = * s + + ) // We are only matching for \n so we can ignore UTF-8 decoding
if ( c = = ' \n ' )
line_count + + ;
s - - ;
if ( s [ 0 ] ! = ' \n ' & & s [ 0 ] ! = ' \r ' )
line_count + + ;
* out_text_end = s ;
return line_count ;
}
static ImVec2 InputTextCalcTextSizeW ( const ImWchar * text_begin , const ImWchar * text_end , const ImWchar * * remaining , ImVec2 * out_offset , bool stop_on_new_line )
{
ImFont * font = GImGui - > Font ;
const float line_height = GImGui - > FontSize ;
const float scale = line_height / font - > FontSize ;
ImVec2 text_size = ImVec2 ( 0 , 0 ) ;
float line_width = 0.0f ;
const ImWchar * s = text_begin ;
while ( s < text_end )
{
unsigned int c = ( unsigned int ) ( * s + + ) ;
if ( c = = ' \n ' )
{
text_size . x = ImMax ( text_size . x , line_width ) ;
text_size . y + = line_height ;
line_width = 0.0f ;
if ( stop_on_new_line )
break ;
continue ;
}
if ( c = = ' \r ' )
continue ;
const float char_width = font - > GetCharAdvance ( ( unsigned short ) c ) * scale ;
line_width + = char_width ;
}
if ( text_size . x < line_width )
text_size . x = line_width ;
if ( out_offset )
* out_offset = ImVec2 ( line_width , text_size . y + line_height ) ; // offset allow for the possibility of sitting after a trailing \n
if ( line_width > 0 | | text_size . y = = 0.0f ) // whereas size.y will ignore the trailing \n
text_size . y + = line_height ;
if ( remaining )
* remaining = s ;
return text_size ;
}
// Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
namespace ImGuiStb
{
static int STB_TEXTEDIT_STRINGLEN ( const STB_TEXTEDIT_STRING * obj ) { return obj - > CurLenW ; }
static ImWchar STB_TEXTEDIT_GETCHAR ( const STB_TEXTEDIT_STRING * obj , int idx ) { return obj - > TextW [ idx ] ; }
static float STB_TEXTEDIT_GETWIDTH ( STB_TEXTEDIT_STRING * obj , int line_start_idx , int char_idx ) { ImWchar c = obj - > TextW [ line_start_idx + char_idx ] ; if ( c = = ' \n ' ) return STB_TEXTEDIT_GETWIDTH_NEWLINE ; return GImGui - > Font - > GetCharAdvance ( c ) * ( GImGui - > FontSize / GImGui - > Font - > FontSize ) ; }
static int STB_TEXTEDIT_KEYTOTEXT ( int key ) { return key > = 0x10000 ? 0 : key ; }
static ImWchar STB_TEXTEDIT_NEWLINE = ' \n ' ;
static void STB_TEXTEDIT_LAYOUTROW ( StbTexteditRow * r , STB_TEXTEDIT_STRING * obj , int line_start_idx )
{
const ImWchar * text = obj - > TextW . Data ;
const ImWchar * text_remaining = NULL ;
const ImVec2 size = InputTextCalcTextSizeW ( text + line_start_idx , text + obj - > CurLenW , & text_remaining , NULL , true ) ;
r - > x0 = 0.0f ;
r - > x1 = size . x ;
r - > baseline_y_delta = size . y ;
r - > ymin = 0.0f ;
r - > ymax = size . y ;
r - > num_chars = ( int ) ( text_remaining - ( text + line_start_idx ) ) ;
}
static bool is_separator ( unsigned int c ) { return ImCharIsBlankW ( c ) | | c = = ' , ' | | c = = ' ; ' | | c = = ' ( ' | | c = = ' ) ' | | c = = ' { ' | | c = = ' } ' | | c = = ' [ ' | | c = = ' ] ' | | c = = ' | ' ; }
static int is_word_boundary_from_right ( STB_TEXTEDIT_STRING * obj , int idx ) { return idx > 0 ? ( is_separator ( obj - > TextW [ idx - 1 ] ) & & ! is_separator ( obj - > TextW [ idx ] ) ) : 1 ; }
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL ( STB_TEXTEDIT_STRING * obj , int idx ) { idx - - ; while ( idx > = 0 & & ! is_word_boundary_from_right ( obj , idx ) ) idx - - ; return idx < 0 ? 0 : idx ; }
# ifdef __APPLE__ // FIXME: Move setting to IO structure
static int is_word_boundary_from_left ( STB_TEXTEDIT_STRING * obj , int idx ) { return idx > 0 ? ( ! is_separator ( obj - > TextW [ idx - 1 ] ) & & is_separator ( obj - > TextW [ idx ] ) ) : 1 ; }
static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL ( STB_TEXTEDIT_STRING * obj , int idx ) { idx + + ; int len = obj - > CurLenW ; while ( idx < len & & ! is_word_boundary_from_left ( obj , idx ) ) idx + + ; return idx > len ? len : idx ; }
# else
static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL ( STB_TEXTEDIT_STRING * obj , int idx ) { idx + + ; int len = obj - > CurLenW ; while ( idx < len & & ! is_word_boundary_from_right ( obj , idx ) ) idx + + ; return idx > len ? len : idx ; }
# endif
# define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
# define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
static void STB_TEXTEDIT_DELETECHARS ( STB_TEXTEDIT_STRING * obj , int pos , int n )
{
ImWchar * dst = obj - > TextW . Data + pos ;
// We maintain our buffer length in both UTF-8 and wchar formats
obj - > CurLenA - = ImTextCountUtf8BytesFromStr ( dst , dst + n ) ;
obj - > CurLenW - = n ;
// Offset remaining text
const ImWchar * src = obj - > TextW . Data + pos + n ;
while ( ImWchar c = * src + + )
* dst + + = c ;
* dst = ' \0 ' ;
}
static bool STB_TEXTEDIT_INSERTCHARS ( STB_TEXTEDIT_STRING * obj , int pos , const ImWchar * new_text , int new_text_len )
{
const bool is_resizable = ( obj - > UserFlags & ImGuiInputTextFlags_CallbackResize ) ! = 0 ;
const int text_len = obj - > CurLenW ;
IM_ASSERT ( pos < = text_len ) ;
const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr ( new_text , new_text + new_text_len ) ;
if ( ! is_resizable & & ( new_text_len_utf8 + obj - > CurLenA + 1 > obj - > BufCapacityA ) )
return false ;
// Grow internal buffer if needed
if ( new_text_len + text_len + 1 > obj - > TextW . Size )
{
if ( ! is_resizable )
return false ;
IM_ASSERT ( text_len < obj - > TextW . Size ) ;
obj - > TextW . resize ( text_len + ImClamp ( new_text_len * 4 , 32 , ImMax ( 256 , new_text_len ) ) + 1 ) ;
}
ImWchar * text = obj - > TextW . Data ;
if ( pos ! = text_len )
memmove ( text + pos + new_text_len , text + pos , ( size_t ) ( text_len - pos ) * sizeof ( ImWchar ) ) ;
memcpy ( text + pos , new_text , ( size_t ) new_text_len * sizeof ( ImWchar ) ) ;
obj - > CurLenW + = new_text_len ;
obj - > CurLenA + = new_text_len_utf8 ;
obj - > TextW [ obj - > CurLenW ] = ' \0 ' ;
return true ;
}
// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
# define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
# define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
# define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
# define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
# define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
# define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
# define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
# define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
# define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
# define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
# define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
# define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
# define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
# define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
# define STB_TEXTEDIT_K_SHIFT 0x20000
# define STB_TEXTEDIT_IMPLEMENTATION
# include "stb_textedit.h"
}
void ImGuiInputTextState : : OnKeyPressed ( int key )
{
stb_textedit_key ( this , & StbState , key ) ;
CursorFollow = true ;
CursorAnimReset ( ) ;
}
ImGuiInputTextCallbackData : : ImGuiInputTextCallbackData ( )
{
memset ( this , 0 , sizeof ( * this ) ) ;
}
// Public API to manipulate UTF-8 text
// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
void ImGuiInputTextCallbackData : : DeleteChars ( int pos , int bytes_count )
{
IM_ASSERT ( pos + bytes_count < = BufTextLen ) ;
char * dst = Buf + pos ;
const char * src = Buf + pos + bytes_count ;
while ( char c = * src + + )
* dst + + = c ;
* dst = ' \0 ' ;
if ( CursorPos + bytes_count > = pos )
CursorPos - = bytes_count ;
else if ( CursorPos > = pos )
CursorPos = pos ;
SelectionStart = SelectionEnd = CursorPos ;
BufDirty = true ;
BufTextLen - = bytes_count ;
}
void ImGuiInputTextCallbackData : : InsertChars ( int pos , const char * new_text , const char * new_text_end )
{
const bool is_resizable = ( Flags & ImGuiInputTextFlags_CallbackResize ) ! = 0 ;
const int new_text_len = new_text_end ? ( int ) ( new_text_end - new_text ) : ( int ) strlen ( new_text ) ;
if ( new_text_len + BufTextLen > = BufSize )
{
if ( ! is_resizable )
return ;
// Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
ImGuiContext & g = * GImGui ;
ImGuiInputTextState * edit_state = & g . InputTextState ;
IM_ASSERT ( edit_state - > ID ! = 0 & & g . ActiveId = = edit_state - > ID ) ;
IM_ASSERT ( Buf = = edit_state - > TempBuffer . Data ) ;
int new_buf_size = BufTextLen + ImClamp ( new_text_len * 4 , 32 , ImMax ( 256 , new_text_len ) ) + 1 ;
edit_state - > TempBuffer . reserve ( new_buf_size + 1 ) ;
Buf = edit_state - > TempBuffer . Data ;
BufSize = edit_state - > BufCapacityA = new_buf_size ;
}
if ( BufTextLen ! = pos )
memmove ( Buf + pos + new_text_len , Buf + pos , ( size_t ) ( BufTextLen - pos ) ) ;
memcpy ( Buf + pos , new_text , ( size_t ) new_text_len * sizeof ( char ) ) ;
Buf [ BufTextLen + new_text_len ] = ' \0 ' ;
if ( CursorPos > = pos )
CursorPos + = new_text_len ;
SelectionStart = SelectionEnd = CursorPos ;
BufDirty = true ;
BufTextLen + = new_text_len ;
}
// Return false to discard a character.
static bool InputTextFilterCharacter ( unsigned int * p_char , ImGuiInputTextFlags flags , ImGuiInputTextCallback callback , void * user_data )
{
unsigned int c = * p_char ;
if ( c < 128 & & c ! = ' ' & & ! isprint ( ( int ) ( c & 0xFF ) ) )
{
bool pass = false ;
pass | = ( c = = ' \n ' & & ( flags & ImGuiInputTextFlags_Multiline ) ) ;
pass | = ( c = = ' \t ' & & ( flags & ImGuiInputTextFlags_AllowTabInput ) ) ;
if ( ! pass )
return false ;
}
if ( c > = 0xE000 & & c < = 0xF8FF ) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
return false ;
if ( flags & ( ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific ) )
{
if ( flags & ImGuiInputTextFlags_CharsDecimal )
if ( ! ( c > = ' 0 ' & & c < = ' 9 ' ) & & ( c ! = ' . ' ) & & ( c ! = ' - ' ) & & ( c ! = ' + ' ) & & ( c ! = ' * ' ) & & ( c ! = ' / ' ) )
return false ;
if ( flags & ImGuiInputTextFlags_CharsScientific )
if ( ! ( c > = ' 0 ' & & c < = ' 9 ' ) & & ( c ! = ' . ' ) & & ( c ! = ' - ' ) & & ( c ! = ' + ' ) & & ( c ! = ' * ' ) & & ( c ! = ' / ' ) & & ( c ! = ' e ' ) & & ( c ! = ' E ' ) )
return false ;
if ( flags & ImGuiInputTextFlags_CharsHexadecimal )
if ( ! ( c > = ' 0 ' & & c < = ' 9 ' ) & & ! ( c > = ' a ' & & c < = ' f ' ) & & ! ( c > = ' A ' & & c < = ' F ' ) )
return false ;
if ( flags & ImGuiInputTextFlags_CharsUppercase )
if ( c > = ' a ' & & c < = ' z ' )
* p_char = ( c + = ( unsigned int ) ( ' A ' - ' a ' ) ) ;
if ( flags & ImGuiInputTextFlags_CharsNoBlank )
if ( ImCharIsBlankW ( c ) )
return false ;
}
if ( flags & ImGuiInputTextFlags_CallbackCharFilter )
{
ImGuiInputTextCallbackData callback_data ;
memset ( & callback_data , 0 , sizeof ( ImGuiInputTextCallbackData ) ) ;
callback_data . EventFlag = ImGuiInputTextFlags_CallbackCharFilter ;
callback_data . EventChar = ( ImWchar ) c ;
callback_data . Flags = flags ;
callback_data . UserData = user_data ;
if ( callback ( & callback_data ) ! = 0 )
return false ;
* p_char = callback_data . EventChar ;
if ( ! callback_data . EventChar )
return false ;
}
return true ;
}
// Edit a string of text
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
// - If you want to use ImGui::InputText() with std::string, see misc/stl/imgui_stl.h
// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
bool ImGui : : InputTextEx ( const char * label , char * buf , int buf_size , const ImVec2 & size_arg , ImGuiInputTextFlags flags , ImGuiInputTextCallback callback , void * callback_user_data )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
IM_ASSERT ( ! ( ( flags & ImGuiInputTextFlags_CallbackHistory ) & & ( flags & ImGuiInputTextFlags_Multiline ) ) ) ; // Can't use both together (they both use up/down keys)
IM_ASSERT ( ! ( ( flags & ImGuiInputTextFlags_CallbackCompletion ) & & ( flags & ImGuiInputTextFlags_AllowTabInput ) ) ) ; // Can't use both together (they both use tab key)
ImGuiContext & g = * GImGui ;
const ImGuiIO & io = g . IO ;
const ImGuiStyle & style = g . Style ;
const bool is_multiline = ( flags & ImGuiInputTextFlags_Multiline ) ! = 0 ;
const bool is_editable = ( flags & ImGuiInputTextFlags_ReadOnly ) = = 0 ;
const bool is_password = ( flags & ImGuiInputTextFlags_Password ) ! = 0 ;
const bool is_undoable = ( flags & ImGuiInputTextFlags_NoUndoRedo ) = = 0 ;
const bool is_resizable = ( flags & ImGuiInputTextFlags_CallbackResize ) ! = 0 ;
if ( is_resizable )
IM_ASSERT ( callback ! = NULL ) ; // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
if ( is_multiline ) // Open group before calling GetID() because groups tracks id created within their scope,
BeginGroup ( ) ;
const ImGuiID id = window - > GetID ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
ImVec2 size = CalcItemSize ( size_arg , CalcItemWidth ( ) , ( is_multiline ? GetTextLineHeight ( ) * 8.0f : label_size . y ) + style . FramePadding . y * 2.0f ) ; // Arbitrary default of 8 lines high for multi-line
const ImRect frame_bb ( window - > DC . CursorPos , window - > DC . CursorPos + size ) ;
const ImRect total_bb ( frame_bb . Min , frame_bb . Max + ImVec2 ( label_size . x > 0.0f ? ( style . ItemInnerSpacing . x + label_size . x ) : 0.0f , 0.0f ) ) ;
ImGuiWindow * draw_window = window ;
if ( is_multiline )
{
ItemAdd ( total_bb , id , & frame_bb ) ;
if ( ! BeginChildFrame ( id , frame_bb . GetSize ( ) ) )
{
EndChildFrame ( ) ;
EndGroup ( ) ;
return false ;
}
draw_window = GetCurrentWindow ( ) ;
draw_window - > DC . NavLayerActiveMaskNext | = draw_window - > DC . NavLayerCurrentMask ; // This is to ensure that EndChild() will display a navigation highlight
size . x - = draw_window - > ScrollbarSizes . x ;
}
else
{
ItemSize ( total_bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( total_bb , id , & frame_bb ) )
return false ;
}
const bool hovered = ItemHoverable ( frame_bb , id ) ;
if ( hovered )
g . MouseCursor = ImGuiMouseCursor_TextInput ;
// Password pushes a temporary font with only a fallback glyph
if ( is_password )
{
const ImFontGlyph * glyph = g . Font - > FindGlyph ( ' * ' ) ;
ImFont * password_font = & g . InputTextPasswordFont ;
password_font - > FontSize = g . Font - > FontSize ;
password_font - > Scale = g . Font - > Scale ;
password_font - > DisplayOffset = g . Font - > DisplayOffset ;
password_font - > Ascent = g . Font - > Ascent ;
password_font - > Descent = g . Font - > Descent ;
password_font - > ContainerAtlas = g . Font - > ContainerAtlas ;
password_font - > FallbackGlyph = glyph ;
password_font - > FallbackAdvanceX = glyph - > AdvanceX ;
IM_ASSERT ( password_font - > Glyphs . empty ( ) & & password_font - > IndexAdvanceX . empty ( ) & & password_font - > IndexLookup . empty ( ) ) ;
PushFont ( password_font ) ;
}
// NB: we are only allowed to access 'edit_state' if we are the active widget.
ImGuiInputTextState & edit_state = g . InputTextState ;
const bool focus_requested = FocusableItemRegister ( window , id , ( flags & ( ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput ) ) = = 0 ) ; // Using completion callback disable keyboard tabbing
const bool focus_requested_by_code = focus_requested & & ( window - > FocusIdxAllCounter = = window - > FocusIdxAllRequestCurrent ) ;
const bool focus_requested_by_tab = focus_requested & & ! focus_requested_by_code ;
const bool user_clicked = hovered & & io . MouseClicked [ 0 ] ;
const bool user_scrolled = is_multiline & & g . ActiveId = = 0 & & edit_state . ID = = id & & g . ActiveIdPreviousFrame = = draw_window - > GetIDNoKeepAlive ( " #SCROLLY " ) ;
const bool user_nav_input_start = ( g . ActiveId ! = id ) & & ( ( g . NavInputId = = id ) | | ( g . NavActivateId = = id & & g . NavInputSource = = ImGuiInputSource_NavKeyboard ) ) ;
bool clear_active_id = false ;
bool select_all = ( g . ActiveId ! = id ) & & ( ( flags & ImGuiInputTextFlags_AutoSelectAll ) ! = 0 | | user_nav_input_start ) & & ( ! is_multiline ) ;
if ( focus_requested | | user_clicked | | user_scrolled | | user_nav_input_start )
{
if ( g . ActiveId ! = id )
{
// Start edition
// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
// From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
const int prev_len_w = edit_state . CurLenW ;
const int init_buf_len = ( int ) strlen ( buf ) ;
edit_state . TextW . resize ( buf_size + 1 ) ; // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
edit_state . InitialText . resize ( init_buf_len + 1 ) ; // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
memcpy ( edit_state . InitialText . Data , buf , init_buf_len + 1 ) ;
const char * buf_end = NULL ;
edit_state . CurLenW = ImTextStrFromUtf8 ( edit_state . TextW . Data , buf_size , buf , NULL , & buf_end ) ;
edit_state . CurLenA = ( int ) ( buf_end - buf ) ; // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
edit_state . CursorAnimReset ( ) ;
// Preserve cursor position and undo/redo stack if we come back to same widget
// FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
const bool recycle_state = ( edit_state . ID = = id ) & & ( prev_len_w = = edit_state . CurLenW ) ;
if ( recycle_state )
{
// Recycle existing cursor/selection/undo stack but clamp position
// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
edit_state . CursorClamp ( ) ;
}
else
{
edit_state . ID = id ;
edit_state . ScrollX = 0.0f ;
stb_textedit_initialize_state ( & edit_state . StbState , ! is_multiline ) ;
if ( ! is_multiline & & focus_requested_by_code )
select_all = true ;
}
if ( flags & ImGuiInputTextFlags_AlwaysInsertMode )
edit_state . StbState . insert_mode = true ;
if ( ! is_multiline & & ( focus_requested_by_tab | | ( user_clicked & & io . KeyCtrl ) ) )
select_all = true ;
}
SetActiveID ( id , window ) ;
SetFocusID ( id , window ) ;
FocusWindow ( window ) ;
if ( ! is_multiline & & ! ( flags & ImGuiInputTextFlags_CallbackHistory ) )
g . ActiveIdAllowNavDirFlags | = ( ( 1 < < ImGuiDir_Up ) | ( 1 < < ImGuiDir_Down ) ) ;
}
else if ( io . MouseClicked [ 0 ] )
{
// Release focus when we click outside
clear_active_id = true ;
}
bool value_changed = false ;
bool enter_pressed = false ;
int backup_current_text_length = 0 ;
if ( g . ActiveId = = id )
{
if ( ! is_editable & & ! g . ActiveIdIsJustActivated )
{
// When read-only we always use the live data passed to the function
edit_state . TextW . resize ( buf_size + 1 ) ;
const char * buf_end = NULL ;
edit_state . CurLenW = ImTextStrFromUtf8 ( edit_state . TextW . Data , edit_state . TextW . Size , buf , NULL , & buf_end ) ;
edit_state . CurLenA = ( int ) ( buf_end - buf ) ;
edit_state . CursorClamp ( ) ;
}
backup_current_text_length = edit_state . CurLenA ;
edit_state . BufCapacityA = buf_size ;
edit_state . UserFlags = flags ;
edit_state . UserCallback = callback ;
edit_state . UserCallbackData = callback_user_data ;
// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
// Down the line we should have a cleaner library-wide concept of Selected vs Active.
g . ActiveIdAllowOverlap = ! io . MouseDown [ 0 ] ;
g . WantTextInputNextFrame = 1 ;
// Edit in progress
const float mouse_x = ( io . MousePos . x - frame_bb . Min . x - style . FramePadding . x ) + edit_state . ScrollX ;
const float mouse_y = ( is_multiline ? ( io . MousePos . y - draw_window - > DC . CursorPos . y - style . FramePadding . y ) : ( g . FontSize * 0.5f ) ) ;
const bool is_osx = io . ConfigMacOSXBehaviors ;
if ( select_all | | ( hovered & & ! is_osx & & io . MouseDoubleClicked [ 0 ] ) )
{
edit_state . SelectAll ( ) ;
edit_state . SelectedAllMouseLock = true ;
}
else if ( hovered & & is_osx & & io . MouseDoubleClicked [ 0 ] )
{
// Double-click select a word only, OS X style (by simulating keystrokes)
edit_state . OnKeyPressed ( STB_TEXTEDIT_K_WORDLEFT ) ;
edit_state . OnKeyPressed ( STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT ) ;
}
else if ( io . MouseClicked [ 0 ] & & ! edit_state . SelectedAllMouseLock )
{
if ( hovered )
{
stb_textedit_click ( & edit_state , & edit_state . StbState , mouse_x , mouse_y ) ;
edit_state . CursorAnimReset ( ) ;
}
}
else if ( io . MouseDown [ 0 ] & & ! edit_state . SelectedAllMouseLock & & ( io . MouseDelta . x ! = 0.0f | | io . MouseDelta . y ! = 0.0f ) )
{
stb_textedit_drag ( & edit_state , & edit_state . StbState , mouse_x , mouse_y ) ;
edit_state . CursorAnimReset ( ) ;
edit_state . CursorFollow = true ;
}
if ( edit_state . SelectedAllMouseLock & & ! io . MouseDown [ 0 ] )
edit_state . SelectedAllMouseLock = false ;
if ( io . InputCharacters [ 0 ] )
{
// Process text input (before we check for Return because using some IME will effectively send a Return?)
// We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
bool ignore_inputs = ( io . KeyCtrl & & ! io . KeyAlt ) | | ( is_osx & & io . KeySuper ) ;
if ( ! ignore_inputs & & is_editable & & ! user_nav_input_start )
for ( int n = 0 ; n < IM_ARRAYSIZE ( io . InputCharacters ) & & io . InputCharacters [ n ] ; n + + )
{
// Insert character if they pass filtering
unsigned int c = ( unsigned int ) io . InputCharacters [ n ] ;
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state . OnKeyPressed ( ( int ) c ) ;
}
// Consume characters
memset ( g . IO . InputCharacters , 0 , sizeof ( g . IO . InputCharacters ) ) ;
}
}
bool cancel_edit = false ;
if ( g . ActiveId = = id & & ! g . ActiveIdIsJustActivated & & ! clear_active_id )
{
// Handle key-presses
const int k_mask = ( io . KeyShift ? STB_TEXTEDIT_K_SHIFT : 0 ) ;
const bool is_osx = io . ConfigMacOSXBehaviors ;
const bool is_shortcut_key = ( is_osx ? ( io . KeySuper & & ! io . KeyCtrl ) : ( io . KeyCtrl & & ! io . KeySuper ) ) & & ! io . KeyAlt & & ! io . KeyShift ; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
const bool is_osx_shift_shortcut = is_osx & & io . KeySuper & & io . KeyShift & & ! io . KeyCtrl & & ! io . KeyAlt ;
const bool is_wordmove_key_down = is_osx ? io . KeyAlt : io . KeyCtrl ; // OS X style: Text editing cursor movement using Alt instead of Ctrl
const bool is_startend_key_down = is_osx & & io . KeySuper & & ! io . KeyCtrl & & ! io . KeyAlt ; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
const bool is_ctrl_key_only = io . KeyCtrl & & ! io . KeyShift & & ! io . KeyAlt & & ! io . KeySuper ;
const bool is_shift_key_only = io . KeyShift & & ! io . KeyCtrl & & ! io . KeyAlt & & ! io . KeySuper ;
const bool is_cut = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_X ) ) | | ( is_shift_key_only & & IsKeyPressedMap ( ImGuiKey_Delete ) ) ) & & is_editable & & ! is_password & & ( ! is_multiline | | edit_state . HasSelection ( ) ) ;
const bool is_copy = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_C ) ) | | ( is_ctrl_key_only & & IsKeyPressedMap ( ImGuiKey_Insert ) ) ) & & ! is_password & & ( ! is_multiline | | edit_state . HasSelection ( ) ) ;
const bool is_paste = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_V ) ) | | ( is_shift_key_only & & IsKeyPressedMap ( ImGuiKey_Insert ) ) ) & & is_editable ;
const bool is_undo = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_Z ) ) & & is_editable & & is_undoable ) ;
const bool is_redo = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_Y ) ) | | ( is_osx_shift_shortcut & & IsKeyPressedMap ( ImGuiKey_Z ) ) ) & & is_editable & & is_undoable ;
if ( IsKeyPressedMap ( ImGuiKey_LeftArrow ) ) { edit_state . OnKeyPressed ( ( is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT ) | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_RightArrow ) ) { edit_state . OnKeyPressed ( ( is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT ) | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_UpArrow ) & & is_multiline ) { if ( io . KeyCtrl ) SetWindowScrollY ( draw_window , ImMax ( draw_window - > Scroll . y - g . FontSize , 0.0f ) ) ; else edit_state . OnKeyPressed ( ( is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP ) | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_DownArrow ) & & is_multiline ) { if ( io . KeyCtrl ) SetWindowScrollY ( draw_window , ImMin ( draw_window - > Scroll . y + g . FontSize , GetScrollMaxY ( ) ) ) ; else edit_state . OnKeyPressed ( ( is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN ) | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Home ) ) { edit_state . OnKeyPressed ( io . KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_End ) ) { edit_state . OnKeyPressed ( io . KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Delete ) & & is_editable ) { edit_state . OnKeyPressed ( STB_TEXTEDIT_K_DELETE | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Backspace ) & & is_editable )
{
if ( ! edit_state . HasSelection ( ) )
{
if ( is_wordmove_key_down ) edit_state . OnKeyPressed ( STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT ) ;
else if ( is_osx & & io . KeySuper & & ! io . KeyAlt & & ! io . KeyCtrl ) edit_state . OnKeyPressed ( STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT ) ;
}
edit_state . OnKeyPressed ( STB_TEXTEDIT_K_BACKSPACE | k_mask ) ;
}
else if ( IsKeyPressedMap ( ImGuiKey_Enter ) )
{
bool ctrl_enter_for_new_line = ( flags & ImGuiInputTextFlags_CtrlEnterForNewLine ) ! = 0 ;
if ( ! is_multiline | | ( ctrl_enter_for_new_line & & ! io . KeyCtrl ) | | ( ! ctrl_enter_for_new_line & & io . KeyCtrl ) )
{
enter_pressed = clear_active_id = true ;
}
else if ( is_editable )
{
unsigned int c = ' \n ' ; // Insert new line
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state . OnKeyPressed ( ( int ) c ) ;
}
}
else if ( ( flags & ImGuiInputTextFlags_AllowTabInput ) & & IsKeyPressedMap ( ImGuiKey_Tab ) & & ! io . KeyCtrl & & ! io . KeyShift & & ! io . KeyAlt & & is_editable )
{
unsigned int c = ' \t ' ; // Insert TAB
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state . OnKeyPressed ( ( int ) c ) ;
}
else if ( IsKeyPressedMap ( ImGuiKey_Escape ) )
{
clear_active_id = cancel_edit = true ;
}
else if ( is_undo | | is_redo )
{
edit_state . OnKeyPressed ( is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO ) ;
edit_state . ClearSelection ( ) ;
}
else if ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_A ) )
{
edit_state . SelectAll ( ) ;
edit_state . CursorFollow = true ;
}
else if ( is_cut | | is_copy )
{
// Cut, Copy
if ( io . SetClipboardTextFn )
{
const int ib = edit_state . HasSelection ( ) ? ImMin ( edit_state . StbState . select_start , edit_state . StbState . select_end ) : 0 ;
const int ie = edit_state . HasSelection ( ) ? ImMax ( edit_state . StbState . select_start , edit_state . StbState . select_end ) : edit_state . CurLenW ;
edit_state . TempBuffer . resize ( ( ie - ib ) * 4 + 1 ) ;
ImTextStrToUtf8 ( edit_state . TempBuffer . Data , edit_state . TempBuffer . Size , edit_state . TextW . Data + ib , edit_state . TextW . Data + ie ) ;
SetClipboardText ( edit_state . TempBuffer . Data ) ;
}
if ( is_cut )
{
if ( ! edit_state . HasSelection ( ) )
edit_state . SelectAll ( ) ;
edit_state . CursorFollow = true ;
stb_textedit_cut ( & edit_state , & edit_state . StbState ) ;
}
}
else if ( is_paste )
{
if ( const char * clipboard = GetClipboardText ( ) )
{
// Filter pasted buffer
const int clipboard_len = ( int ) strlen ( clipboard ) ;
ImWchar * clipboard_filtered = ( ImWchar * ) ImGui : : MemAlloc ( ( clipboard_len + 1 ) * sizeof ( ImWchar ) ) ;
int clipboard_filtered_len = 0 ;
for ( const char * s = clipboard ; * s ; )
{
unsigned int c ;
s + = ImTextCharFromUtf8 ( & c , s , NULL ) ;
if ( c = = 0 )
break ;
if ( c > = 0x10000 | | ! InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
continue ;
clipboard_filtered [ clipboard_filtered_len + + ] = ( ImWchar ) c ;
}
clipboard_filtered [ clipboard_filtered_len ] = 0 ;
if ( clipboard_filtered_len > 0 ) // If everything was filtered, ignore the pasting operation
{
stb_textedit_paste ( & edit_state , & edit_state . StbState , clipboard_filtered , clipboard_filtered_len ) ;
edit_state . CursorFollow = true ;
}
ImGui : : MemFree ( clipboard_filtered ) ;
}
}
}
if ( g . ActiveId = = id )
{
const char * apply_new_text = NULL ;
int apply_new_text_length = 0 ;
if ( cancel_edit )
{
// Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
if ( is_editable & & strcmp ( buf , edit_state . InitialText . Data ) ! = 0 )
{
apply_new_text = edit_state . InitialText . Data ;
apply_new_text_length = edit_state . InitialText . Size - 1 ;
}
}
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
bool apply_edit_back_to_user_buffer = ! cancel_edit | | ( enter_pressed & & ( flags & ImGuiInputTextFlags_EnterReturnsTrue ) ! = 0 ) ;
if ( apply_edit_back_to_user_buffer )
{
// Apply new value immediately - copy modified buffer back
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
// FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
if ( is_editable )
{
edit_state . TempBuffer . resize ( edit_state . TextW . Size * 4 + 1 ) ;
ImTextStrToUtf8 ( edit_state . TempBuffer . Data , edit_state . TempBuffer . Size , edit_state . TextW . Data , NULL ) ;
}
// User callback
if ( ( flags & ( ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways ) ) ! = 0 )
{
IM_ASSERT ( callback ! = NULL ) ;
// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
ImGuiInputTextFlags event_flag = 0 ;
ImGuiKey event_key = ImGuiKey_COUNT ;
if ( ( flags & ImGuiInputTextFlags_CallbackCompletion ) ! = 0 & & IsKeyPressedMap ( ImGuiKey_Tab ) )
{
event_flag = ImGuiInputTextFlags_CallbackCompletion ;
event_key = ImGuiKey_Tab ;
}
else if ( ( flags & ImGuiInputTextFlags_CallbackHistory ) ! = 0 & & IsKeyPressedMap ( ImGuiKey_UpArrow ) )
{
event_flag = ImGuiInputTextFlags_CallbackHistory ;
event_key = ImGuiKey_UpArrow ;
}
else if ( ( flags & ImGuiInputTextFlags_CallbackHistory ) ! = 0 & & IsKeyPressedMap ( ImGuiKey_DownArrow ) )
{
event_flag = ImGuiInputTextFlags_CallbackHistory ;
event_key = ImGuiKey_DownArrow ;
}
else if ( flags & ImGuiInputTextFlags_CallbackAlways )
event_flag = ImGuiInputTextFlags_CallbackAlways ;
if ( event_flag )
{
ImGuiInputTextCallbackData callback_data ;
memset ( & callback_data , 0 , sizeof ( ImGuiInputTextCallbackData ) ) ;
callback_data . EventFlag = event_flag ;
callback_data . Flags = flags ;
callback_data . UserData = callback_user_data ;
callback_data . EventKey = event_key ;
callback_data . Buf = edit_state . TempBuffer . Data ;
callback_data . BufTextLen = edit_state . CurLenA ;
callback_data . BufSize = edit_state . BufCapacityA ;
callback_data . BufDirty = false ;
// We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
ImWchar * text = edit_state . TextW . Data ;
const int utf8_cursor_pos = callback_data . CursorPos = ImTextCountUtf8BytesFromStr ( text , text + edit_state . StbState . cursor ) ;
const int utf8_selection_start = callback_data . SelectionStart = ImTextCountUtf8BytesFromStr ( text , text + edit_state . StbState . select_start ) ;
const int utf8_selection_end = callback_data . SelectionEnd = ImTextCountUtf8BytesFromStr ( text , text + edit_state . StbState . select_end ) ;
// Call user code
callback ( & callback_data ) ;
// Read back what user may have modified
IM_ASSERT ( callback_data . Buf = = edit_state . TempBuffer . Data ) ; // Invalid to modify those fields
IM_ASSERT ( callback_data . BufSize = = edit_state . BufCapacityA ) ;
IM_ASSERT ( callback_data . Flags = = flags ) ;
if ( callback_data . CursorPos ! = utf8_cursor_pos ) { edit_state . StbState . cursor = ImTextCountCharsFromUtf8 ( callback_data . Buf , callback_data . Buf + callback_data . CursorPos ) ; edit_state . CursorFollow = true ; }
if ( callback_data . SelectionStart ! = utf8_selection_start ) { edit_state . StbState . select_start = ImTextCountCharsFromUtf8 ( callback_data . Buf , callback_data . Buf + callback_data . SelectionStart ) ; }
if ( callback_data . SelectionEnd ! = utf8_selection_end ) { edit_state . StbState . select_end = ImTextCountCharsFromUtf8 ( callback_data . Buf , callback_data . Buf + callback_data . SelectionEnd ) ; }
if ( callback_data . BufDirty )
{
IM_ASSERT ( callback_data . BufTextLen = = ( int ) strlen ( callback_data . Buf ) ) ; // You need to maintain BufTextLen if you change the text!
if ( callback_data . BufTextLen > backup_current_text_length & & is_resizable )
edit_state . TextW . resize ( edit_state . TextW . Size + ( callback_data . BufTextLen - backup_current_text_length ) ) ;
edit_state . CurLenW = ImTextStrFromUtf8 ( edit_state . TextW . Data , edit_state . TextW . Size , callback_data . Buf , NULL ) ;
edit_state . CurLenA = callback_data . BufTextLen ; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
edit_state . CursorAnimReset ( ) ;
}
}
}
// Will copy result string if modified
if ( is_editable & & strcmp ( edit_state . TempBuffer . Data , buf ) ! = 0 )
{
apply_new_text = edit_state . TempBuffer . Data ;
apply_new_text_length = edit_state . CurLenA ;
}
}
// Copy result to user buffer
if ( apply_new_text )
{
IM_ASSERT ( apply_new_text_length > = 0 ) ;
if ( backup_current_text_length ! = apply_new_text_length & & is_resizable )
{
ImGuiInputTextCallbackData callback_data ;
callback_data . EventFlag = ImGuiInputTextFlags_CallbackResize ;
callback_data . Flags = flags ;
callback_data . Buf = buf ;
callback_data . BufTextLen = apply_new_text_length ;
callback_data . BufSize = ImMax ( buf_size , apply_new_text_length + 1 ) ;
callback_data . UserData = callback_user_data ;
callback ( & callback_data ) ;
buf = callback_data . Buf ;
buf_size = callback_data . BufSize ;
apply_new_text_length = ImMin ( callback_data . BufTextLen , buf_size - 1 ) ;
IM_ASSERT ( apply_new_text_length < = buf_size ) ;
}
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
ImStrncpy ( buf , edit_state . TempBuffer . Data , ImMin ( apply_new_text_length + 1 , buf_size ) ) ;
value_changed = true ;
}
// Clear temporary user storage
edit_state . UserFlags = 0 ;
edit_state . UserCallback = NULL ;
edit_state . UserCallbackData = NULL ;
}
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
if ( clear_active_id & & g . ActiveId = = id )
ClearActiveID ( ) ;
// Render
// Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
const char * buf_display = ( g . ActiveId = = id & & is_editable ) ? edit_state . TempBuffer . Data : buf ; buf = NULL ;
// Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
// without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
// Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
const int buf_display_max_length = 2 * 1024 * 1024 ;
if ( ! is_multiline )
{
RenderNavHighlight ( frame_bb , id ) ;
RenderFrame ( frame_bb . Min , frame_bb . Max , GetColorU32 ( ImGuiCol_FrameBg ) , true , style . FrameRounding ) ;
}
const ImVec4 clip_rect ( frame_bb . Min . x , frame_bb . Min . y , frame_bb . Min . x + size . x , frame_bb . Min . y + size . y ) ; // Not using frame_bb.Max because we have adjusted size
ImVec2 render_pos = is_multiline ? draw_window - > DC . CursorPos : frame_bb . Min + style . FramePadding ;
ImVec2 text_size ( 0.f , 0.f ) ;
const bool is_currently_scrolling = ( edit_state . ID = = id & & is_multiline & & g . ActiveId = = draw_window - > GetIDNoKeepAlive ( " #SCROLLY " ) ) ;
if ( g . ActiveId = = id | | is_currently_scrolling )
{
edit_state . CursorAnim + = io . DeltaTime ;
// This is going to be messy. We need to:
// - Display the text (this alone can be more easily clipped)
// - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
// - Measure text height (for scrollbar)
// We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
// FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
const ImWchar * text_begin = edit_state . TextW . Data ;
ImVec2 cursor_offset , select_start_offset ;
{
// Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
const ImWchar * searches_input_ptr [ 2 ] ;
searches_input_ptr [ 0 ] = text_begin + edit_state . StbState . cursor ;
searches_input_ptr [ 1 ] = NULL ;
int searches_remaining = 1 ;
int searches_result_line_number [ 2 ] = { - 1 , - 999 } ;
if ( edit_state . StbState . select_start ! = edit_state . StbState . select_end )
{
searches_input_ptr [ 1 ] = text_begin + ImMin ( edit_state . StbState . select_start , edit_state . StbState . select_end ) ;
searches_result_line_number [ 1 ] = - 1 ;
searches_remaining + + ;
}
// Iterate all lines to find our line numbers
// In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
searches_remaining + = is_multiline ? 1 : 0 ;
int line_count = 0 ;
for ( const ImWchar * s = text_begin ; * s ! = 0 ; s + + )
if ( * s = = ' \n ' )
{
line_count + + ;
if ( searches_result_line_number [ 0 ] = = - 1 & & s > = searches_input_ptr [ 0 ] ) { searches_result_line_number [ 0 ] = line_count ; if ( - - searches_remaining < = 0 ) break ; }
if ( searches_result_line_number [ 1 ] = = - 1 & & s > = searches_input_ptr [ 1 ] ) { searches_result_line_number [ 1 ] = line_count ; if ( - - searches_remaining < = 0 ) break ; }
}
line_count + + ;
if ( searches_result_line_number [ 0 ] = = - 1 ) searches_result_line_number [ 0 ] = line_count ;
if ( searches_result_line_number [ 1 ] = = - 1 ) searches_result_line_number [ 1 ] = line_count ;
// Calculate 2d position by finding the beginning of the line and measuring distance
cursor_offset . x = InputTextCalcTextSizeW ( ImStrbolW ( searches_input_ptr [ 0 ] , text_begin ) , searches_input_ptr [ 0 ] ) . x ;
cursor_offset . y = searches_result_line_number [ 0 ] * g . FontSize ;
if ( searches_result_line_number [ 1 ] > = 0 )
{
select_start_offset . x = InputTextCalcTextSizeW ( ImStrbolW ( searches_input_ptr [ 1 ] , text_begin ) , searches_input_ptr [ 1 ] ) . x ;
select_start_offset . y = searches_result_line_number [ 1 ] * g . FontSize ;
}
// Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
if ( is_multiline )
text_size = ImVec2 ( size . x , line_count * g . FontSize ) ;
}
// Scroll
if ( edit_state . CursorFollow )
{
// Horizontal scroll in chunks of quarter width
if ( ! ( flags & ImGuiInputTextFlags_NoHorizontalScroll ) )
{
const float scroll_increment_x = size . x * 0.25f ;
if ( cursor_offset . x < edit_state . ScrollX )
edit_state . ScrollX = ( float ) ( int ) ImMax ( 0.0f , cursor_offset . x - scroll_increment_x ) ;
else if ( cursor_offset . x - size . x > = edit_state . ScrollX )
edit_state . ScrollX = ( float ) ( int ) ( cursor_offset . x - size . x + scroll_increment_x ) ;
}
else
{
edit_state . ScrollX = 0.0f ;
}
// Vertical scroll
if ( is_multiline )
{
float scroll_y = draw_window - > Scroll . y ;
if ( cursor_offset . y - g . FontSize < scroll_y )
scroll_y = ImMax ( 0.0f , cursor_offset . y - g . FontSize ) ;
else if ( cursor_offset . y - size . y > = scroll_y )
scroll_y = cursor_offset . y - size . y ;
draw_window - > DC . CursorPos . y + = ( draw_window - > Scroll . y - scroll_y ) ; // To avoid a frame of lag
draw_window - > Scroll . y = scroll_y ;
render_pos . y = draw_window - > DC . CursorPos . y ;
}
}
edit_state . CursorFollow = false ;
const ImVec2 render_scroll = ImVec2 ( edit_state . ScrollX , 0.0f ) ;
// Draw selection
if ( edit_state . StbState . select_start ! = edit_state . StbState . select_end )
{
const ImWchar * text_selected_begin = text_begin + ImMin ( edit_state . StbState . select_start , edit_state . StbState . select_end ) ;
const ImWchar * text_selected_end = text_begin + ImMax ( edit_state . StbState . select_start , edit_state . StbState . select_end ) ;
float bg_offy_up = is_multiline ? 0.0f : - 1.0f ; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
float bg_offy_dn = is_multiline ? 0.0f : 2.0f ;
ImU32 bg_color = GetColorU32 ( ImGuiCol_TextSelectedBg ) ;
ImVec2 rect_pos = render_pos + select_start_offset - render_scroll ;
for ( const ImWchar * p = text_selected_begin ; p < text_selected_end ; )
{
if ( rect_pos . y > clip_rect . w + g . FontSize )
break ;
if ( rect_pos . y < clip_rect . y )
{
while ( p < text_selected_end )
if ( * p + + = = ' \n ' )
break ;
}
else
{
ImVec2 rect_size = InputTextCalcTextSizeW ( p , text_selected_end , & p , NULL , true ) ;
if ( rect_size . x < = 0.0f ) rect_size . x = ( float ) ( int ) ( g . Font - > GetCharAdvance ( ( unsigned short ) ' ' ) * 0.50f ) ; // So we can see selected empty lines
ImRect rect ( rect_pos + ImVec2 ( 0.0f , bg_offy_up - g . FontSize ) , rect_pos + ImVec2 ( rect_size . x , bg_offy_dn ) ) ;
rect . ClipWith ( clip_rect ) ;
if ( rect . Overlaps ( clip_rect ) )
draw_window - > DrawList - > AddRectFilled ( rect . Min , rect . Max , bg_color ) ;
}
rect_pos . x = render_pos . x - render_scroll . x ;
rect_pos . y + = g . FontSize ;
}
}
const int buf_display_len = edit_state . CurLenA ;
if ( is_multiline | | buf_display_len < buf_display_max_length )
draw_window - > DrawList - > AddText ( g . Font , g . FontSize , render_pos - render_scroll , GetColorU32 ( ImGuiCol_Text ) , buf_display , buf_display + buf_display_len , 0.0f , is_multiline ? NULL : & clip_rect ) ;
// Draw blinking cursor
bool cursor_is_visible = ( ! g . IO . ConfigCursorBlink ) | | ( g . InputTextState . CursorAnim < = 0.0f ) | | ImFmod ( g . InputTextState . CursorAnim , 1.20f ) < = 0.80f ;
ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll ;
ImRect cursor_screen_rect ( cursor_screen_pos . x , cursor_screen_pos . y - g . FontSize + 0.5f , cursor_screen_pos . x + 1.0f , cursor_screen_pos . y - 1.5f ) ;
if ( cursor_is_visible & & cursor_screen_rect . Overlaps ( clip_rect ) )
draw_window - > DrawList - > AddLine ( cursor_screen_rect . Min , cursor_screen_rect . GetBL ( ) , GetColorU32 ( ImGuiCol_Text ) ) ;
// Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
if ( is_editable )
g . PlatformImePos = ImVec2 ( cursor_screen_pos . x - 1 , cursor_screen_pos . y - g . FontSize ) ;
}
else
{
// Render text only
const char * buf_end = NULL ;
if ( is_multiline )
text_size = ImVec2 ( size . x , InputTextCalcTextLenAndLineCount ( buf_display , & buf_end ) * g . FontSize ) ; // We don't need width
else
buf_end = buf_display + strlen ( buf_display ) ;
if ( is_multiline | | ( buf_end - buf_display ) < buf_display_max_length )
draw_window - > DrawList - > AddText ( g . Font , g . FontSize , render_pos , GetColorU32 ( ImGuiCol_Text ) , buf_display , buf_end , 0.0f , is_multiline ? NULL : & clip_rect ) ;
}
if ( is_multiline )
{
Dummy ( text_size + ImVec2 ( 0.0f , g . FontSize ) ) ; // Always add room to scroll an extra line
EndChildFrame ( ) ;
EndGroup ( ) ;
}
if ( is_password )
PopFont ( ) ;
// Log as text
if ( g . LogEnabled & & ! is_password )
LogRenderedText ( & render_pos , buf_display , NULL ) ;
if ( label_size . x > 0 )
RenderText ( ImVec2 ( frame_bb . Max . x + style . ItemInnerSpacing . x , frame_bb . Min . y + style . FramePadding . y ) , label ) ;
if ( value_changed )
MarkItemEdited ( id ) ;
if ( ( flags & ImGuiInputTextFlags_EnterReturnsTrue ) ! = 0 )
return enter_pressed ;
else
return value_changed ;
}
bool ImGui : : InputText ( const char * label , char * buf , size_t buf_size , ImGuiInputTextFlags flags , ImGuiInputTextCallback callback , void * user_data )
{
IM_ASSERT ( ! ( flags & ImGuiInputTextFlags_Multiline ) ) ; // call InputTextMultiline()
return InputTextEx ( label , buf , ( int ) buf_size , ImVec2 ( 0 , 0 ) , flags , callback , user_data ) ;
}
bool ImGui : : InputTextMultiline ( const char * label , char * buf , size_t buf_size , const ImVec2 & size , ImGuiInputTextFlags flags , ImGuiInputTextCallback callback , void * user_data )
{
return InputTextEx ( label , buf , ( int ) buf_size , size , flags | ImGuiInputTextFlags_Multiline , callback , user_data ) ;
}
// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
bool ImGui : : InputScalar ( const char * label , ImGuiDataType data_type , void * data_ptr , const void * step , const void * step_fast , const char * format , ImGuiInputTextFlags extra_flags )
{