@ -1,4 +1,4 @@
// dear imgui, v1.6 8
// dear imgui, v1.6 9 WIP
// (widgets code)
/*
@ -741,11 +741,9 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_no
return pressed ;
}
ImGuiID ImGui : : GetScrollbarID ( ImGui LayoutType direction )
ImGuiID ImGui : : GetScrollbarID ( ImGui Window* window , ImGuiAxis axis )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
return window - > GetID ( ( direction = = ImGuiLayoutType_Horizontal ) ? " #SCROLLX " : " #SCROLLY " ) ;
return window - > GetIDNoKeepAlive ( axis = = ImGuiAxis_X ? " #SCROLLX " : " #SCROLLY " ) ;
}
// Vertical/Horizontal scrollbar
@ -753,15 +751,16 @@ ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction)
// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
void ImGui : : Scrollbar ( ImGui LayoutType direction )
void ImGui : : Scrollbar ( ImGui Axis axis )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
const bool horizontal = ( direction = = ImGuiLayoutType_Horizontal ) ;
const bool horizontal = ( axis = = ImGuiAxis_X ) ;
const ImGuiStyle & style = g . Style ;
const ImGuiID id = GetScrollbarID ( direction ) ;
const ImGuiID id = GetScrollbarID ( window , axis ) ;
KeepAliveID ( id ) ;
// Render background
bool other_scrollbar = ( horizontal ? window - > ScrollbarY : window - > ScrollbarX ) ;
float other_scrollbar_size_w = other_scrollbar ? style . ScrollbarSize : 0.0f ;
@ -779,7 +778,7 @@ void ImGui::Scrollbar(ImGuiLayoutType direction)
// When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab)
float alpha = 1.0f ;
if ( ( direction = = ImGuiLayoutType_Vertical ) & & bb_height < g . FontSize + g . Style . FramePadding . y * 2.0f )
if ( ( axis = = ImGuiAxis_Y ) & & bb_height < g . FontSize + g . Style . FramePadding . y * 2.0f )
{
alpha = ImSaturate ( ( bb_height - g . FontSize ) / ( g . Style . FramePadding . y * 2.0f ) ) ;
if ( alpha < = 0.0f )
@ -2647,6 +2646,7 @@ int ImParseFormatPrecision(const char* fmt, int default_precision)
// FIXME: Facilitate using this in variety of other situations.
bool ImGui : : InputScalarAsWidgetReplacement ( const ImRect & bb , ImGuiID id , const char * label , ImGuiDataType data_type , void * data_ptr , const char * format )
{
IM_UNUSED ( id ) ;
ImGuiContext & g = * GImGui ;
// On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id.
@ -2668,7 +2668,7 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c
g . ScalarAsInputTextId = g . ActiveId ;
}
if ( value_changed )
return DataTypeApplyOpFromText ( data_buf , g . InputTextState . InitialText . Data , data_type , data_ptr , NULL ) ;
return DataTypeApplyOpFromText ( data_buf , g . InputTextState . InitialText A . Data , data_type , data_ptr , NULL ) ;
return false ;
}
@ -2679,7 +2679,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
ImGuiStyle & style = g . Style ;
IM_ASSERT ( data_type > = 0 & & data_type < ImGuiDataType_COUNT ) ;
if ( format = = NULL )
@ -2701,10 +2701,12 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
PushID ( label ) ;
PushItemWidth ( ImMax ( 1.0f , CalcItemWidth ( ) - ( button_size + style . ItemInnerSpacing . x ) * 2 ) ) ;
if ( InputText ( " " , buf , IM_ARRAYSIZE ( buf ) , flags ) ) // PushId(label) + "" gives us the expected ID from outside point of view
value_changed = DataTypeApplyOpFromText ( buf , g . InputTextState . InitialText . Data , data_type , data_ptr , format ) ;
value_changed = DataTypeApplyOpFromText ( buf , g . InputTextState . InitialText A . Data , data_type , data_ptr , format ) ;
PopItemWidth ( ) ;
// Step buttons
const ImVec2 backup_frame_padding = style . FramePadding ;
style . FramePadding . x = style . FramePadding . y ;
ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups ;
if ( flags & ImGuiInputTextFlags_ReadOnly )
button_flags | = ImGuiButtonFlags_Disabled ;
@ -2722,6 +2724,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
}
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
TextUnformatted ( label , FindRenderedTextEnd ( label ) ) ;
style . FramePadding = backup_frame_padding ;
PopID ( ) ;
EndGroup ( ) ;
@ -2729,7 +2732,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p
else
{
if ( InputText ( label , buf , IM_ARRAYSIZE ( buf ) , flags ) )
value_changed = DataTypeApplyOpFromText ( buf , g . InputTextState . InitialText . Data , data_type , data_ptr , format ) ;
value_changed = DataTypeApplyOpFromText ( buf , g . InputTextState . InitialText A . Data , data_type , data_ptr , format ) ;
}
return value_changed ;
@ -2926,7 +2929,7 @@ static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* t
}
// 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 Im Gui Stb
namespace Im Stb
{
static int STB_TEXTEDIT_STRINGLEN ( const STB_TEXTEDIT_STRING * obj ) { return obj - > CurLenW ; }
@ -3029,7 +3032,7 @@ static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const Im
void ImGuiInputTextState : : OnKeyPressed ( int key )
{
stb_textedit_key ( this , & Stb State , key ) ;
stb_textedit_key ( this , & Stb , key ) ;
CursorFollow = true ;
CursorAnimReset ( ) ;
}
@ -3073,10 +3076,10 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
ImGuiContext & g = * GImGui ;
ImGuiInputTextState * edit_state = & g . InputTextState ;
IM_ASSERT ( edit_state - > ID ! = 0 & & g . ActiveId = = edit_state - > ID ) ;
IM_ASSERT ( Buf = = edit_state - > Te mpBuffer . Data ) ;
IM_ASSERT ( Buf = = edit_state - > Te xtA . Data ) ;
int new_buf_size = BufTextLen + ImClamp ( new_text_len * 4 , 32 , ImMax ( 256 , new_text_len ) ) + 1 ;
edit_state - > Te mpBuffer . reserve ( new_buf_size + 1 ) ;
Buf = edit_state - > Te mpBuffer . Data ;
edit_state - > Te xtA . reserve ( new_buf_size + 1 ) ;
Buf = edit_state - > Te xtA . Data ;
BufSize = edit_state - > BufCapacityA = new_buf_size ;
}
@ -3156,7 +3159,8 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
// 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/cpp/imgui_stdlib.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)
// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally 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 ( ) ;
@ -3170,8 +3174,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
ImGuiIO & io = g . IO ;
const ImGuiStyle & style = g . Style ;
const bool RENDER_SELECTION_WHEN_INACTIVE = true ;
const bool is_multiline = ( flags & ImGuiInputTextFlags_Multiline ) ! = 0 ;
const bool is_ editable = ( flags & ImGuiInputTextFlags_ReadOnly ) = = 0 ;
const bool is_ readonly = ( 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 ;
@ -3233,58 +3238,68 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
// NB: we are only allowed to access 'edit_state' if we are the active widget.
ImGuiInputTextState & edit_state = g . InputTextState ;
ImGuiInputTextState * state = NULL ;
if ( g . InputTextState . ID = = id )
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 ) ) ;
const bool user_scroll_finish = is_multiline & & state ! = NULL & & g . ActiveId = = 0 & & g . ActiveIdPreviousFrame = = GetScrollbarID ( draw_window , ImGuiAxis_Y ) ;
const bool user_scroll_active = is_multiline & & state ! = NULL & & g . ActiveId = = GetScrollbarID ( draw_window , ImGuiAxis_Y ) ;
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 )
const bool init_make_active = ( focus_requested | | user_clicked | | user_scroll_finish | | user_nav_input_start ) ;
if ( init_make_active & & g . ActiveId ! = id )
{
if ( g . ActiveId ! = id )
// Access state even if we don't own it yet.
state = & g . InputTextState ;
state - > CursorAnimReset ( ) ;
// 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 buf_len = ( int ) strlen ( buf ) ;
state - > InitialTextA . resize ( buf_len + 1 ) ; // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
memcpy ( state - > InitialTextA . Data , buf , buf_len + 1 ) ;
// Start edition
const int prev_len_w = state - > CurLenW ;
const char * buf_end = NULL ;
state - > TextW . resize ( buf_size + 1 ) ; // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
state - > CurLenW = ImTextStrFromUtf8 ( state - > TextW . Data , buf_size , buf , NULL , & buf_end ) ;
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.
// 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 = ( state - > ID = = id ) & & ( prev_len_w = = state - > CurLenW ) ;
if ( recycle_state )
{
// 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 = 1 ;
if ( ! is_multiline & & ( focus_requested_by_tab | | ( user_clicked & & io . KeyCtrl ) ) )
// 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.
state - > CursorClamp ( ) ;
}
else
{
state - > ID = id ;
state - > ScrollX = 0.0f ;
stb_textedit_initialize_state ( & state - > Stb , ! is_multiline ) ;
if ( ! is_multiline & & focus_requested_by_code )
select_all = true ;
}
if ( flags & ImGuiInputTextFlags_AlwaysInsertMode )
state - > Stb . insert_mode = 1 ;
if ( ! is_multiline & & ( focus_requested_by_tab | | ( user_clicked & & io . KeyCtrl ) ) )
select_all = true ;
}
if ( init_make_active )
{
IM_ASSERT ( state & & state - > ID = = id ) ;
SetActiveID ( id , window ) ;
SetFocusID ( id , window ) ;
FocusWindow ( window ) ;
@ -3292,33 +3307,38 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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
// Release focus when we click outside
if ( ! init_make_active & & io . MouseClicked [ 0 ] )
clear_active_id = true ;
}
// We have an edge case if ActiveId was set through another widget (e.g. widget being swapped)
if ( g . ActiveId = = id & & state = = NULL )
ClearActiveID ( ) ;
bool value_changed = false ;
bool enter_pressed = false ;
int backup_current_text_length = 0 ;
// Process mouse inputs and character inputs
if ( g . ActiveId = = id )
{
if ( ! is_editable & & ! g . ActiveIdIsJustActivated )
IM_ASSERT ( state ! = NULL ) ;
if ( is_readonly & & ! 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 ( ) ;
state - > TextW . resize ( buf_size + 1 ) ;
state - > CurLenW = ImTextStrFromUtf8 ( state - > TextW . Data , state - > TextW . Size , buf , NULL , & buf_end ) ;
state - > CurLenA = ( int ) ( buf_end - buf ) ;
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 ;
backup_current_text_length = state- > CurLenA ;
state- > BufCapacityA = buf_size ;
state- > UserFlags = flags ;
state- > UserCallback = callback ;
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.
@ -3326,50 +3346,50 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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_x = ( io . MousePos . x - frame_bb . Min . x - style . FramePadding . x ) + 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 ;
state- > SelectAll ( ) ;
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 ) ;
state- > OnKeyPressed ( STB_TEXTEDIT_K_WORDLEFT ) ;
state- > OnKeyPressed ( STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT ) ;
}
else if ( io . MouseClicked [ 0 ] & & ! edit_state. SelectedAllMouseLock )
else if ( io . MouseClicked [ 0 ] & & ! state- > SelectedAllMouseLock )
{
if ( hovered )
{
stb_textedit_click ( & edit_state , & edit_state . StbState , mouse_x , mouse_y ) ;
edit_state. CursorAnimReset ( ) ;
stb_textedit_click ( state , & state - > Stb , mouse_x , mouse_y ) ;
state- > CursorAnimReset ( ) ;
}
}
else if ( io . MouseDown [ 0 ] & & ! edit_state. SelectedAllMouseLock & & ( io . MouseDelta . x ! = 0.0f | | io . MouseDelta . y ! = 0.0f ) )
else if ( io . MouseDown [ 0 ] & & ! 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 ;
stb_textedit_drag ( state , & state - > Stb , mouse_x , mouse_y ) ;
state- > CursorAnimReset ( ) ;
state- > CursorFollow = true ;
}
if ( edit_state. SelectedAllMouseLock & & ! io . MouseDown [ 0 ] )
edit_state. SelectedAllMouseLock = false ;
if ( state- > SelectedAllMouseLock & & ! io . MouseDown [ 0 ] )
state- > SelectedAllMouseLock = false ;
if ( io . InputQueueCharacters . Size > 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 )
if ( ! ignore_inputs & & ! is_readonly & & ! user_nav_input_start )
for ( int n = 0 ; n < io . InputQueueCharacters . Size ; n + + )
{
// Insert character if they pass filtering
unsigned int c = ( unsigned int ) io . InputQueueCharacters [ n ] ;
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state. OnKeyPressed ( ( int ) c ) ;
state- > OnKeyPressed ( ( int ) c ) ;
}
// Consume characters
@ -3377,10 +3397,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
}
// Process other shortcuts/key-presses
bool cancel_edit = false ;
if ( g . ActiveId = = id & & ! g . ActiveIdIsJustActivated & & ! clear_active_id )
{
// Handle key-presses
IM_ASSERT ( state ! = NULL ) ;
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
@ -3390,27 +3411,29 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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 )
const bool is_cut = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_X ) ) | | ( is_shift_key_only & & IsKeyPressedMap ( ImGuiKey_Delete ) ) ) & & ! is_readonly & & ! is_password & & ( ! is_multiline | | state- > HasSelection ( ) ) ;
const bool is_copy = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_C ) ) | | ( is_ctrl_key_only & & IsKeyPressedMap ( ImGuiKey_Insert ) ) ) & & ! is_password & & ( ! is_multiline | | state- > HasSelection ( ) ) ;
const bool is_paste = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_V ) ) | | ( is_shift_key_only & & IsKeyPressedMap ( ImGuiKey_Insert ) ) ) & & ! is_readonly ;
const bool is_undo = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_Z ) ) & & ! is_readonly & & is_undoable ) ;
const bool is_redo = ( ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_Y ) ) | | ( is_osx_shift_shortcut & & IsKeyPressedMap ( ImGuiKey_Z ) ) ) & & ! is_readonly & & is_undoable ;
if ( IsKeyPressedMap ( ImGuiKey_LeftArrow ) ) { 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 ) ) { 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 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 state- > OnKeyPressed ( ( is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN ) | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Home ) ) { state- > OnKeyPressed ( io . KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_End ) ) { state- > OnKeyPressed ( io . KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Delete ) & & ! is_readonly ) { state - > OnKeyPressed ( STB_TEXTEDIT_K_DELETE | k_mask ) ; }
else if ( IsKeyPressedMap ( ImGuiKey_Backspace ) & & ! is_readonly )
{
if ( ! edit_state. HasSelection ( ) )
if ( ! 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 ) ;
if ( is_wordmove_key_down )
state - > OnKeyPressed ( STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT ) ;
else if ( is_osx & & io . KeySuper & & ! io . KeyAlt & & ! io . KeyCtrl )
state - > OnKeyPressed ( STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT ) ;
}
edit_state . OnKeyPressed ( STB_TEXTEDIT_K_BACKSPACE | k_mask ) ;
state- > OnKeyPressed ( STB_TEXTEDIT_K_BACKSPACE | k_mask ) ;
}
else if ( IsKeyPressedMap ( ImGuiKey_Enter ) )
{
@ -3419,18 +3442,18 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
{
enter_pressed = clear_active_id = true ;
}
else if ( is_editable )
else if ( ! is_readonly )
{
unsigned int c = ' \n ' ; // Insert new line
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state. OnKeyPressed ( ( int ) c ) ;
state- > OnKeyPressed ( ( int ) c ) ;
}
}
else if ( ( flags & ImGuiInputTextFlags_AllowTabInput ) & & IsKeyPressedMap ( ImGuiKey_Tab ) & & ! io . KeyCtrl & & ! io . KeyShift & & ! io . KeyAlt & & is_editable )
else if ( ( flags & ImGuiInputTextFlags_AllowTabInput ) & & IsKeyPressedMap ( ImGuiKey_Tab ) & & ! io . KeyCtrl & & ! io . KeyShift & & ! io . KeyAlt & & ! is_readonly )
{
unsigned int c = ' \t ' ; // Insert TAB
if ( InputTextFilterCharacter ( & c , flags , callback , callback_user_data ) )
edit_state. OnKeyPressed ( ( int ) c ) ;
state- > OnKeyPressed ( ( int ) c ) ;
}
else if ( IsKeyPressedMap ( ImGuiKey_Escape ) )
{
@ -3438,31 +3461,33 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
else if ( is_undo | | is_redo )
{
edit_state. OnKeyPressed ( is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO ) ;
edit_state. ClearSelection ( ) ;
state- > OnKeyPressed ( is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO ) ;
state- > ClearSelection ( ) ;
}
else if ( is_shortcut_key & & IsKeyPressedMap ( ImGuiKey_A ) )
{
edit_state. SelectAll ( ) ;
edit_state. CursorFollow = true ;
state- > SelectAll ( ) ;
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 ) ;
const int ib = state - > HasSelection ( ) ? ImMin ( state - > Stb . select_start , state - > Stb . select_end ) : 0 ;
const int ie = state - > HasSelection ( ) ? ImMax ( state - > Stb . select_start , state - > Stb . select_end ) : state - > CurLenW ;
const int clipboard_data_len = ImTextCountUtf8BytesFromStr ( state - > TextW . Data + ib , state - > TextW . Data + ie ) + 1 ;
char * clipboard_data = ( char * ) MemAlloc ( clipboard_data_len * sizeof ( char ) ) ;
ImTextStrToUtf8 ( clipboard_data , clipboard_data_len , state - > TextW . Data + ib , state - > TextW . Data + ie ) ;
SetClipboardText ( clipboard_data ) ;
MemFree ( clipboard_data ) ;
}
if ( is_cut )
{
if ( ! edit_state. HasSelection ( ) )
edit_state. SelectAll ( ) ;
edit_state. CursorFollow = true ;
stb_textedit_cut ( & edit_state , & edit_state . StbState ) ;
if ( ! state- > HasSelection ( ) )
state- > SelectAll ( ) ;
state- > CursorFollow = true ;
stb_textedit_cut ( state , & state - > Stb ) ;
}
}
else if ( is_paste )
@ -3486,25 +3511,27 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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 ;
stb_textedit_paste ( state , & state - > Stb , clipboard_filtered , clipboard_filtered_len ) ;
state- > CursorFollow = true ;
}
MemFree ( clipboard_filtered ) ;
}
}
}
// Process callbacks and apply result back to user's buffer.
if ( g . ActiveId = = id )
{
IM_ASSERT ( state ! = NULL ) ;
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 )
if ( ! is_readonly & & strcmp ( buf , state - > InitialTextA . Data ) ! = 0 )
{
apply_new_text = edit_state. InitialText . Data ;
apply_new_text_length = edit_state. InitialText . Size - 1 ;
apply_new_text = state- > InitialTextA . Data ;
apply_new_text_length = state- > InitialTextA . Size - 1 ;
}
}
@ -3517,10 +3544,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// 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 )
if ( ! is_readonly )
{
edit_state. TempBuffer . resize ( edit_state . TextW . Size * 4 + 1 ) ;
ImTextStrToUtf8 ( edit_state. TempBuffer . Data , edit_state . TempBuffer . Size , edit_state . TextW . Data , NULL ) ;
state- > TextA . resize ( state - > TextW . Size * 4 + 1 ) ;
ImTextStrToUtf8 ( state- > TextA . Data , state - > TextA . Size , state - > TextW . Data , NULL ) ;
}
// User callback
@ -3558,44 +3585,44 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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 . Buf = state- > TextA . Data ;
callback_data . BufTextLen = state- > CurLenA ;
callback_data . BufSize = 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 ) ;
ImWchar * text = state- > TextW . Data ;
const int utf8_cursor_pos = callback_data . CursorPos = ImTextCountUtf8BytesFromStr ( text , text + state- > Stb . cursor ) ;
const int utf8_selection_start = callback_data . SelectionStart = ImTextCountUtf8BytesFromStr ( text , text + state- > Stb . select_start ) ;
const int utf8_selection_end = callback_data . SelectionEnd = ImTextCountUtf8BytesFromStr ( text , text + state- > Stb . 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 . Buf = = state- > TextA . Data ) ; // Invalid to modify those fields
IM_ASSERT ( callback_data . BufSize = = 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 . CursorPos ! = utf8_cursor_pos ) { state- > Stb . cursor = ImTextCountCharsFromUtf8 ( callback_data . Buf , callback_data . Buf + callback_data . CursorPos ) ; state- > CursorFollow = true ; }
if ( callback_data . SelectionStart ! = utf8_selection_start ) { state- > Stb . select_start = ImTextCountCharsFromUtf8 ( callback_data . Buf , callback_data . Buf + callback_data . SelectionStart ) ; }
if ( callback_data . SelectionEnd ! = utf8_selection_end ) { state- > Stb . 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 ( ) ;
state- > TextW . resize ( state - > TextW . Size + ( callback_data . BufTextLen - backup_current_text_length ) ) ;
state- > CurLenW = ImTextStrFromUtf8 ( state - > TextW . Data , state - > TextW . Size , callback_data . Buf , NULL ) ;
state- > CurLenA = callback_data . BufTextLen ; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
state- > CursorAnimReset ( ) ;
}
}
}
// Will copy result string if modified
if ( is_editable & & strcmp ( edit_state . TempBuffer . Data , buf ) ! = 0 )
if ( ! is_readonly & & strcmp ( state - > TextA . Data , buf ) ! = 0 )
{
apply_new_text = edit_state. TempBuffer . Data ;
apply_new_text_length = edit_state. CurLenA ;
apply_new_text = state- > TextA . Data ;
apply_new_text_length = state- > CurLenA ;
}
}
@ -3625,24 +3652,16 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
// Clear temporary user storage
edit_state. UserFlags = 0 ;
edit_state. UserCallback = NULL ;
edit_state. UserCallbackData = NULL ;
state- > UserFlags = 0 ;
state- > UserCallback = NULL ;
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 ;
// Render frame
if ( ! is_multiline )
{
RenderNavHighlight ( frame_bb , id ) ;
@ -3650,33 +3669,51 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
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 ;
ImVec2 draw_pos = is_multiline ? draw_window - > DC . CursorPos : frame_bb . Min + style . FramePadding ;
ImVec2 text_size ( 0.0f , 0.0f ) ;
// 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 ;
// Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on.
const char * buf_display = ( state ! = NULL & & ! is_readonly ) ? state - > TextA . Data : buf ;
IM_ASSERT ( buf_display ) ;
buf = NULL ;
// Render text. We currently only render selection when the widget is active or while scrolling.
// FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
const bool render_cursor = ( g . ActiveId = = id ) | | user_scroll_active ;
const bool render_selection = state & & state - > HasSelection ( ) & & ( RENDER_SELECTION_WHEN_INACTIVE | | render_cursor ) ;
if ( render_cursor | | render_selection )
{
// Render text (with cursor and selection)
// 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 ;
IM_ASSERT ( state ! = NULL ) ;
const ImWchar * text_begin = 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 )
// Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions.
const ImWchar * searches_input_ptr [ 2 ] = { NULL , NULL } ;
int searches_result_line_no [ 2 ] = { - 1000 , - 1000 } ;
int searches_remaining = 0 ;
if ( render_cursor )
{
searches_input_ptr [ 0 ] = text_begin + state - > Stb . cursor ;
searches_result_line_no [ 0 ] = - 1 ;
searches_remaining + + ;
}
if ( render_selection )
{
searches_input_ptr [ 1 ] = text_begin + ImMin ( edit_state . StbState . select_start , edit_state . StbState . select_end ) ;
searches_result_line_number [ 1 ] = - 1 ;
searches_input_ptr [ 1 ] = text_begin + ImMin ( state- > Stb . select_start , state - > Stb . select_end ) ;
searches_result_line_n o [ 1 ] = - 1 ;
searches_remaining + + ;
}
@ -3689,20 +3726,22 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
if ( * s = = ' \n ' )
{
line_count + + ;
if ( searches_result_line_n umber [ 0 ] = = - 1 & & s > = searches_input_ptr [ 0 ] ) { searches_result_line_n umber [ 0 ] = line_count ; if ( - - searches_remaining < = 0 ) break ; }
if ( searches_result_line_n umber [ 1 ] = = - 1 & & s > = searches_input_ptr [ 1 ] ) { searches_result_line_n umber [ 1 ] = line_count ; if ( - - searches_remaining < = 0 ) break ; }
if ( searches_result_line_n o [ 0 ] = = - 1 & & s > = searches_input_ptr [ 0 ] ) { searches_result_line_n o [ 0 ] = line_count ; if ( - - searches_remaining < = 0 ) break ; }
if ( searches_result_line_n o [ 1 ] = = - 1 & & s > = searches_input_ptr [ 1 ] ) { searches_result_line_n o [ 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 ;
if ( searches_result_line_no [ 0 ] = = - 1 )
searches_result_line_no [ 0 ] = line_count ;
if ( searches_result_line_no [ 1 ] = = - 1 )
searches_result_line_no [ 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_n umber [ 0 ] * g . FontSize ;
if ( searches_result_line_n umber [ 1 ] > = 0 )
cursor_offset . y = searches_result_line_n o [ 0 ] * g . FontSize ;
if ( searches_result_line_n o [ 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_n umber [ 1 ] * g . FontSize ;
select_start_offset . y = searches_result_line_n o [ 1 ] * g . FontSize ;
}
// Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
@ -3711,20 +3750,20 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
}
// Scroll
if ( edit_state. CursorFollow )
if ( render_cursor & & 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 ) ;
if ( cursor_offset . x < state- > ScrollX )
state- > ScrollX = ( float ) ( int ) ImMax ( 0.0f , cursor_offset . x - scroll_increment_x ) ;
else if ( cursor_offset . x - size . x > = state- > ScrollX )
state- > ScrollX = ( float ) ( int ) ( cursor_offset . x - size . x + scroll_increment_x ) ;
}
else
{
edit_state. ScrollX = 0.0f ;
state- > ScrollX = 0.0f ;
}
// Vertical scroll
@ -3735,24 +3774,25 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
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 - > DC . CursorPos . y + = ( draw_window - > Scroll . y - scroll_y ) ; // Manipulate cursor pos immediately avoid a frame of lag
draw_window - > Scroll . y = scroll_y ;
ren de r_pos. y = draw_window - > DC . CursorPos . y ;
draw _pos. y = draw_window - > DC . CursorPos . y ;
}
state - > CursorFollow = false ;
}
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 ImVec2 draw_scroll = ImVec2 ( state - > ScrollX , 0.0f ) ;
if ( render_selection )
{
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 ) ;
const ImWchar * text_selected_begin = text_begin + ImMin ( state- > Stb . select_start , state - > Stb . select_end ) ;
const ImWchar * text_selected_end = text_begin + ImMax ( state- > Stb . select_start , state - > Stb . select_end ) ;
ImU32 bg_color = GetColorU32 ( ImGuiCol_TextSelectedBg , render_cursor ? 1.0f : 0.6f ) ; // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
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 ;
ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll ;
for ( const ImWchar * p = text_selected_begin ; p < text_selected_end ; )
{
if ( rect_pos . y > clip_rect . w + g . FontSize )
@ -3774,39 +3814,44 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
if ( rect . Overlaps ( clip_rect ) )
draw_window - > DrawList - > AddRectFilled ( rect . Min , rect . Max , bg_color ) ;
}
rect_pos . x = ren de r_pos. x - ren de r_scroll. x ;
rect_pos . x = draw _pos. x - draw _scroll. x ;
rect_pos . y + = g . FontSize ;
}
}
const int buf_display_len = edit_state . CurLenA ;
// We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
const int buf_display_len = state - > CurLenA ;
if ( is_multiline | | buf_display_len < buf_display_max_length )
draw_window - > DrawList - > AddText ( g . Font , g . FontSize , ren de r_pos - ren de r_scroll, GetColorU32 ( ImGuiCol_Text ) , buf_display , buf_display + buf_display_len , 0.0f , is_multiline ? NULL : & clip_rect ) ;
draw_window - > DrawList - > AddText ( g . Font , g . FontSize , draw _pos - draw _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 . ConfigInputTextCursorBlink ) | | ( 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 )
if ( render_cursor )
{
g . PlatformImePos = ImVec2 ( cursor_screen_pos . x - 1 , cursor_screen_pos . y - g . FontSize ) ;
g . PlatformImePosViewport = window - > Viewport ;
state - > CursorAnim + = io . DeltaTime ;
bool cursor_is_visible = ( ! g . IO . ConfigInputTextCursorBlink ) | | ( state - > CursorAnim < = 0.0f ) | | ImFmod ( state - > CursorAnim , 1.20f ) < = 0.80f ;
ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_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_readonly )
{
g . PlatformImePos = ImVec2 ( cursor_screen_pos . x - 1 , cursor_screen_pos . y - g . FontSize ) ;
g . PlatformImePosViewport = window - > Viewport ;
}
}
}
else
{
// Render text only
// Render text only (no selection, no cursor)
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 , ren de r_pos, GetColorU32 ( ImGuiCol_Text ) , buf_display , buf_end , 0.0f , is_multiline ? NULL : & clip_rect ) ;
draw_window - > DrawList - > AddText ( g . Font , g . FontSize , draw _pos, GetColorU32 ( ImGuiCol_Text ) , buf_display , buf_end , 0.0f , is_multiline ? NULL : & clip_rect ) ;
}
if ( is_multiline )
@ -3821,7 +3866,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
// Log as text
if ( g . LogEnabled & & ! is_password )
LogRenderedText ( & ren de r_pos, buf_display , NULL ) ;
LogRenderedText ( & draw _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 ) ;
@ -5896,6 +5941,20 @@ static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
return ( b - > Index - a - > Index ) ;
}
static ImGuiTabBar * GetTabBarFromTabBarRef ( const ImGuiTabBarRef & ref )
{
ImGuiContext & g = * GImGui ;
return ref . Ptr ? ref . Ptr : g . TabBars . GetByIndex ( ref . IndexInMainPool ) ;
}
static ImGuiTabBarRef GetTabBarRefFromTabBar ( ImGuiTabBar * tab_bar )
{
ImGuiContext & g = * GImGui ;
if ( g . TabBars . Contains ( tab_bar ) )
return ImGuiTabBarRef ( g . TabBars . GetIndex ( tab_bar ) ) ;
return ImGuiTabBarRef ( tab_bar ) ;
}
bool ImGui : : BeginTabBar ( const char * str_id , ImGuiTabBarFlags flags )
{
ImGuiContext & g = * GImGui ;
@ -5920,7 +5979,10 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG
if ( ( flags & ImGuiTabBarFlags_DockNode ) = = 0 )
window - > IDStack . push_back ( tab_bar - > ID ) ;
g . CurrentTabBar . push_back ( tab_bar ) ;
// Add to stack
g . CurrentTabBarStack . push_back ( GetTabBarRefFromTabBar ( tab_bar ) ) ;
g . CurrentTabBar = tab_bar ;
if ( tab_bar - > CurrFrameVisible = = g . FrameCount )
{
//IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
@ -5973,8 +6035,12 @@ void ImGui::EndTabBar()
if ( window - > SkipItems )
return ;
IM_ASSERT ( ! g . CurrentTabBar . empty ( ) ) ; // Mismatched BeginTabBar/EndTabBar
ImGuiTabBar * tab_bar = g . CurrentTabBar . back ( ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar ;
if ( tab_bar = = NULL )
{
IM_ASSERT ( tab_bar ! = NULL & & " Mismatched BeginTabBar()/EndTabBar()! " ) ;
return ; // FIXME-ERRORHANDLING
}
if ( tab_bar - > WantLayout )
TabBarLayout ( tab_bar ) ;
@ -5987,7 +6053,9 @@ void ImGui::EndTabBar()
if ( ( tab_bar - > Flags & ImGuiTabBarFlags_DockNode ) = = 0 )
PopID ( ) ;
g . CurrentTabBar . pop_back ( ) ;
g . CurrentTabBarStack . pop_back ( ) ;
g . CurrentTabBar = g . CurrentTabBarStack . empty ( ) ? NULL : GetTabBarFromTabBarRef ( g . CurrentTabBarStack . back ( ) ) ;
}
// This is called only once a frame before by the first call to ItemTab()
@ -6201,7 +6269,7 @@ void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGu
{
ImGuiContext & g = * GImGui ;
IM_ASSERT ( TabBarFindTabByID ( tab_bar , window - > ID ) = = NULL ) ;
IM_ASSERT ( g . CurrentTabBar . empty ( ) ) ; // Can't work while the tab bar is active as our tab doesn't have an X offset yet
IM_ASSERT ( g . CurrentTabBar = = NULL ) ; // Can't work while the tab bar is active as our tab doesn't have an X offset yet
ImGuiTabItem new_tab ;
new_tab . ID = window - > ID ;
@ -6376,8 +6444,12 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f
if ( g . CurrentWindow - > SkipItems )
return false ;
IM_ASSERT ( g . CurrentTabBar . Size > 0 & & " Needs to be called between BeginTabBar() and EndTabBar()! " ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar . back ( ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar ;
if ( tab_bar = = NULL )
{
IM_ASSERT ( tab_bar & & " Needs to be called between BeginTabBar() and EndTabBar()! " ) ;
return false ; // FIXME-ERRORHANDLING
}
bool ret = TabItemEx ( tab_bar , label , p_open , flags , NULL ) ;
if ( ret & & ! ( flags & ImGuiTabItemFlags_NoPushId ) )
{
@ -6393,9 +6465,13 @@ void ImGui::EndTabItem()
if ( g . CurrentWindow - > SkipItems )
return ;
IM_ASSERT ( g . CurrentTabBar . Size > 0 & & " Needs to be called between BeginTabBar() and EndTabBar()! " ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar . back ( ) ;
IM_ASSERT ( tab_bar - > LastTabItemIdx > = 0 & & " Needs to be called between BeginTabItem() and EndTabItem() " ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar ;
if ( tab_bar = = NULL )
{
IM_ASSERT ( tab_bar ! = NULL & & " Needs to be called between BeginTabBar() and EndTabBar()! " ) ;
return ;
}
IM_ASSERT ( tab_bar - > LastTabItemIdx > = 0 ) ;
ImGuiTabItem * tab = & tab_bar - > Tabs [ tab_bar - > LastTabItemIdx ] ;
if ( ! ( tab - > Flags & ImGuiTabItemFlags_NoPushId ) )
g . CurrentWindow - > IDStack . pop_back ( ) ;
@ -6650,10 +6726,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
void ImGui : : SetTabItemClosed ( const char * label )
{
ImGuiContext & g = * GImGui ;
bool is_within_manual_tab_bar = ( g . CurrentTabBar . Size > 0 ) & & ! ( g . CurrentTabBar . back ( ) - > Flags & ImGuiTabBarFlags_DockNode ) ;
bool is_within_manual_tab_bar = g . CurrentTabBar & & ! ( g . CurrentTabBar - > Flags & ImGuiTabBarFlags_DockNode ) ;
if ( is_within_manual_tab_bar )
{
ImGuiTabBar * tab_bar = g . CurrentTabBar .back ( ) ;
ImGuiTabBar * tab_bar = g . CurrentTabBar ;
IM_ASSERT ( tab_bar - > WantLayout ) ; // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
ImGuiID tab_id = TabBarCalcTabID ( tab_bar , label ) ;
TabBarRemoveTab ( tab_bar , tab_id ) ;