2018-08-28 19:59:14 +00:00
// dear imgui, v1.64 WIP
// (widgets code)
# if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
# define _CRT_SECURE_NO_WARNINGS
# endif
# include "imgui.h"
# ifndef IMGUI_DEFINE_MATH_OPERATORS
# define IMGUI_DEFINE_MATH_OPERATORS
# endif
# include "imgui_internal.h"
2018-08-29 13:15:36 +00:00
# include <ctype.h> // toupper, isprint
// Visual Studio warnings
# ifdef _MSC_VER
# pragma warning (disable: 4127) // condition expression is constant
# pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
# endif
// Clang/GCC warnings with -Weverything
# ifdef __clang__
# pragma clang diagnostic ignored "-Wformat-nonliteral" // warning : format string is not a string literal // passing non-literal to vsnformat(). yes, user passing incorrect format strings can crash the code.
# pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
# elif defined(__GNUC__)
# pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
# if __GNUC__ >= 8
# pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
# endif
# endif
2018-08-28 19:59:14 +00:00
//-------------------------------------------------------------------------
// Forward Declarations
//-------------------------------------------------------------------------
2018-08-30 13:09:33 +00:00
// For InputTextEx()
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 ) ;
2018-08-28 19:59:14 +00:00
//-------------------------------------------------------------------------
2018-08-29 13:15:36 +00:00
// SHARED UTILITIES
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Text
// - TextUnformatted()
// - Text()
// - TextV()
// - TextColored()
// - TextColoredV()
// - TextDisabled()
// - TextDisabledV()
// - TextWrapped()
// - TextWrappedV()
// - LabelText()
// - LabelTextV()
// - BulletText()
// - BulletTextV()
//-------------------------------------------------------------------------
2018-08-30 12:38:44 +00:00
void ImGui : : TextUnformatted ( const char * text , const char * text_end )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
IM_ASSERT ( text ! = NULL ) ;
const char * text_begin = text ;
if ( text_end = = NULL )
text_end = text + strlen ( text ) ; // FIXME-OPT
const ImVec2 text_pos ( window - > DC . CursorPos . x , window - > DC . CursorPos . y + window - > DC . CurrentLineTextBaseOffset ) ;
const float wrap_pos_x = window - > DC . TextWrapPos ;
const bool wrap_enabled = wrap_pos_x > = 0.0f ;
if ( text_end - text > 2000 & & ! wrap_enabled )
{
// Long text!
// Perform manual coarse clipping to optimize for long multi-line text
// From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
// We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
const char * line = text ;
const float line_height = GetTextLineHeight ( ) ;
const ImRect clip_rect = window - > ClipRect ;
ImVec2 text_size ( 0 , 0 ) ;
if ( text_pos . y < = clip_rect . Max . y )
{
ImVec2 pos = text_pos ;
// Lines to skip (can't skip when logging text)
if ( ! g . LogEnabled )
{
int lines_skippable = ( int ) ( ( clip_rect . Min . y - text_pos . y ) / line_height ) ;
if ( lines_skippable > 0 )
{
int lines_skipped = 0 ;
while ( line < text_end & & lines_skipped < lines_skippable )
{
const char * line_end = strchr ( line , ' \n ' ) ;
if ( ! line_end )
line_end = text_end ;
line = line_end + 1 ;
lines_skipped + + ;
}
pos . y + = lines_skipped * line_height ;
}
}
// Lines to render
if ( line < text_end )
{
ImRect line_rect ( pos , pos + ImVec2 ( FLT_MAX , line_height ) ) ;
while ( line < text_end )
{
const char * line_end = strchr ( line , ' \n ' ) ;
if ( IsClippedEx ( line_rect , 0 , false ) )
break ;
const ImVec2 line_size = CalcTextSize ( line , line_end , false ) ;
text_size . x = ImMax ( text_size . x , line_size . x ) ;
RenderText ( pos , line , line_end , false ) ;
if ( ! line_end )
line_end = text_end ;
line = line_end + 1 ;
line_rect . Min . y + = line_height ;
line_rect . Max . y + = line_height ;
pos . y + = line_height ;
}
// Count remaining lines
int lines_skipped = 0 ;
while ( line < text_end )
{
const char * line_end = strchr ( line , ' \n ' ) ;
if ( ! line_end )
line_end = text_end ;
line = line_end + 1 ;
lines_skipped + + ;
}
pos . y + = lines_skipped * line_height ;
}
text_size . y + = ( pos - text_pos ) . y ;
}
ImRect bb ( text_pos , text_pos + text_size ) ;
ItemSize ( bb ) ;
ItemAdd ( bb , 0 ) ;
}
else
{
const float wrap_width = wrap_enabled ? CalcWrapWidthForPos ( window - > DC . CursorPos , wrap_pos_x ) : 0.0f ;
const ImVec2 text_size = CalcTextSize ( text_begin , text_end , false , wrap_width ) ;
// Account of baseline offset
ImRect bb ( text_pos , text_pos + text_size ) ;
ItemSize ( text_size ) ;
if ( ! ItemAdd ( bb , 0 ) )
return ;
// Render (we don't hide text after ## in this end-user function)
RenderTextWrapped ( bb . Min , text_begin , text_end , wrap_width ) ;
}
}
void ImGui : : Text ( const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
TextV ( fmt , args ) ;
va_end ( args ) ;
}
void ImGui : : TextV ( const char * fmt , va_list args )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const char * text_end = g . TempBuffer + ImFormatStringV ( g . TempBuffer , IM_ARRAYSIZE ( g . TempBuffer ) , fmt , args ) ;
TextUnformatted ( g . TempBuffer , text_end ) ;
}
void ImGui : : TextColored ( const ImVec4 & col , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
TextColoredV ( col , fmt , args ) ;
va_end ( args ) ;
}
void ImGui : : TextColoredV ( const ImVec4 & col , const char * fmt , va_list args )
{
PushStyleColor ( ImGuiCol_Text , col ) ;
TextV ( fmt , args ) ;
PopStyleColor ( ) ;
}
void ImGui : : TextDisabled ( const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
TextDisabledV ( fmt , args ) ;
va_end ( args ) ;
}
void ImGui : : TextDisabledV ( const char * fmt , va_list args )
{
PushStyleColor ( ImGuiCol_Text , GImGui - > Style . Colors [ ImGuiCol_TextDisabled ] ) ;
TextV ( fmt , args ) ;
PopStyleColor ( ) ;
}
void ImGui : : TextWrapped ( const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
TextWrappedV ( fmt , args ) ;
va_end ( args ) ;
}
void ImGui : : TextWrappedV ( const char * fmt , va_list args )
{
bool need_wrap = ( GImGui - > CurrentWindow - > DC . TextWrapPos < 0.0f ) ; // Keep existing wrap position is one ia already set
if ( need_wrap ) PushTextWrapPos ( 0.0f ) ;
TextV ( fmt , args ) ;
if ( need_wrap ) PopTextWrapPos ( ) ;
}
void ImGui : : LabelText ( const char * label , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
LabelTextV ( label , fmt , args ) ;
va_end ( args ) ;
}
// Add a label+text combo aligned to other label+value widgets
void ImGui : : LabelTextV ( const char * label , const char * fmt , va_list args )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const float w = CalcItemWidth ( ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
const ImRect value_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( w , label_size . y + style . FramePadding . y * 2 ) ) ;
const ImRect total_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( w + ( label_size . x > 0.0f ? style . ItemInnerSpacing . x : 0.0f ) , style . FramePadding . y * 2 ) + label_size ) ;
ItemSize ( total_bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( total_bb , 0 ) )
return ;
// Render
const char * value_text_begin = & g . TempBuffer [ 0 ] ;
const char * value_text_end = value_text_begin + ImFormatStringV ( g . TempBuffer , IM_ARRAYSIZE ( g . TempBuffer ) , fmt , args ) ;
RenderTextClipped ( value_bb . Min , value_bb . Max , value_text_begin , value_text_end , NULL , ImVec2 ( 0.0f , 0.5f ) ) ;
if ( label_size . x > 0.0f )
RenderText ( ImVec2 ( value_bb . Max . x + style . ItemInnerSpacing . x , value_bb . Min . y + style . FramePadding . y ) , label ) ;
}
void ImGui : : BulletText ( const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
BulletTextV ( fmt , args ) ;
va_end ( args ) ;
}
// Text with a little bullet aligned to the typical tree node.
void ImGui : : BulletTextV ( const char * fmt , va_list args )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const char * text_begin = g . TempBuffer ;
const char * text_end = text_begin + ImFormatStringV ( g . TempBuffer , IM_ARRAYSIZE ( g . TempBuffer ) , fmt , args ) ;
const ImVec2 label_size = CalcTextSize ( text_begin , text_end , false ) ;
const float text_base_offset_y = ImMax ( 0.0f , window - > DC . CurrentLineTextBaseOffset ) ; // Latch before ItemSize changes it
const float line_height = ImMax ( ImMin ( window - > DC . CurrentLineSize . y , g . FontSize + g . Style . FramePadding . y * 2 ) , g . FontSize ) ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( g . FontSize + ( label_size . x > 0.0f ? ( label_size . x + style . FramePadding . x * 2 ) : 0.0f ) , ImMax ( line_height , label_size . y ) ) ) ; // Empty text doesn't add padding
ItemSize ( bb ) ;
if ( ! ItemAdd ( bb , 0 ) )
return ;
// Render
RenderBullet ( bb . Min + ImVec2 ( style . FramePadding . x + g . FontSize * 0.5f , line_height * 0.5f ) ) ;
RenderText ( bb . Min + ImVec2 ( g . FontSize + style . FramePadding . x * 2 , text_base_offset_y ) , text_begin , text_end , false ) ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Main
// - ButtonBehavior() [Internal]
// - Button()
// - SmallButton()
// - InvisibleButton()
// - ArrowButton()
// - CloseButton() [Internal]
// - CollapseButton() [Internal]
// - Image()
// - ImageButton()
// - Checkbox()
// - CheckboxFlags()
// - RadioButton()
// - ProgressBar()
// - Bullet()
//-------------------------------------------------------------------------
2018-08-30 12:41:55 +00:00
bool ImGui : : ButtonBehavior ( const ImRect & bb , ImGuiID id , bool * out_hovered , bool * out_held , ImGuiButtonFlags flags )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( flags & ImGuiButtonFlags_Disabled )
{
if ( out_hovered ) * out_hovered = false ;
if ( out_held ) * out_held = false ;
if ( g . ActiveId = = id ) ClearActiveID ( ) ;
return false ;
}
// Default behavior requires click+release on same spot
if ( ( flags & ( ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick ) ) = = 0 )
flags | = ImGuiButtonFlags_PressedOnClickRelease ;
ImGuiWindow * backup_hovered_window = g . HoveredWindow ;
if ( ( flags & ImGuiButtonFlags_FlattenChildren ) & & g . HoveredRootWindow = = window )
g . HoveredWindow = window ;
bool pressed = false ;
bool hovered = ItemHoverable ( bb , id ) ;
// Drag source doesn't report as hovered
if ( hovered & & g . DragDropActive & & g . DragDropPayload . SourceId = = id & & ! ( g . DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover ) )
hovered = false ;
// Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
if ( g . DragDropActive & & ( flags & ImGuiButtonFlags_PressedOnDragDropHold ) & & ! ( g . DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers ) )
if ( IsItemHovered ( ImGuiHoveredFlags_AllowWhenBlockedByActiveItem ) )
{
hovered = true ;
SetHoveredID ( id ) ;
if ( CalcTypematicPressedRepeatAmount ( g . HoveredIdTimer + 0.0001f , g . HoveredIdTimer + 0.0001f - g . IO . DeltaTime , 0.01f , 0.70f ) ) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
{
pressed = true ;
FocusWindow ( window ) ;
}
}
if ( ( flags & ImGuiButtonFlags_FlattenChildren ) & & g . HoveredRootWindow = = window )
g . HoveredWindow = backup_hovered_window ;
// AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
if ( hovered & & ( flags & ImGuiButtonFlags_AllowItemOverlap ) & & ( g . HoveredIdPreviousFrame ! = id & & g . HoveredIdPreviousFrame ! = 0 ) )
hovered = false ;
// Mouse
if ( hovered )
{
if ( ! ( flags & ImGuiButtonFlags_NoKeyModifiers ) | | ( ! g . IO . KeyCtrl & & ! g . IO . KeyShift & & ! g . IO . KeyAlt ) )
{
// | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
// PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
// PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
// PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
// PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
// FIXME-NAV: We don't honor those different behaviors.
if ( ( flags & ImGuiButtonFlags_PressedOnClickRelease ) & & g . IO . MouseClicked [ 0 ] )
{
SetActiveID ( id , window ) ;
if ( ! ( flags & ImGuiButtonFlags_NoNavFocus ) )
SetFocusID ( id , window ) ;
FocusWindow ( window ) ;
}
if ( ( ( flags & ImGuiButtonFlags_PressedOnClick ) & & g . IO . MouseClicked [ 0 ] ) | | ( ( flags & ImGuiButtonFlags_PressedOnDoubleClick ) & & g . IO . MouseDoubleClicked [ 0 ] ) )
{
pressed = true ;
if ( flags & ImGuiButtonFlags_NoHoldingActiveID )
ClearActiveID ( ) ;
else
SetActiveID ( id , window ) ; // Hold on ID
FocusWindow ( window ) ;
}
if ( ( flags & ImGuiButtonFlags_PressedOnRelease ) & & g . IO . MouseReleased [ 0 ] )
{
if ( ! ( ( flags & ImGuiButtonFlags_Repeat ) & & g . IO . MouseDownDurationPrev [ 0 ] > = g . IO . KeyRepeatDelay ) ) // Repeat mode trumps <on release>
pressed = true ;
ClearActiveID ( ) ;
}
// 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
// Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
if ( ( flags & ImGuiButtonFlags_Repeat ) & & g . ActiveId = = id & & g . IO . MouseDownDuration [ 0 ] > 0.0f & & IsMouseClicked ( 0 , true ) )
pressed = true ;
}
if ( pressed )
g . NavDisableHighlight = true ;
}
// Gamepad/Keyboard navigation
// We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
if ( g . NavId = = id & & ! g . NavDisableHighlight & & g . NavDisableMouseHover & & ( g . ActiveId = = 0 | | g . ActiveId = = id | | g . ActiveId = = window - > MoveId ) )
hovered = true ;
if ( g . NavActivateDownId = = id )
{
bool nav_activated_by_code = ( g . NavActivateId = = id ) ;
bool nav_activated_by_inputs = IsNavInputPressed ( ImGuiNavInput_Activate , ( flags & ImGuiButtonFlags_Repeat ) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed ) ;
if ( nav_activated_by_code | | nav_activated_by_inputs )
pressed = true ;
if ( nav_activated_by_code | | nav_activated_by_inputs | | g . ActiveId = = id )
{
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
g . NavActivateId = id ; // This is so SetActiveId assign a Nav source
SetActiveID ( id , window ) ;
if ( ! ( flags & ImGuiButtonFlags_NoNavFocus ) )
SetFocusID ( id , window ) ;
g . ActiveIdAllowNavDirFlags = ( 1 < < ImGuiDir_Left ) | ( 1 < < ImGuiDir_Right ) | ( 1 < < ImGuiDir_Up ) | ( 1 < < ImGuiDir_Down ) ;
}
}
bool held = false ;
if ( g . ActiveId = = id )
{
if ( g . ActiveIdSource = = ImGuiInputSource_Mouse )
{
if ( g . ActiveIdIsJustActivated )
g . ActiveIdClickOffset = g . IO . MousePos - bb . Min ;
if ( g . IO . MouseDown [ 0 ] )
{
held = true ;
}
else
{
if ( hovered & & ( flags & ImGuiButtonFlags_PressedOnClickRelease ) )
if ( ! ( ( flags & ImGuiButtonFlags_Repeat ) & & g . IO . MouseDownDurationPrev [ 0 ] > = g . IO . KeyRepeatDelay ) ) // Repeat mode trumps <on release>
if ( ! g . DragDropActive )
pressed = true ;
ClearActiveID ( ) ;
}
if ( ! ( flags & ImGuiButtonFlags_NoNavFocus ) )
g . NavDisableHighlight = true ;
}
else if ( g . ActiveIdSource = = ImGuiInputSource_Nav )
{
if ( g . NavActivateDownId ! = id )
ClearActiveID ( ) ;
}
}
if ( out_hovered ) * out_hovered = hovered ;
if ( out_held ) * out_held = held ;
return pressed ;
}
bool ImGui : : ButtonEx ( const char * label , const ImVec2 & size_arg , ImGuiButtonFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const ImGuiID id = window - > GetID ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
ImVec2 pos = window - > DC . CursorPos ;
if ( ( flags & ImGuiButtonFlags_AlignTextBaseLine ) & & style . FramePadding . y < window - > DC . CurrentLineTextBaseOffset ) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
pos . y + = window - > DC . CurrentLineTextBaseOffset - style . FramePadding . y ;
ImVec2 size = CalcItemSize ( size_arg , label_size . x + style . FramePadding . x * 2.0f , label_size . y + style . FramePadding . y * 2.0f ) ;
const ImRect bb ( pos , pos + size ) ;
ItemSize ( bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( bb , id ) )
return false ;
if ( window - > DC . ItemFlags & ImGuiItemFlags_ButtonRepeat )
flags | = ImGuiButtonFlags_Repeat ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held , flags ) ;
if ( pressed )
MarkItemEdited ( id ) ;
// Render
const ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button ) ;
RenderNavHighlight ( bb , id ) ;
RenderFrame ( bb . Min , bb . Max , col , true , style . FrameRounding ) ;
RenderTextClipped ( bb . Min + style . FramePadding , bb . Max - style . FramePadding , label , NULL , & label_size , style . ButtonTextAlign , & bb ) ;
// Automatically close popups
//if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
// CloseCurrentPopup();
return pressed ;
}
bool ImGui : : Button ( const char * label , const ImVec2 & size_arg )
{
return ButtonEx ( label , size_arg , 0 ) ;
}
// Small buttons fits within text without additional vertical spacing.
bool ImGui : : SmallButton ( const char * label )
{
ImGuiContext & g = * GImGui ;
float backup_padding_y = g . Style . FramePadding . y ;
g . Style . FramePadding . y = 0.0f ;
bool pressed = ButtonEx ( label , ImVec2 ( 0 , 0 ) , ImGuiButtonFlags_AlignTextBaseLine ) ;
g . Style . FramePadding . y = backup_padding_y ;
return pressed ;
}
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
// Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
bool ImGui : : InvisibleButton ( const char * str_id , const ImVec2 & size_arg )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
// Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
IM_ASSERT ( size_arg . x ! = 0.0f & & size_arg . y ! = 0.0f ) ;
const ImGuiID id = window - > GetID ( str_id ) ;
ImVec2 size = CalcItemSize ( size_arg , 0.0f , 0.0f ) ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + size ) ;
ItemSize ( bb ) ;
if ( ! ItemAdd ( bb , id ) )
return false ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held ) ;
return pressed ;
}
bool ImGui : : ArrowButtonEx ( const char * str_id , ImGuiDir dir , ImVec2 size , ImGuiButtonFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiID id = window - > GetID ( str_id ) ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + size ) ;
const float default_size = GetFrameHeight ( ) ;
ItemSize ( bb , ( size . y > = default_size ) ? g . Style . FramePadding . y : 0.0f ) ;
if ( ! ItemAdd ( bb , id ) )
return false ;
if ( window - > DC . ItemFlags & ImGuiItemFlags_ButtonRepeat )
flags | = ImGuiButtonFlags_Repeat ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held , flags ) ;
// Render
const ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button ) ;
RenderNavHighlight ( bb , id ) ;
RenderFrame ( bb . Min , bb . Max , col , true , g . Style . FrameRounding ) ;
RenderArrow ( bb . Min + ImVec2 ( ImMax ( 0.0f , size . x - g . FontSize - g . Style . FramePadding . x ) , ImMax ( 0.0f , size . y - g . FontSize - g . Style . FramePadding . y ) ) , dir ) ;
return pressed ;
}
bool ImGui : : ArrowButton ( const char * str_id , ImGuiDir dir )
{
float sz = GetFrameHeight ( ) ;
return ArrowButtonEx ( str_id , dir , ImVec2 ( sz , sz ) , 0 ) ;
}
// Button to close a window
bool ImGui : : CloseButton ( ImGuiID id , const ImVec2 & pos , float radius )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
// We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
// (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
const ImRect bb ( pos - ImVec2 ( radius , radius ) , pos + ImVec2 ( radius , radius ) ) ;
bool is_clipped = ! ItemAdd ( bb , id ) ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held ) ;
if ( is_clipped )
return pressed ;
// Render
ImVec2 center = bb . GetCenter ( ) ;
if ( hovered )
window - > DrawList - > AddCircleFilled ( center , ImMax ( 2.0f , radius ) , GetColorU32 ( ( held & & hovered ) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered ) , 9 ) ;
float cross_extent = ( radius * 0.7071f ) - 1.0f ;
ImU32 cross_col = GetColorU32 ( ImGuiCol_Text ) ;
center - = ImVec2 ( 0.5f , 0.5f ) ;
window - > DrawList - > AddLine ( center + ImVec2 ( + cross_extent , + cross_extent ) , center + ImVec2 ( - cross_extent , - cross_extent ) , cross_col , 1.0f ) ;
window - > DrawList - > AddLine ( center + ImVec2 ( + cross_extent , - cross_extent ) , center + ImVec2 ( - cross_extent , + cross_extent ) , cross_col , 1.0f ) ;
return pressed ;
}
bool ImGui : : CollapseButton ( ImGuiID id , const ImVec2 & pos )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
ImRect bb ( pos , pos + ImVec2 ( g . FontSize , g . FontSize ) + g . Style . FramePadding * 2.0f ) ;
ItemAdd ( bb , id ) ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held , ImGuiButtonFlags_None ) ;
ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button ) ;
if ( hovered | | held )
window - > DrawList - > AddCircleFilled ( bb . GetCenter ( ) + ImVec2 ( 0.0f , - 0.5f ) , g . FontSize * 0.5f + 1.0f , col , 9 ) ;
RenderArrow ( bb . Min + g . Style . FramePadding , window - > Collapsed ? ImGuiDir_Right : ImGuiDir_Down , 1.0f ) ;
// Switch to moving the window after mouse is moved beyond the initial drag threshold
if ( IsItemActive ( ) & & IsMouseDragging ( ) )
StartMouseMovingWindow ( window ) ;
return pressed ;
}
void ImGui : : Image ( ImTextureID user_texture_id , const ImVec2 & size , const ImVec2 & uv0 , const ImVec2 & uv1 , const ImVec4 & tint_col , const ImVec4 & border_col )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + size ) ;
if ( border_col . w > 0.0f )
bb . Max + = ImVec2 ( 2 , 2 ) ;
ItemSize ( bb ) ;
if ( ! ItemAdd ( bb , 0 ) )
return ;
if ( border_col . w > 0.0f )
{
window - > DrawList - > AddRect ( bb . Min , bb . Max , GetColorU32 ( border_col ) , 0.0f ) ;
window - > DrawList - > AddImage ( user_texture_id , bb . Min + ImVec2 ( 1 , 1 ) , bb . Max - ImVec2 ( 1 , 1 ) , uv0 , uv1 , GetColorU32 ( tint_col ) ) ;
}
else
{
window - > DrawList - > AddImage ( user_texture_id , bb . Min , bb . Max , uv0 , uv1 , GetColorU32 ( tint_col ) ) ;
}
}
// frame_padding < 0: uses FramePadding from style (default)
// frame_padding = 0: no framing
// frame_padding > 0: set framing size
// The color used are the button colors.
bool ImGui : : ImageButton ( ImTextureID user_texture_id , const ImVec2 & size , const ImVec2 & uv0 , const ImVec2 & uv1 , int frame_padding , const ImVec4 & bg_col , const ImVec4 & tint_col )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
// Default to using texture ID as ID. User can still push string/integer prefixes.
// We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
PushID ( ( void * ) user_texture_id ) ;
const ImGuiID id = window - > GetID ( " #image " ) ;
PopID ( ) ;
const ImVec2 padding = ( frame_padding > = 0 ) ? ImVec2 ( ( float ) frame_padding , ( float ) frame_padding ) : style . FramePadding ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + size + padding * 2 ) ;
const ImRect image_bb ( window - > DC . CursorPos + padding , window - > DC . CursorPos + padding + size ) ;
ItemSize ( bb ) ;
if ( ! ItemAdd ( bb , id ) )
return false ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held ) ;
// Render
const ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button ) ;
RenderNavHighlight ( bb , id ) ;
RenderFrame ( bb . Min , bb . Max , col , true , ImClamp ( ( float ) ImMin ( padding . x , padding . y ) , 0.0f , style . FrameRounding ) ) ;
if ( bg_col . w > 0.0f )
window - > DrawList - > AddRectFilled ( image_bb . Min , image_bb . Max , GetColorU32 ( bg_col ) ) ;
window - > DrawList - > AddImage ( user_texture_id , image_bb . Min , image_bb . Max , uv0 , uv1 , GetColorU32 ( tint_col ) ) ;
return pressed ;
}
bool ImGui : : Checkbox ( const char * label , bool * v )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const ImGuiID id = window - > GetID ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
const ImRect check_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( label_size . y + style . FramePadding . y * 2 , label_size . y + style . FramePadding . y * 2 ) ) ; // We want a square shape to we use Y twice
ItemSize ( check_bb , style . FramePadding . y ) ;
ImRect total_bb = check_bb ;
if ( label_size . x > 0 )
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
const ImRect text_bb ( window - > DC . CursorPos + ImVec2 ( 0 , style . FramePadding . y ) , window - > DC . CursorPos + ImVec2 ( 0 , style . FramePadding . y ) + label_size ) ;
if ( label_size . x > 0 )
{
ItemSize ( ImVec2 ( text_bb . GetWidth ( ) , check_bb . GetHeight ( ) ) , style . FramePadding . y ) ;
total_bb = ImRect ( ImMin ( check_bb . Min , text_bb . Min ) , ImMax ( check_bb . Max , text_bb . Max ) ) ;
}
if ( ! ItemAdd ( total_bb , id ) )
return false ;
bool hovered , held ;
bool pressed = ButtonBehavior ( total_bb , id , & hovered , & held ) ;
if ( pressed )
{
* v = ! ( * v ) ;
MarkItemEdited ( id ) ;
}
RenderNavHighlight ( total_bb , id ) ;
RenderFrame ( check_bb . Min , check_bb . Max , GetColorU32 ( ( held & & hovered ) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg ) , true , style . FrameRounding ) ;
if ( * v )
{
const float check_sz = ImMin ( check_bb . GetWidth ( ) , check_bb . GetHeight ( ) ) ;
const float pad = ImMax ( 1.0f , ( float ) ( int ) ( check_sz / 6.0f ) ) ;
RenderCheckMark ( check_bb . Min + ImVec2 ( pad , pad ) , GetColorU32 ( ImGuiCol_CheckMark ) , check_bb . GetWidth ( ) - pad * 2.0f ) ;
}
if ( g . LogEnabled )
LogRenderedText ( & text_bb . Min , * v ? " [x] " : " [ ] " ) ;
if ( label_size . x > 0.0f )
RenderText ( text_bb . Min , label ) ;
return pressed ;
}
bool ImGui : : CheckboxFlags ( const char * label , unsigned int * flags , unsigned int flags_value )
{
bool v = ( ( * flags & flags_value ) = = flags_value ) ;
bool pressed = Checkbox ( label , & v ) ;
if ( pressed )
{
if ( v )
* flags | = flags_value ;
else
* flags & = ~ flags_value ;
}
return pressed ;
}
bool ImGui : : RadioButton ( const char * label , bool active )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const ImGuiID id = window - > GetID ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
const ImRect check_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( label_size . y + style . FramePadding . y * 2 - 1 , label_size . y + style . FramePadding . y * 2 - 1 ) ) ;
ItemSize ( check_bb , style . FramePadding . y ) ;
ImRect total_bb = check_bb ;
if ( label_size . x > 0 )
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
const ImRect text_bb ( window - > DC . CursorPos + ImVec2 ( 0 , style . FramePadding . y ) , window - > DC . CursorPos + ImVec2 ( 0 , style . FramePadding . y ) + label_size ) ;
if ( label_size . x > 0 )
{
ItemSize ( ImVec2 ( text_bb . GetWidth ( ) , check_bb . GetHeight ( ) ) , style . FramePadding . y ) ;
total_bb . Add ( text_bb ) ;
}
if ( ! ItemAdd ( total_bb , id ) )
return false ;
ImVec2 center = check_bb . GetCenter ( ) ;
center . x = ( float ) ( int ) center . x + 0.5f ;
center . y = ( float ) ( int ) center . y + 0.5f ;
const float radius = check_bb . GetHeight ( ) * 0.5f ;
bool hovered , held ;
bool pressed = ButtonBehavior ( total_bb , id , & hovered , & held ) ;
if ( pressed )
MarkItemEdited ( id ) ;
RenderNavHighlight ( total_bb , id ) ;
window - > DrawList - > AddCircleFilled ( center , radius , GetColorU32 ( ( held & & hovered ) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg ) , 16 ) ;
if ( active )
{
const float check_sz = ImMin ( check_bb . GetWidth ( ) , check_bb . GetHeight ( ) ) ;
const float pad = ImMax ( 1.0f , ( float ) ( int ) ( check_sz / 6.0f ) ) ;
window - > DrawList - > AddCircleFilled ( center , radius - pad , GetColorU32 ( ImGuiCol_CheckMark ) , 16 ) ;
}
if ( style . FrameBorderSize > 0.0f )
{
window - > DrawList - > AddCircle ( center + ImVec2 ( 1 , 1 ) , radius , GetColorU32 ( ImGuiCol_BorderShadow ) , 16 , style . FrameBorderSize ) ;
window - > DrawList - > AddCircle ( center , radius , GetColorU32 ( ImGuiCol_Border ) , 16 , style . FrameBorderSize ) ;
}
if ( g . LogEnabled )
LogRenderedText ( & text_bb . Min , active ? " (x) " : " ( ) " ) ;
if ( label_size . x > 0.0f )
RenderText ( text_bb . Min , label ) ;
return pressed ;
}
bool ImGui : : RadioButton ( const char * label , int * v , int v_button )
{
const bool pressed = RadioButton ( label , * v = = v_button ) ;
if ( pressed )
* v = v_button ;
return pressed ;
}
// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
void ImGui : : ProgressBar ( float fraction , const ImVec2 & size_arg , const char * overlay )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
ImVec2 pos = window - > DC . CursorPos ;
ImRect bb ( pos , pos + CalcItemSize ( size_arg , CalcItemWidth ( ) , g . FontSize + style . FramePadding . y * 2.0f ) ) ;
ItemSize ( bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( bb , 0 ) )
return ;
// Render
fraction = ImSaturate ( fraction ) ;
RenderFrame ( bb . Min , bb . Max , GetColorU32 ( ImGuiCol_FrameBg ) , true , style . FrameRounding ) ;
bb . Expand ( ImVec2 ( - style . FrameBorderSize , - style . FrameBorderSize ) ) ;
const ImVec2 fill_br = ImVec2 ( ImLerp ( bb . Min . x , bb . Max . x , fraction ) , bb . Max . y ) ;
RenderRectFilledRangeH ( window - > DrawList , bb , GetColorU32 ( ImGuiCol_PlotHistogram ) , 0.0f , fraction , style . FrameRounding ) ;
// Default displaying the fraction as percentage string, but user can override it
char overlay_buf [ 32 ] ;
if ( ! overlay )
{
ImFormatString ( overlay_buf , IM_ARRAYSIZE ( overlay_buf ) , " %.0f%% " , fraction * 100 + 0.01f ) ;
overlay = overlay_buf ;
}
ImVec2 overlay_size = CalcTextSize ( overlay , NULL ) ;
if ( overlay_size . x > 0.0f )
RenderTextClipped ( ImVec2 ( ImClamp ( fill_br . x + style . ItemSpacing . x , bb . Min . x , bb . Max . x - overlay_size . x - style . ItemInnerSpacing . x ) , bb . Min . y ) , bb . Max , overlay , NULL , & overlay_size , ImVec2 ( 0.0f , 0.5f ) , & bb ) ;
}
void ImGui : : Bullet ( )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const float line_height = ImMax ( ImMin ( window - > DC . CurrentLineSize . y , g . FontSize + g . Style . FramePadding . y * 2 ) , g . FontSize ) ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( g . FontSize , line_height ) ) ;
ItemSize ( bb ) ;
if ( ! ItemAdd ( bb , 0 ) )
{
SameLine ( 0 , style . FramePadding . x * 2 ) ;
return ;
}
// Render and stay on same line
RenderBullet ( bb . Min + ImVec2 ( style . FramePadding . x + g . FontSize * 0.5f , line_height * 0.5f ) ) ;
SameLine ( 0 , style . FramePadding . x * 2 ) ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Combo Box
// - BeginCombo()
// - EndCombo()
// - Combo()
//-------------------------------------------------------------------------
2018-08-30 12:49:28 +00:00
static float CalcMaxPopupHeightFromItemCount ( int items_count )
{
ImGuiContext & g = * GImGui ;
if ( items_count < = 0 )
return FLT_MAX ;
return ( g . FontSize + g . Style . ItemSpacing . y ) * items_count - g . Style . ItemSpacing . y + ( g . Style . WindowPadding . y * 2 ) ;
}
bool ImGui : : BeginCombo ( const char * label , const char * preview_value , ImGuiComboFlags flags )
{
// Always consume the SetNextWindowSizeConstraint() call in our early return paths
ImGuiContext & g = * GImGui ;
ImGuiCond backup_next_window_size_constraint = g . NextWindowData . SizeConstraintCond ;
g . NextWindowData . SizeConstraintCond = 0 ;
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
IM_ASSERT ( ( flags & ( ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview ) ) ! = ( ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview ) ) ; // Can't use both flags together
const ImGuiStyle & style = g . Style ;
const ImGuiID id = window - > GetID ( label ) ;
const float arrow_size = ( flags & ImGuiComboFlags_NoArrowButton ) ? 0.0f : GetFrameHeight ( ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
const float w = ( flags & ImGuiComboFlags_NoPreview ) ? arrow_size : CalcItemWidth ( ) ;
const ImRect frame_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( w , label_size . y + style . FramePadding . y * 2.0f ) ) ;
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 ) ) ;
ItemSize ( total_bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( total_bb , id , & frame_bb ) )
return false ;
bool hovered , held ;
bool pressed = ButtonBehavior ( frame_bb , id , & hovered , & held ) ;
bool popup_open = IsPopupOpen ( id ) ;
const ImRect value_bb ( frame_bb . Min , frame_bb . Max - ImVec2 ( arrow_size , 0.0f ) ) ;
const ImU32 frame_col = GetColorU32 ( hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg ) ;
RenderNavHighlight ( frame_bb , id ) ;
if ( ! ( flags & ImGuiComboFlags_NoPreview ) )
window - > DrawList - > AddRectFilled ( frame_bb . Min , ImVec2 ( frame_bb . Max . x - arrow_size , frame_bb . Max . y ) , frame_col , style . FrameRounding , ImDrawCornerFlags_Left ) ;
if ( ! ( flags & ImGuiComboFlags_NoArrowButton ) )
{
window - > DrawList - > AddRectFilled ( ImVec2 ( frame_bb . Max . x - arrow_size , frame_bb . Min . y ) , frame_bb . Max , GetColorU32 ( ( popup_open | | hovered ) ? ImGuiCol_ButtonHovered : ImGuiCol_Button ) , style . FrameRounding , ( w < = arrow_size ) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right ) ;
RenderArrow ( ImVec2 ( frame_bb . Max . x - arrow_size + style . FramePadding . y , frame_bb . Min . y + style . FramePadding . y ) , ImGuiDir_Down ) ;
}
RenderFrameBorder ( frame_bb . Min , frame_bb . Max , style . FrameRounding ) ;
if ( preview_value ! = NULL & & ! ( flags & ImGuiComboFlags_NoPreview ) )
RenderTextClipped ( frame_bb . Min + style . FramePadding , value_bb . Max , preview_value , NULL , NULL , ImVec2 ( 0.0f , 0.0f ) ) ;
if ( label_size . x > 0 )
RenderText ( ImVec2 ( frame_bb . Max . x + style . ItemInnerSpacing . x , frame_bb . Min . y + style . FramePadding . y ) , label ) ;
if ( ( pressed | | g . NavActivateId = = id ) & & ! popup_open )
{
if ( window - > DC . NavLayerCurrent = = 0 )
window - > NavLastIds [ 0 ] = id ;
OpenPopupEx ( id ) ;
popup_open = true ;
}
if ( ! popup_open )
return false ;
if ( backup_next_window_size_constraint )
{
g . NextWindowData . SizeConstraintCond = backup_next_window_size_constraint ;
g . NextWindowData . SizeConstraintRect . Min . x = ImMax ( g . NextWindowData . SizeConstraintRect . Min . x , w ) ;
}
else
{
if ( ( flags & ImGuiComboFlags_HeightMask_ ) = = 0 )
flags | = ImGuiComboFlags_HeightRegular ;
IM_ASSERT ( ImIsPowerOfTwo ( flags & ImGuiComboFlags_HeightMask_ ) ) ; // Only one
int popup_max_height_in_items = - 1 ;
if ( flags & ImGuiComboFlags_HeightRegular ) popup_max_height_in_items = 8 ;
else if ( flags & ImGuiComboFlags_HeightSmall ) popup_max_height_in_items = 4 ;
else if ( flags & ImGuiComboFlags_HeightLarge ) popup_max_height_in_items = 20 ;
SetNextWindowSizeConstraints ( ImVec2 ( w , 0.0f ) , ImVec2 ( FLT_MAX , CalcMaxPopupHeightFromItemCount ( popup_max_height_in_items ) ) ) ;
}
char name [ 16 ] ;
ImFormatString ( name , IM_ARRAYSIZE ( name ) , " ##Combo_%02d " , g . CurrentPopupStack . Size ) ; // Recycle windows based on depth
// Peak into expected window size so we can position it
if ( ImGuiWindow * popup_window = FindWindowByName ( name ) )
if ( popup_window - > WasActive )
{
ImVec2 size_expected = CalcWindowExpectedSize ( popup_window ) ;
if ( flags & ImGuiComboFlags_PopupAlignLeft )
popup_window - > AutoPosLastDirection = ImGuiDir_Left ;
ImRect r_outer = GetWindowAllowedExtentRect ( popup_window ) ;
ImVec2 pos = FindBestWindowPosForPopupEx ( frame_bb . GetBL ( ) , size_expected , & popup_window - > AutoPosLastDirection , r_outer , frame_bb , ImGuiPopupPositionPolicy_ComboBox ) ;
SetNextWindowPos ( pos ) ;
}
// Horizontally align ourselves with the framed text
ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings ;
PushStyleVar ( ImGuiStyleVar_WindowPadding , ImVec2 ( style . FramePadding . x , style . WindowPadding . y ) ) ;
bool ret = Begin ( name , NULL , window_flags ) ;
PopStyleVar ( ) ;
if ( ! ret )
{
EndPopup ( ) ;
IM_ASSERT ( 0 ) ; // This should never happen as we tested for IsPopupOpen() above
return false ;
}
return true ;
}
void ImGui : : EndCombo ( )
{
EndPopup ( ) ;
}
// Getter for the old Combo() API: const char*[]
static bool Items_ArrayGetter ( void * data , int idx , const char * * out_text )
{
const char * const * items = ( const char * const * ) data ;
if ( out_text )
* out_text = items [ idx ] ;
return true ;
}
// Getter for the old Combo() API: "item1\0item2\0item3\0"
static bool Items_SingleStringGetter ( void * data , int idx , const char * * out_text )
{
// FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
const char * items_separated_by_zeros = ( const char * ) data ;
int items_count = 0 ;
const char * p = items_separated_by_zeros ;
while ( * p )
{
if ( idx = = items_count )
break ;
p + = strlen ( p ) + 1 ;
items_count + + ;
}
if ( ! * p )
return false ;
if ( out_text )
* out_text = p ;
return true ;
}
// Old API, prefer using BeginCombo() nowadays if you can.
bool ImGui : : Combo ( const char * label , int * current_item , bool ( * items_getter ) ( void * , int , const char * * ) , void * data , int items_count , int popup_max_height_in_items )
{
ImGuiContext & g = * GImGui ;
// Call the getter to obtain the preview string which is a parameter to BeginCombo()
const char * preview_value = NULL ;
if ( * current_item > = 0 & & * current_item < items_count )
items_getter ( data , * current_item , & preview_value ) ;
// The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
if ( popup_max_height_in_items ! = - 1 & & ! g . NextWindowData . SizeConstraintCond )
SetNextWindowSizeConstraints ( ImVec2 ( 0 , 0 ) , ImVec2 ( FLT_MAX , CalcMaxPopupHeightFromItemCount ( popup_max_height_in_items ) ) ) ;
if ( ! BeginCombo ( label , preview_value , ImGuiComboFlags_None ) )
return false ;
// Display items
// FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
bool value_changed = false ;
for ( int i = 0 ; i < items_count ; i + + )
{
PushID ( ( void * ) ( intptr_t ) i ) ;
const bool item_selected = ( i = = * current_item ) ;
const char * item_text ;
if ( ! items_getter ( data , i , & item_text ) )
item_text = " *Unknown item* " ;
if ( Selectable ( item_text , item_selected ) )
{
value_changed = true ;
* current_item = i ;
}
if ( item_selected )
SetItemDefaultFocus ( ) ;
PopID ( ) ;
}
EndCombo ( ) ;
return value_changed ;
}
// Combo box helper allowing to pass an array of strings.
bool ImGui : : Combo ( const char * label , int * current_item , const char * const items [ ] , int items_count , int height_in_items )
{
const bool value_changed = Combo ( label , current_item , Items_ArrayGetter , ( void * ) items , items_count , height_in_items ) ;
return value_changed ;
}
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
bool ImGui : : Combo ( const char * label , int * current_item , const char * items_separated_by_zeros , int height_in_items )
{
int items_count = 0 ;
const char * p = items_separated_by_zeros ; // FIXME-OPT: Avoid computing this, or at least only when combo is open
while ( * p )
{
p + = strlen ( p ) + 1 ;
items_count + + ;
}
bool value_changed = Combo ( label , current_item , Items_SingleStringGetter , ( void * ) items_separated_by_zeros , items_count , height_in_items ) ;
return value_changed ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Data Type and Data Formatting Helpers [Internal]
// - PatchFormatStringFloatToInt()
// - DataTypeFormatString()
// - DataTypeApplyOp()
// - DataTypeApplyOpFromText()
// - GetMinimumStepAtDecimalPrecision
// - RoundScalarWithFormat<>()
//-------------------------------------------------------------------------
2018-08-28 19:59:14 +00:00
//-------------------------------------------------------------------------
2018-08-29 13:15:36 +00:00
// WIDGETS: Drags
// - DragBehaviorT<>() [Internal]
// - DragBehavior() [Internal]
// - DragScalar()
// - DragScalarN()
// - DragFloat()
// - DragFloat2()
// - DragFloat3()
// - DragFloat4()
// - DragFloatRange2()
// - DragInt()
// - DragInt2()
// - DragInt3()
// - DragInt4()
// - DragIntRange2()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Sliders
// - SliderBehaviorT<>() [Internal]
// - SliderBehavior() [Internal]
// - SliderScalar()
// - SliderScalarN()
// - SliderFloat()
// - SliderFloat2()
// - SliderFloat3()
// - SliderFloat4()
// - SliderAngle()
// - SliderInt()
// - SliderInt2()
// - SliderInt3()
// - SliderInt4()
// - VSliderScalar()
// - VSliderFloat()
// - VSliderInt()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Inputs (_excepted InputText_)
// - ImParseFormatFindStart()
// - ImParseFormatFindEnd()
// - ImParseFormatTrimDecorations()
// - ImParseFormatPrecision()
// - InputScalarAsWidgetReplacement() [Internal]
// - InputScalar()
// - InputScalarN()
// - InputFloat()
// - InputFloat2()
// - InputFloat3()
// - InputFloat4()
// - InputInt()
// - InputInt2()
// - InputInt3()
// - InputInt4()
// - InputDouble()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: InputText
// - InputText()
// - InputTextMultiline()
// - InputTextEx() [Internal]
//-------------------------------------------------------------------------
2018-08-30 13:09:33 +00:00
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 ) ;
}
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 ) ;
g . PlatformImePosViewport = window - > Viewport ;
}
}
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 ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Color Editor / Picker
// - ColorEdit3()
// - ColorEdit4()
// - ColorPicker3()
// - RenderColorRectWithAlphaCheckerboard() [Internal]
// - ColorPicker4()
// - ColorButton()
// - SetColorEditOptions()
// - ColorTooltip() [Internal]
// - ColorEditOptionsPopup() [Internal]
// - ColorPickerOptionsPopup() [Internal]
//-------------------------------------------------------------------------
2018-08-30 13:06:05 +00:00
bool ImGui : : ColorEdit3 ( const char * label , float col [ 3 ] , ImGuiColorEditFlags flags )
{
return ColorEdit4 ( label , col , flags | ImGuiColorEditFlags_NoAlpha ) ;
}
// Edit colors components (each component in 0.0f..1.0f range).
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
bool ImGui : : ColorEdit4 ( const char * label , float col [ 4 ] , ImGuiColorEditFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const float square_sz = GetFrameHeight ( ) ;
const float w_extra = ( flags & ImGuiColorEditFlags_NoSmallPreview ) ? 0.0f : ( square_sz + style . ItemInnerSpacing . x ) ;
const float w_items_all = CalcItemWidth ( ) - w_extra ;
const char * label_display_end = FindRenderedTextEnd ( label ) ;
BeginGroup ( ) ;
PushID ( label ) ;
// If we're not showing any slider there's no point in doing any HSV conversions
const ImGuiColorEditFlags flags_untouched = flags ;
if ( flags & ImGuiColorEditFlags_NoInputs )
flags = ( flags & ( ~ ImGuiColorEditFlags__InputsMask ) ) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions ;
// Context menu: display and modify options (before defaults are applied)
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
ColorEditOptionsPopup ( col , flags ) ;
// Read stored options
if ( ! ( flags & ImGuiColorEditFlags__InputsMask ) )
flags | = ( g . ColorEditOptions & ImGuiColorEditFlags__InputsMask ) ;
if ( ! ( flags & ImGuiColorEditFlags__DataTypeMask ) )
flags | = ( g . ColorEditOptions & ImGuiColorEditFlags__DataTypeMask ) ;
if ( ! ( flags & ImGuiColorEditFlags__PickerMask ) )
flags | = ( g . ColorEditOptions & ImGuiColorEditFlags__PickerMask ) ;
flags | = ( g . ColorEditOptions & ~ ( ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask ) ) ;
const bool alpha = ( flags & ImGuiColorEditFlags_NoAlpha ) = = 0 ;
const bool hdr = ( flags & ImGuiColorEditFlags_HDR ) ! = 0 ;
const int components = alpha ? 4 : 3 ;
// Convert to the formats we need
float f [ 4 ] = { col [ 0 ] , col [ 1 ] , col [ 2 ] , alpha ? col [ 3 ] : 1.0f } ;
if ( flags & ImGuiColorEditFlags_HSV )
ColorConvertRGBtoHSV ( f [ 0 ] , f [ 1 ] , f [ 2 ] , f [ 0 ] , f [ 1 ] , f [ 2 ] ) ;
int i [ 4 ] = { IM_F32_TO_INT8_UNBOUND ( f [ 0 ] ) , IM_F32_TO_INT8_UNBOUND ( f [ 1 ] ) , IM_F32_TO_INT8_UNBOUND ( f [ 2 ] ) , IM_F32_TO_INT8_UNBOUND ( f [ 3 ] ) } ;
bool value_changed = false ;
bool value_changed_as_float = false ;
if ( ( flags & ( ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV ) ) ! = 0 & & ( flags & ImGuiColorEditFlags_NoInputs ) = = 0 )
{
// RGB/HSV 0..255 Sliders
const float w_item_one = ImMax ( 1.0f , ( float ) ( int ) ( ( w_items_all - ( style . ItemInnerSpacing . x ) * ( components - 1 ) ) / ( float ) components ) ) ;
const float w_item_last = ImMax ( 1.0f , ( float ) ( int ) ( w_items_all - ( w_item_one + style . ItemInnerSpacing . x ) * ( components - 1 ) ) ) ;
const bool hide_prefix = ( w_item_one < = CalcTextSize ( ( flags & ImGuiColorEditFlags_Float ) ? " M:0.000 " : " M:000 " ) . x ) ;
const char * ids [ 4 ] = { " ##X " , " ##Y " , " ##Z " , " ##W " } ;
const char * fmt_table_int [ 3 ] [ 4 ] =
{
{ " %3d " , " %3d " , " %3d " , " %3d " } , // Short display
{ " R:%3d " , " G:%3d " , " B:%3d " , " A:%3d " } , // Long display for RGBA
{ " H:%3d " , " S:%3d " , " V:%3d " , " A:%3d " } // Long display for HSVA
} ;
const char * fmt_table_float [ 3 ] [ 4 ] =
{
{ " %0.3f " , " %0.3f " , " %0.3f " , " %0.3f " } , // Short display
{ " R:%0.3f " , " G:%0.3f " , " B:%0.3f " , " A:%0.3f " } , // Long display for RGBA
{ " H:%0.3f " , " S:%0.3f " , " V:%0.3f " , " A:%0.3f " } // Long display for HSVA
} ;
const int fmt_idx = hide_prefix ? 0 : ( flags & ImGuiColorEditFlags_HSV ) ? 2 : 1 ;
PushItemWidth ( w_item_one ) ;
for ( int n = 0 ; n < components ; n + + )
{
if ( n > 0 )
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
if ( n + 1 = = components )
PushItemWidth ( w_item_last ) ;
if ( flags & ImGuiColorEditFlags_Float )
value_changed = value_changed_as_float = value_changed | DragFloat ( ids [ n ] , & f [ n ] , 1.0f / 255.0f , 0.0f , hdr ? 0.0f : 1.0f , fmt_table_float [ fmt_idx ] [ n ] ) ;
else
value_changed | = DragInt ( ids [ n ] , & i [ n ] , 1.0f , 0 , hdr ? 0 : 255 , fmt_table_int [ fmt_idx ] [ n ] ) ;
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
OpenPopupOnItemClick ( " context " ) ;
}
PopItemWidth ( ) ;
PopItemWidth ( ) ;
}
else if ( ( flags & ImGuiColorEditFlags_HEX ) ! = 0 & & ( flags & ImGuiColorEditFlags_NoInputs ) = = 0 )
{
// RGB Hexadecimal Input
char buf [ 64 ] ;
if ( alpha )
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " #%02X%02X%02X%02X " , ImClamp ( i [ 0 ] , 0 , 255 ) , ImClamp ( i [ 1 ] , 0 , 255 ) , ImClamp ( i [ 2 ] , 0 , 255 ) , ImClamp ( i [ 3 ] , 0 , 255 ) ) ;
else
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " #%02X%02X%02X " , ImClamp ( i [ 0 ] , 0 , 255 ) , ImClamp ( i [ 1 ] , 0 , 255 ) , ImClamp ( i [ 2 ] , 0 , 255 ) ) ;
PushItemWidth ( w_items_all ) ;
if ( InputText ( " ##Text " , buf , IM_ARRAYSIZE ( buf ) , ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase ) )
{
value_changed = true ;
char * p = buf ;
while ( * p = = ' # ' | | ImCharIsBlankA ( * p ) )
p + + ;
i [ 0 ] = i [ 1 ] = i [ 2 ] = i [ 3 ] = 0 ;
if ( alpha )
sscanf ( p , " %02X%02X%02X%02X " , ( unsigned int * ) & i [ 0 ] , ( unsigned int * ) & i [ 1 ] , ( unsigned int * ) & i [ 2 ] , ( unsigned int * ) & i [ 3 ] ) ; // Treat at unsigned (%X is unsigned)
else
sscanf ( p , " %02X%02X%02X " , ( unsigned int * ) & i [ 0 ] , ( unsigned int * ) & i [ 1 ] , ( unsigned int * ) & i [ 2 ] ) ;
}
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
OpenPopupOnItemClick ( " context " ) ;
PopItemWidth ( ) ;
}
ImGuiWindow * picker_active_window = NULL ;
if ( ! ( flags & ImGuiColorEditFlags_NoSmallPreview ) )
{
if ( ! ( flags & ImGuiColorEditFlags_NoInputs ) )
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
const ImVec4 col_v4 ( col [ 0 ] , col [ 1 ] , col [ 2 ] , alpha ? col [ 3 ] : 1.0f ) ;
if ( ColorButton ( " ##ColorButton " , col_v4 , flags ) )
{
if ( ! ( flags & ImGuiColorEditFlags_NoPicker ) )
{
// Store current color and open a picker
g . ColorPickerRef = col_v4 ;
OpenPopup ( " picker " ) ;
SetNextWindowPos ( window - > DC . LastItemRect . GetBL ( ) + ImVec2 ( - 1 , style . ItemSpacing . y ) ) ;
}
}
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
OpenPopupOnItemClick ( " context " ) ;
if ( BeginPopup ( " picker " ) )
{
picker_active_window = g . CurrentWindow ;
if ( label ! = label_display_end )
{
TextUnformatted ( label , label_display_end ) ;
Separator ( ) ;
}
ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar ;
ImGuiColorEditFlags picker_flags = ( flags_untouched & picker_flags_to_forward ) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf ;
PushItemWidth ( square_sz * 12.0f ) ; // Use 256 + bar sizes?
value_changed | = ColorPicker4 ( " ##picker " , col , picker_flags , & g . ColorPickerRef . x ) ;
PopItemWidth ( ) ;
EndPopup ( ) ;
}
}
if ( label ! = label_display_end & & ! ( flags & ImGuiColorEditFlags_NoLabel ) )
{
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
TextUnformatted ( label , label_display_end ) ;
}
// Convert back
if ( picker_active_window = = NULL )
{
if ( ! value_changed_as_float )
for ( int n = 0 ; n < 4 ; n + + )
f [ n ] = i [ n ] / 255.0f ;
if ( flags & ImGuiColorEditFlags_HSV )
ColorConvertHSVtoRGB ( f [ 0 ] , f [ 1 ] , f [ 2 ] , f [ 0 ] , f [ 1 ] , f [ 2 ] ) ;
if ( value_changed )
{
col [ 0 ] = f [ 0 ] ;
col [ 1 ] = f [ 1 ] ;
col [ 2 ] = f [ 2 ] ;
if ( alpha )
col [ 3 ] = f [ 3 ] ;
}
}
PopID ( ) ;
EndGroup ( ) ;
// Drag and Drop Target
// NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
if ( ( window - > DC . LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect ) & & ! ( flags & ImGuiColorEditFlags_NoDragDrop ) & & BeginDragDropTarget ( ) )
{
if ( const ImGuiPayload * payload = AcceptDragDropPayload ( IMGUI_PAYLOAD_TYPE_COLOR_3F ) )
{
memcpy ( ( float * ) col , payload - > Data , sizeof ( float ) * 3 ) ;
value_changed = true ;
}
if ( const ImGuiPayload * payload = AcceptDragDropPayload ( IMGUI_PAYLOAD_TYPE_COLOR_4F ) )
{
memcpy ( ( float * ) col , payload - > Data , sizeof ( float ) * components ) ;
value_changed = true ;
}
EndDragDropTarget ( ) ;
}
// When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
if ( picker_active_window & & g . ActiveId ! = 0 & & g . ActiveIdWindow = = picker_active_window )
window - > DC . LastItemId = g . ActiveId ;
if ( value_changed )
MarkItemEdited ( window - > DC . LastItemId ) ;
return value_changed ;
}
bool ImGui : : ColorPicker3 ( const char * label , float col [ 3 ] , ImGuiColorEditFlags flags )
{
float col4 [ 4 ] = { col [ 0 ] , col [ 1 ] , col [ 2 ] , 1.0f } ;
if ( ! ColorPicker4 ( label , col4 , flags | ImGuiColorEditFlags_NoAlpha ) )
return false ;
col [ 0 ] = col4 [ 0 ] ; col [ 1 ] = col4 [ 1 ] ; col [ 2 ] = col4 [ 2 ] ;
return true ;
}
static inline ImU32 ImAlphaBlendColor ( ImU32 col_a , ImU32 col_b )
{
float t = ( ( col_b > > IM_COL32_A_SHIFT ) & 0xFF ) / 255.f ;
int r = ImLerp ( ( int ) ( col_a > > IM_COL32_R_SHIFT ) & 0xFF , ( int ) ( col_b > > IM_COL32_R_SHIFT ) & 0xFF , t ) ;
int g = ImLerp ( ( int ) ( col_a > > IM_COL32_G_SHIFT ) & 0xFF , ( int ) ( col_b > > IM_COL32_G_SHIFT ) & 0xFF , t ) ;
int b = ImLerp ( ( int ) ( col_a > > IM_COL32_B_SHIFT ) & 0xFF , ( int ) ( col_b > > IM_COL32_B_SHIFT ) & 0xFF , t ) ;
return IM_COL32 ( r , g , b , 0xFF ) ;
}
// Helper for ColorPicker4()
// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
void ImGui : : RenderColorRectWithAlphaCheckerboard ( ImVec2 p_min , ImVec2 p_max , ImU32 col , float grid_step , ImVec2 grid_off , float rounding , int rounding_corners_flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( ( ( col & IM_COL32_A_MASK ) > > IM_COL32_A_SHIFT ) < 0xFF )
{
ImU32 col_bg1 = GetColorU32 ( ImAlphaBlendColor ( IM_COL32 ( 204 , 204 , 204 , 255 ) , col ) ) ;
ImU32 col_bg2 = GetColorU32 ( ImAlphaBlendColor ( IM_COL32 ( 128 , 128 , 128 , 255 ) , col ) ) ;
window - > DrawList - > AddRectFilled ( p_min , p_max , col_bg1 , rounding , rounding_corners_flags ) ;
int yi = 0 ;
for ( float y = p_min . y + grid_off . y ; y < p_max . y ; y + = grid_step , yi + + )
{
float y1 = ImClamp ( y , p_min . y , p_max . y ) , y2 = ImMin ( y + grid_step , p_max . y ) ;
if ( y2 < = y1 )
continue ;
for ( float x = p_min . x + grid_off . x + ( yi & 1 ) * grid_step ; x < p_max . x ; x + = grid_step * 2.0f )
{
float x1 = ImClamp ( x , p_min . x , p_max . x ) , x2 = ImMin ( x + grid_step , p_max . x ) ;
if ( x2 < = x1 )
continue ;
int rounding_corners_flags_cell = 0 ;
if ( y1 < = p_min . y ) { if ( x1 < = p_min . x ) rounding_corners_flags_cell | = ImDrawCornerFlags_TopLeft ; if ( x2 > = p_max . x ) rounding_corners_flags_cell | = ImDrawCornerFlags_TopRight ; }
if ( y2 > = p_max . y ) { if ( x1 < = p_min . x ) rounding_corners_flags_cell | = ImDrawCornerFlags_BotLeft ; if ( x2 > = p_max . x ) rounding_corners_flags_cell | = ImDrawCornerFlags_BotRight ; }
rounding_corners_flags_cell & = rounding_corners_flags ;
window - > DrawList - > AddRectFilled ( ImVec2 ( x1 , y1 ) , ImVec2 ( x2 , y2 ) , col_bg2 , rounding_corners_flags_cell ? rounding : 0.0f , rounding_corners_flags_cell ) ;
}
}
}
else
{
window - > DrawList - > AddRectFilled ( p_min , p_max , col , rounding , rounding_corners_flags ) ;
}
}
// Helper for ColorPicker4()
static void RenderArrowsForVerticalBar ( ImDrawList * draw_list , ImVec2 pos , ImVec2 half_sz , float bar_w )
{
ImGui : : RenderArrowPointingAt ( draw_list , ImVec2 ( pos . x + half_sz . x + 1 , pos . y ) , ImVec2 ( half_sz . x + 2 , half_sz . y + 1 ) , ImGuiDir_Right , IM_COL32_BLACK ) ;
ImGui : : RenderArrowPointingAt ( draw_list , ImVec2 ( pos . x + half_sz . x , pos . y ) , half_sz , ImGuiDir_Right , IM_COL32_WHITE ) ;
ImGui : : RenderArrowPointingAt ( draw_list , ImVec2 ( pos . x + bar_w - half_sz . x - 1 , pos . y ) , ImVec2 ( half_sz . x + 2 , half_sz . y + 1 ) , ImGuiDir_Left , IM_COL32_BLACK ) ;
ImGui : : RenderArrowPointingAt ( draw_list , ImVec2 ( pos . x + bar_w - half_sz . x , pos . y ) , half_sz , ImGuiDir_Left , IM_COL32_WHITE ) ;
}
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
bool ImGui : : ColorPicker4 ( const char * label , float col [ 4 ] , ImGuiColorEditFlags flags , const float * ref_col )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = GetCurrentWindow ( ) ;
ImDrawList * draw_list = window - > DrawList ;
ImGuiStyle & style = g . Style ;
ImGuiIO & io = g . IO ;
PushID ( label ) ;
BeginGroup ( ) ;
if ( ! ( flags & ImGuiColorEditFlags_NoSidePreview ) )
flags | = ImGuiColorEditFlags_NoSmallPreview ;
// Context menu: display and store options.
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
ColorPickerOptionsPopup ( col , flags ) ;
// Read stored options
if ( ! ( flags & ImGuiColorEditFlags__PickerMask ) )
flags | = ( ( g . ColorEditOptions & ImGuiColorEditFlags__PickerMask ) ? g . ColorEditOptions : ImGuiColorEditFlags__OptionsDefault ) & ImGuiColorEditFlags__PickerMask ;
IM_ASSERT ( ImIsPowerOfTwo ( ( int ) ( flags & ImGuiColorEditFlags__PickerMask ) ) ) ; // Check that only 1 is selected
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
flags | = ( g . ColorEditOptions & ImGuiColorEditFlags_AlphaBar ) ;
// Setup
int components = ( flags & ImGuiColorEditFlags_NoAlpha ) ? 3 : 4 ;
bool alpha_bar = ( flags & ImGuiColorEditFlags_AlphaBar ) & & ! ( flags & ImGuiColorEditFlags_NoAlpha ) ;
ImVec2 picker_pos = window - > DC . CursorPos ;
float square_sz = GetFrameHeight ( ) ;
float bars_width = square_sz ; // Arbitrary smallish width of Hue/Alpha picking bars
float sv_picker_size = ImMax ( bars_width * 1 , CalcItemWidth ( ) - ( alpha_bar ? 2 : 1 ) * ( bars_width + style . ItemInnerSpacing . x ) ) ; // Saturation/Value picking box
float bar0_pos_x = picker_pos . x + sv_picker_size + style . ItemInnerSpacing . x ;
float bar1_pos_x = bar0_pos_x + bars_width + style . ItemInnerSpacing . x ;
float bars_triangles_half_sz = ( float ) ( int ) ( bars_width * 0.20f ) ;
float backup_initial_col [ 4 ] ;
memcpy ( backup_initial_col , col , components * sizeof ( float ) ) ;
float wheel_thickness = sv_picker_size * 0.08f ;
float wheel_r_outer = sv_picker_size * 0.50f ;
float wheel_r_inner = wheel_r_outer - wheel_thickness ;
ImVec2 wheel_center ( picker_pos . x + ( sv_picker_size + bars_width ) * 0.5f , picker_pos . y + sv_picker_size * 0.5f ) ;
// Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
float triangle_r = wheel_r_inner - ( int ) ( sv_picker_size * 0.027f ) ;
ImVec2 triangle_pa = ImVec2 ( triangle_r , 0.0f ) ; // Hue point.
ImVec2 triangle_pb = ImVec2 ( triangle_r * - 0.5f , triangle_r * - 0.866025f ) ; // Black point.
ImVec2 triangle_pc = ImVec2 ( triangle_r * - 0.5f , triangle_r * + 0.866025f ) ; // White point.
float H , S , V ;
ColorConvertRGBtoHSV ( col [ 0 ] , col [ 1 ] , col [ 2 ] , H , S , V ) ;
bool value_changed = false , value_changed_h = false , value_changed_sv = false ;
PushItemFlag ( ImGuiItemFlags_NoNav , true ) ;
if ( flags & ImGuiColorEditFlags_PickerHueWheel )
{
// Hue wheel + SV triangle logic
InvisibleButton ( " hsv " , ImVec2 ( sv_picker_size + style . ItemInnerSpacing . x + bars_width , sv_picker_size ) ) ;
if ( IsItemActive ( ) )
{
ImVec2 initial_off = g . IO . MouseClickedPos [ 0 ] - wheel_center ;
ImVec2 current_off = g . IO . MousePos - wheel_center ;
float initial_dist2 = ImLengthSqr ( initial_off ) ;
if ( initial_dist2 > = ( wheel_r_inner - 1 ) * ( wheel_r_inner - 1 ) & & initial_dist2 < = ( wheel_r_outer + 1 ) * ( wheel_r_outer + 1 ) )
{
// Interactive with Hue wheel
H = ImAtan2 ( current_off . y , current_off . x ) / IM_PI * 0.5f ;
if ( H < 0.0f )
H + = 1.0f ;
value_changed = value_changed_h = true ;
}
float cos_hue_angle = ImCos ( - H * 2.0f * IM_PI ) ;
float sin_hue_angle = ImSin ( - H * 2.0f * IM_PI ) ;
if ( ImTriangleContainsPoint ( triangle_pa , triangle_pb , triangle_pc , ImRotate ( initial_off , cos_hue_angle , sin_hue_angle ) ) )
{
// Interacting with SV triangle
ImVec2 current_off_unrotated = ImRotate ( current_off , cos_hue_angle , sin_hue_angle ) ;
if ( ! ImTriangleContainsPoint ( triangle_pa , triangle_pb , triangle_pc , current_off_unrotated ) )
current_off_unrotated = ImTriangleClosestPoint ( triangle_pa , triangle_pb , triangle_pc , current_off_unrotated ) ;
float uu , vv , ww ;
ImTriangleBarycentricCoords ( triangle_pa , triangle_pb , triangle_pc , current_off_unrotated , uu , vv , ww ) ;
V = ImClamp ( 1.0f - vv , 0.0001f , 1.0f ) ;
S = ImClamp ( uu / V , 0.0001f , 1.0f ) ;
value_changed = value_changed_sv = true ;
}
}
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
OpenPopupOnItemClick ( " context " ) ;
}
else if ( flags & ImGuiColorEditFlags_PickerHueBar )
{
// SV rectangle logic
InvisibleButton ( " sv " , ImVec2 ( sv_picker_size , sv_picker_size ) ) ;
if ( IsItemActive ( ) )
{
S = ImSaturate ( ( io . MousePos . x - picker_pos . x ) / ( sv_picker_size - 1 ) ) ;
V = 1.0f - ImSaturate ( ( io . MousePos . y - picker_pos . y ) / ( sv_picker_size - 1 ) ) ;
value_changed = value_changed_sv = true ;
}
if ( ! ( flags & ImGuiColorEditFlags_NoOptions ) )
OpenPopupOnItemClick ( " context " ) ;
// Hue bar logic
SetCursorScreenPos ( ImVec2 ( bar0_pos_x , picker_pos . y ) ) ;
InvisibleButton ( " hue " , ImVec2 ( bars_width , sv_picker_size ) ) ;
if ( IsItemActive ( ) )
{
H = ImSaturate ( ( io . MousePos . y - picker_pos . y ) / ( sv_picker_size - 1 ) ) ;
value_changed = value_changed_h = true ;
}
}
// Alpha bar logic
if ( alpha_bar )
{
SetCursorScreenPos ( ImVec2 ( bar1_pos_x , picker_pos . y ) ) ;
InvisibleButton ( " alpha " , ImVec2 ( bars_width , sv_picker_size ) ) ;
if ( IsItemActive ( ) )
{
col [ 3 ] = 1.0f - ImSaturate ( ( io . MousePos . y - picker_pos . y ) / ( sv_picker_size - 1 ) ) ;
value_changed = true ;
}
}
PopItemFlag ( ) ; // ImGuiItemFlags_NoNav
if ( ! ( flags & ImGuiColorEditFlags_NoSidePreview ) )
{
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
BeginGroup ( ) ;
}
if ( ! ( flags & ImGuiColorEditFlags_NoLabel ) )
{
const char * label_display_end = FindRenderedTextEnd ( label ) ;
if ( label ! = label_display_end )
{
if ( ( flags & ImGuiColorEditFlags_NoSidePreview ) )
SameLine ( 0 , style . ItemInnerSpacing . x ) ;
TextUnformatted ( label , label_display_end ) ;
}
}
if ( ! ( flags & ImGuiColorEditFlags_NoSidePreview ) )
{
PushItemFlag ( ImGuiItemFlags_NoNavDefaultFocus , true ) ;
ImVec4 col_v4 ( col [ 0 ] , col [ 1 ] , col [ 2 ] , ( flags & ImGuiColorEditFlags_NoAlpha ) ? 1.0f : col [ 3 ] ) ;
if ( ( flags & ImGuiColorEditFlags_NoLabel ) )
Text ( " Current " ) ;
ColorButton ( " ##current " , col_v4 , ( flags & ( ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip ) ) , ImVec2 ( square_sz * 3 , square_sz * 2 ) ) ;
if ( ref_col ! = NULL )
{
Text ( " Original " ) ;
ImVec4 ref_col_v4 ( ref_col [ 0 ] , ref_col [ 1 ] , ref_col [ 2 ] , ( flags & ImGuiColorEditFlags_NoAlpha ) ? 1.0f : ref_col [ 3 ] ) ;
if ( ColorButton ( " ##original " , ref_col_v4 , ( flags & ( ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip ) ) , ImVec2 ( square_sz * 3 , square_sz * 2 ) ) )
{
memcpy ( col , ref_col , components * sizeof ( float ) ) ;
value_changed = true ;
}
}
PopItemFlag ( ) ;
EndGroup ( ) ;
}
// Convert back color to RGB
if ( value_changed_h | | value_changed_sv )
ColorConvertHSVtoRGB ( H > = 1.0f ? H - 10 * 1e-6 f : H , S > 0.0f ? S : 10 * 1e-6 f , V > 0.0f ? V : 1e-6 f , col [ 0 ] , col [ 1 ] , col [ 2 ] ) ;
// R,G,B and H,S,V slider color editor
bool value_changed_fix_hue_wrap = false ;
if ( ( flags & ImGuiColorEditFlags_NoInputs ) = = 0 )
{
PushItemWidth ( ( alpha_bar ? bar1_pos_x : bar0_pos_x ) + bars_width - picker_pos . x ) ;
ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf ;
ImGuiColorEditFlags sub_flags = ( flags & sub_flags_to_forward ) | ImGuiColorEditFlags_NoPicker ;
if ( flags & ImGuiColorEditFlags_RGB | | ( flags & ImGuiColorEditFlags__InputsMask ) = = 0 )
if ( ColorEdit4 ( " ##rgb " , col , sub_flags | ImGuiColorEditFlags_RGB ) )
{
// FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
// For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
value_changed_fix_hue_wrap = ( g . ActiveId ! = 0 & & ! g . ActiveIdAllowOverlap ) ;
value_changed = true ;
}
if ( flags & ImGuiColorEditFlags_HSV | | ( flags & ImGuiColorEditFlags__InputsMask ) = = 0 )
value_changed | = ColorEdit4 ( " ##hsv " , col , sub_flags | ImGuiColorEditFlags_HSV ) ;
if ( flags & ImGuiColorEditFlags_HEX | | ( flags & ImGuiColorEditFlags__InputsMask ) = = 0 )
value_changed | = ColorEdit4 ( " ##hex " , col , sub_flags | ImGuiColorEditFlags_HEX ) ;
PopItemWidth ( ) ;
}
// Try to cancel hue wrap (after ColorEdit4 call), if any
if ( value_changed_fix_hue_wrap )
{
float new_H , new_S , new_V ;
ColorConvertRGBtoHSV ( col [ 0 ] , col [ 1 ] , col [ 2 ] , new_H , new_S , new_V ) ;
if ( new_H < = 0 & & H > 0 )
{
if ( new_V < = 0 & & V ! = new_V )
ColorConvertHSVtoRGB ( H , S , new_V < = 0 ? V * 0.5f : new_V , col [ 0 ] , col [ 1 ] , col [ 2 ] ) ;
else if ( new_S < = 0 )
ColorConvertHSVtoRGB ( H , new_S < = 0 ? S * 0.5f : new_S , new_V , col [ 0 ] , col [ 1 ] , col [ 2 ] ) ;
}
}
ImVec4 hue_color_f ( 1 , 1 , 1 , 1 ) ; ColorConvertHSVtoRGB ( H , 1 , 1 , hue_color_f . x , hue_color_f . y , hue_color_f . z ) ;
ImU32 hue_color32 = ColorConvertFloat4ToU32 ( hue_color_f ) ;
ImU32 col32_no_alpha = ColorConvertFloat4ToU32 ( ImVec4 ( col [ 0 ] , col [ 1 ] , col [ 2 ] , 1.0f ) ) ;
const ImU32 hue_colors [ 6 + 1 ] = { IM_COL32 ( 255 , 0 , 0 , 255 ) , IM_COL32 ( 255 , 255 , 0 , 255 ) , IM_COL32 ( 0 , 255 , 0 , 255 ) , IM_COL32 ( 0 , 255 , 255 , 255 ) , IM_COL32 ( 0 , 0 , 255 , 255 ) , IM_COL32 ( 255 , 0 , 255 , 255 ) , IM_COL32 ( 255 , 0 , 0 , 255 ) } ;
ImVec2 sv_cursor_pos ;
if ( flags & ImGuiColorEditFlags_PickerHueWheel )
{
// Render Hue Wheel
const float aeps = 1.5f / wheel_r_outer ; // Half a pixel arc length in radians (2pi cancels out).
const int segment_per_arc = ImMax ( 4 , ( int ) wheel_r_outer / 12 ) ;
for ( int n = 0 ; n < 6 ; n + + )
{
const float a0 = ( n ) / 6.0f * 2.0f * IM_PI - aeps ;
const float a1 = ( n + 1.0f ) / 6.0f * 2.0f * IM_PI + aeps ;
const int vert_start_idx = draw_list - > VtxBuffer . Size ;
draw_list - > PathArcTo ( wheel_center , ( wheel_r_inner + wheel_r_outer ) * 0.5f , a0 , a1 , segment_per_arc ) ;
draw_list - > PathStroke ( IM_COL32_WHITE , false , wheel_thickness ) ;
const int vert_end_idx = draw_list - > VtxBuffer . Size ;
// Paint colors over existing vertices
ImVec2 gradient_p0 ( wheel_center . x + ImCos ( a0 ) * wheel_r_inner , wheel_center . y + ImSin ( a0 ) * wheel_r_inner ) ;
ImVec2 gradient_p1 ( wheel_center . x + ImCos ( a1 ) * wheel_r_inner , wheel_center . y + ImSin ( a1 ) * wheel_r_inner ) ;
ShadeVertsLinearColorGradientKeepAlpha ( draw_list , vert_start_idx , vert_end_idx , gradient_p0 , gradient_p1 , hue_colors [ n ] , hue_colors [ n + 1 ] ) ;
}
// Render Cursor + preview on Hue Wheel
float cos_hue_angle = ImCos ( H * 2.0f * IM_PI ) ;
float sin_hue_angle = ImSin ( H * 2.0f * IM_PI ) ;
ImVec2 hue_cursor_pos ( wheel_center . x + cos_hue_angle * ( wheel_r_inner + wheel_r_outer ) * 0.5f , wheel_center . y + sin_hue_angle * ( wheel_r_inner + wheel_r_outer ) * 0.5f ) ;
float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f ;
int hue_cursor_segments = ImClamp ( ( int ) ( hue_cursor_rad / 1.4f ) , 9 , 32 ) ;
draw_list - > AddCircleFilled ( hue_cursor_pos , hue_cursor_rad , hue_color32 , hue_cursor_segments ) ;
draw_list - > AddCircle ( hue_cursor_pos , hue_cursor_rad + 1 , IM_COL32 ( 128 , 128 , 128 , 255 ) , hue_cursor_segments ) ;
draw_list - > AddCircle ( hue_cursor_pos , hue_cursor_rad , IM_COL32_WHITE , hue_cursor_segments ) ;
// Render SV triangle (rotated according to hue)
ImVec2 tra = wheel_center + ImRotate ( triangle_pa , cos_hue_angle , sin_hue_angle ) ;
ImVec2 trb = wheel_center + ImRotate ( triangle_pb , cos_hue_angle , sin_hue_angle ) ;
ImVec2 trc = wheel_center + ImRotate ( triangle_pc , cos_hue_angle , sin_hue_angle ) ;
ImVec2 uv_white = GetFontTexUvWhitePixel ( ) ;
draw_list - > PrimReserve ( 6 , 6 ) ;
draw_list - > PrimVtx ( tra , uv_white , hue_color32 ) ;
draw_list - > PrimVtx ( trb , uv_white , hue_color32 ) ;
draw_list - > PrimVtx ( trc , uv_white , IM_COL32_WHITE ) ;
draw_list - > PrimVtx ( tra , uv_white , IM_COL32_BLACK_TRANS ) ;
draw_list - > PrimVtx ( trb , uv_white , IM_COL32_BLACK ) ;
draw_list - > PrimVtx ( trc , uv_white , IM_COL32_BLACK_TRANS ) ;
draw_list - > AddTriangle ( tra , trb , trc , IM_COL32 ( 128 , 128 , 128 , 255 ) , 1.5f ) ;
sv_cursor_pos = ImLerp ( ImLerp ( trc , tra , ImSaturate ( S ) ) , trb , ImSaturate ( 1 - V ) ) ;
}
else if ( flags & ImGuiColorEditFlags_PickerHueBar )
{
// Render SV Square
draw_list - > AddRectFilledMultiColor ( picker_pos , picker_pos + ImVec2 ( sv_picker_size , sv_picker_size ) , IM_COL32_WHITE , hue_color32 , hue_color32 , IM_COL32_WHITE ) ;
draw_list - > AddRectFilledMultiColor ( picker_pos , picker_pos + ImVec2 ( sv_picker_size , sv_picker_size ) , IM_COL32_BLACK_TRANS , IM_COL32_BLACK_TRANS , IM_COL32_BLACK , IM_COL32_BLACK ) ;
RenderFrameBorder ( picker_pos , picker_pos + ImVec2 ( sv_picker_size , sv_picker_size ) , 0.0f ) ;
sv_cursor_pos . x = ImClamp ( ( float ) ( int ) ( picker_pos . x + ImSaturate ( S ) * sv_picker_size + 0.5f ) , picker_pos . x + 2 , picker_pos . x + sv_picker_size - 2 ) ; // Sneakily prevent the circle to stick out too much
sv_cursor_pos . y = ImClamp ( ( float ) ( int ) ( picker_pos . y + ImSaturate ( 1 - V ) * sv_picker_size + 0.5f ) , picker_pos . y + 2 , picker_pos . y + sv_picker_size - 2 ) ;
// Render Hue Bar
for ( int i = 0 ; i < 6 ; + + i )
draw_list - > AddRectFilledMultiColor ( ImVec2 ( bar0_pos_x , picker_pos . y + i * ( sv_picker_size / 6 ) ) , ImVec2 ( bar0_pos_x + bars_width , picker_pos . y + ( i + 1 ) * ( sv_picker_size / 6 ) ) , hue_colors [ i ] , hue_colors [ i ] , hue_colors [ i + 1 ] , hue_colors [ i + 1 ] ) ;
float bar0_line_y = ( float ) ( int ) ( picker_pos . y + H * sv_picker_size + 0.5f ) ;
RenderFrameBorder ( ImVec2 ( bar0_pos_x , picker_pos . y ) , ImVec2 ( bar0_pos_x + bars_width , picker_pos . y + sv_picker_size ) , 0.0f ) ;
RenderArrowsForVerticalBar ( draw_list , ImVec2 ( bar0_pos_x - 1 , bar0_line_y ) , ImVec2 ( bars_triangles_half_sz + 1 , bars_triangles_half_sz ) , bars_width + 2.0f ) ;
}
// Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f ;
draw_list - > AddCircleFilled ( sv_cursor_pos , sv_cursor_rad , col32_no_alpha , 12 ) ;
draw_list - > AddCircle ( sv_cursor_pos , sv_cursor_rad + 1 , IM_COL32 ( 128 , 128 , 128 , 255 ) , 12 ) ;
draw_list - > AddCircle ( sv_cursor_pos , sv_cursor_rad , IM_COL32_WHITE , 12 ) ;
// Render alpha bar
if ( alpha_bar )
{
float alpha = ImSaturate ( col [ 3 ] ) ;
ImRect bar1_bb ( bar1_pos_x , picker_pos . y , bar1_pos_x + bars_width , picker_pos . y + sv_picker_size ) ;
RenderColorRectWithAlphaCheckerboard ( bar1_bb . Min , bar1_bb . Max , IM_COL32 ( 0 , 0 , 0 , 0 ) , bar1_bb . GetWidth ( ) / 2.0f , ImVec2 ( 0.0f , 0.0f ) ) ;
draw_list - > AddRectFilledMultiColor ( bar1_bb . Min , bar1_bb . Max , col32_no_alpha , col32_no_alpha , col32_no_alpha & ~ IM_COL32_A_MASK , col32_no_alpha & ~ IM_COL32_A_MASK ) ;
float bar1_line_y = ( float ) ( int ) ( picker_pos . y + ( 1.0f - alpha ) * sv_picker_size + 0.5f ) ;
RenderFrameBorder ( bar1_bb . Min , bar1_bb . Max , 0.0f ) ;
RenderArrowsForVerticalBar ( draw_list , ImVec2 ( bar1_pos_x - 1 , bar1_line_y ) , ImVec2 ( bars_triangles_half_sz + 1 , bars_triangles_half_sz ) , bars_width + 2.0f ) ;
}
EndGroup ( ) ;
if ( value_changed & & memcmp ( backup_initial_col , col , components * sizeof ( float ) ) = = 0 )
value_changed = false ;
if ( value_changed )
MarkItemEdited ( window - > DC . LastItemId ) ;
PopID ( ) ;
return value_changed ;
}
// A little colored square. Return true when clicked.
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
bool ImGui : : ColorButton ( const char * desc_id , const ImVec4 & col , ImGuiColorEditFlags flags , ImVec2 size )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiID id = window - > GetID ( desc_id ) ;
float default_size = GetFrameHeight ( ) ;
if ( size . x = = 0.0f )
size . x = default_size ;
if ( size . y = = 0.0f )
size . y = default_size ;
const ImRect bb ( window - > DC . CursorPos , window - > DC . CursorPos + size ) ;
ItemSize ( bb , ( size . y > = default_size ) ? g . Style . FramePadding . y : 0.0f ) ;
if ( ! ItemAdd ( bb , id ) )
return false ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held ) ;
if ( flags & ImGuiColorEditFlags_NoAlpha )
flags & = ~ ( ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf ) ;
ImVec4 col_without_alpha ( col . x , col . y , col . z , 1.0f ) ;
float grid_step = ImMin ( size . x , size . y ) / 2.99f ;
float rounding = ImMin ( g . Style . FrameRounding , grid_step * 0.5f ) ;
ImRect bb_inner = bb ;
float off = - 0.75f ; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
bb_inner . Expand ( off ) ;
if ( ( flags & ImGuiColorEditFlags_AlphaPreviewHalf ) & & col . w < 1.0f )
{
float mid_x = ( float ) ( int ) ( ( bb_inner . Min . x + bb_inner . Max . x ) * 0.5f + 0.5f ) ;
RenderColorRectWithAlphaCheckerboard ( ImVec2 ( bb_inner . Min . x + grid_step , bb_inner . Min . y ) , bb_inner . Max , GetColorU32 ( col ) , grid_step , ImVec2 ( - grid_step + off , off ) , rounding , ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight ) ;
window - > DrawList - > AddRectFilled ( bb_inner . Min , ImVec2 ( mid_x , bb_inner . Max . y ) , GetColorU32 ( col_without_alpha ) , rounding , ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft ) ;
}
else
{
// Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
ImVec4 col_source = ( flags & ImGuiColorEditFlags_AlphaPreview ) ? col : col_without_alpha ;
if ( col_source . w < 1.0f )
RenderColorRectWithAlphaCheckerboard ( bb_inner . Min , bb_inner . Max , GetColorU32 ( col_source ) , grid_step , ImVec2 ( off , off ) , rounding ) ;
else
window - > DrawList - > AddRectFilled ( bb_inner . Min , bb_inner . Max , GetColorU32 ( col_source ) , rounding , ImDrawCornerFlags_All ) ;
}
RenderNavHighlight ( bb , id ) ;
if ( g . Style . FrameBorderSize > 0.0f )
RenderFrameBorder ( bb . Min , bb . Max , rounding ) ;
else
window - > DrawList - > AddRect ( bb . Min , bb . Max , GetColorU32 ( ImGuiCol_FrameBg ) , rounding ) ; // Color button are often in need of some sort of border
// Drag and Drop Source
// NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
if ( g . ActiveId = = id & & ! ( flags & ImGuiColorEditFlags_NoDragDrop ) & & BeginDragDropSource ( ) )
{
if ( flags & ImGuiColorEditFlags_NoAlpha )
SetDragDropPayload ( IMGUI_PAYLOAD_TYPE_COLOR_3F , & col , sizeof ( float ) * 3 , ImGuiCond_Once ) ;
else
SetDragDropPayload ( IMGUI_PAYLOAD_TYPE_COLOR_4F , & col , sizeof ( float ) * 4 , ImGuiCond_Once ) ;
ColorButton ( desc_id , col , flags ) ;
SameLine ( ) ;
TextUnformatted ( " Color " ) ;
EndDragDropSource ( ) ;
}
// Tooltip
if ( ! ( flags & ImGuiColorEditFlags_NoTooltip ) & & hovered )
ColorTooltip ( desc_id , & col . x , flags & ( ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf ) ) ;
if ( pressed )
MarkItemEdited ( id ) ;
return pressed ;
}
void ImGui : : SetColorEditOptions ( ImGuiColorEditFlags flags )
{
ImGuiContext & g = * GImGui ;
if ( ( flags & ImGuiColorEditFlags__InputsMask ) = = 0 )
flags | = ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask ;
if ( ( flags & ImGuiColorEditFlags__DataTypeMask ) = = 0 )
flags | = ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask ;
if ( ( flags & ImGuiColorEditFlags__PickerMask ) = = 0 )
flags | = ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask ;
IM_ASSERT ( ImIsPowerOfTwo ( ( int ) ( flags & ImGuiColorEditFlags__InputsMask ) ) ) ; // Check only 1 option is selected
IM_ASSERT ( ImIsPowerOfTwo ( ( int ) ( flags & ImGuiColorEditFlags__DataTypeMask ) ) ) ; // Check only 1 option is selected
IM_ASSERT ( ImIsPowerOfTwo ( ( int ) ( flags & ImGuiColorEditFlags__PickerMask ) ) ) ; // Check only 1 option is selected
g . ColorEditOptions = flags ;
}
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
void ImGui : : ColorTooltip ( const char * text , const float * col , ImGuiColorEditFlags flags )
{
ImGuiContext & g = * GImGui ;
int cr = IM_F32_TO_INT8_SAT ( col [ 0 ] ) , cg = IM_F32_TO_INT8_SAT ( col [ 1 ] ) , cb = IM_F32_TO_INT8_SAT ( col [ 2 ] ) , ca = ( flags & ImGuiColorEditFlags_NoAlpha ) ? 255 : IM_F32_TO_INT8_SAT ( col [ 3 ] ) ;
BeginTooltipEx ( 0 , true ) ;
const char * text_end = text ? FindRenderedTextEnd ( text , NULL ) : text ;
if ( text_end > text )
{
TextUnformatted ( text , text_end ) ;
Separator ( ) ;
}
ImVec2 sz ( g . FontSize * 3 + g . Style . FramePadding . y * 2 , g . FontSize * 3 + g . Style . FramePadding . y * 2 ) ;
ColorButton ( " ##preview " , ImVec4 ( col [ 0 ] , col [ 1 ] , col [ 2 ] , col [ 3 ] ) , ( flags & ( ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf ) ) | ImGuiColorEditFlags_NoTooltip , sz ) ;
SameLine ( ) ;
if ( flags & ImGuiColorEditFlags_NoAlpha )
Text ( " #%02X%02X%02X \n R: %d, G: %d, B: %d \n (%.3f, %.3f, %.3f) " , cr , cg , cb , cr , cg , cb , col [ 0 ] , col [ 1 ] , col [ 2 ] ) ;
else
Text ( " #%02X%02X%02X%02X \n R:%d, G:%d, B:%d, A:%d \n (%.3f, %.3f, %.3f, %.3f) " , cr , cg , cb , ca , cr , cg , cb , ca , col [ 0 ] , col [ 1 ] , col [ 2 ] , col [ 3 ] ) ;
EndTooltip ( ) ;
}
void ImGui : : ColorEditOptionsPopup ( const float * col , ImGuiColorEditFlags flags )
{
bool allow_opt_inputs = ! ( flags & ImGuiColorEditFlags__InputsMask ) ;
bool allow_opt_datatype = ! ( flags & ImGuiColorEditFlags__DataTypeMask ) ;
if ( ( ! allow_opt_inputs & & ! allow_opt_datatype ) | | ! BeginPopup ( " context " ) )
return ;
ImGuiContext & g = * GImGui ;
ImGuiColorEditFlags opts = g . ColorEditOptions ;
if ( allow_opt_inputs )
{
if ( RadioButton ( " RGB " , ( opts & ImGuiColorEditFlags_RGB ) ! = 0 ) ) opts = ( opts & ~ ImGuiColorEditFlags__InputsMask ) | ImGuiColorEditFlags_RGB ;
if ( RadioButton ( " HSV " , ( opts & ImGuiColorEditFlags_HSV ) ! = 0 ) ) opts = ( opts & ~ ImGuiColorEditFlags__InputsMask ) | ImGuiColorEditFlags_HSV ;
if ( RadioButton ( " HEX " , ( opts & ImGuiColorEditFlags_HEX ) ! = 0 ) ) opts = ( opts & ~ ImGuiColorEditFlags__InputsMask ) | ImGuiColorEditFlags_HEX ;
}
if ( allow_opt_datatype )
{
if ( allow_opt_inputs ) Separator ( ) ;
if ( RadioButton ( " 0..255 " , ( opts & ImGuiColorEditFlags_Uint8 ) ! = 0 ) ) opts = ( opts & ~ ImGuiColorEditFlags__DataTypeMask ) | ImGuiColorEditFlags_Uint8 ;
if ( RadioButton ( " 0.00..1.00 " , ( opts & ImGuiColorEditFlags_Float ) ! = 0 ) ) opts = ( opts & ~ ImGuiColorEditFlags__DataTypeMask ) | ImGuiColorEditFlags_Float ;
}
if ( allow_opt_inputs | | allow_opt_datatype )
Separator ( ) ;
if ( Button ( " Copy as.. " , ImVec2 ( - 1 , 0 ) ) )
OpenPopup ( " Copy " ) ;
if ( BeginPopup ( " Copy " ) )
{
int cr = IM_F32_TO_INT8_SAT ( col [ 0 ] ) , cg = IM_F32_TO_INT8_SAT ( col [ 1 ] ) , cb = IM_F32_TO_INT8_SAT ( col [ 2 ] ) , ca = ( flags & ImGuiColorEditFlags_NoAlpha ) ? 255 : IM_F32_TO_INT8_SAT ( col [ 3 ] ) ;
char buf [ 64 ] ;
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " (%.3ff, %.3ff, %.3ff, %.3ff) " , col [ 0 ] , col [ 1 ] , col [ 2 ] , ( flags & ImGuiColorEditFlags_NoAlpha ) ? 1.0f : col [ 3 ] ) ;
if ( Selectable ( buf ) )
SetClipboardText ( buf ) ;
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " (%d,%d,%d,%d) " , cr , cg , cb , ca ) ;
if ( Selectable ( buf ) )
SetClipboardText ( buf ) ;
if ( flags & ImGuiColorEditFlags_NoAlpha )
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " 0x%02X%02X%02X " , cr , cg , cb ) ;
else
ImFormatString ( buf , IM_ARRAYSIZE ( buf ) , " 0x%02X%02X%02X%02X " , cr , cg , cb , ca ) ;
if ( Selectable ( buf ) )
SetClipboardText ( buf ) ;
EndPopup ( ) ;
}
g . ColorEditOptions = opts ;
EndPopup ( ) ;
}
void ImGui : : ColorPickerOptionsPopup ( const float * ref_col , ImGuiColorEditFlags flags )
{
bool allow_opt_picker = ! ( flags & ImGuiColorEditFlags__PickerMask ) ;
bool allow_opt_alpha_bar = ! ( flags & ImGuiColorEditFlags_NoAlpha ) & & ! ( flags & ImGuiColorEditFlags_AlphaBar ) ;
if ( ( ! allow_opt_picker & & ! allow_opt_alpha_bar ) | | ! ImGui : : BeginPopup ( " context " ) )
return ;
ImGuiContext & g = * GImGui ;
if ( allow_opt_picker )
{
ImVec2 picker_size ( g . FontSize * 8 , ImMax ( g . FontSize * 8 - ( ImGui : : GetFrameHeight ( ) + g . Style . ItemInnerSpacing . x ) , 1.0f ) ) ; // FIXME: Picker size copied from main picker function
ImGui : : PushItemWidth ( picker_size . x ) ;
for ( int picker_type = 0 ; picker_type < 2 ; picker_type + + )
{
// Draw small/thumbnail version of each picker type (over an invisible button for selection)
if ( picker_type > 0 ) ImGui : : Separator ( ) ;
ImGui : : PushID ( picker_type ) ;
ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | ( flags & ImGuiColorEditFlags_NoAlpha ) ;
if ( picker_type = = 0 ) picker_flags | = ImGuiColorEditFlags_PickerHueBar ;
if ( picker_type = = 1 ) picker_flags | = ImGuiColorEditFlags_PickerHueWheel ;
ImVec2 backup_pos = ImGui : : GetCursorScreenPos ( ) ;
if ( ImGui : : Selectable ( " ##selectable " , false , 0 , picker_size ) ) // By default, Selectable() is closing popup
g . ColorEditOptions = ( g . ColorEditOptions & ~ ImGuiColorEditFlags__PickerMask ) | ( picker_flags & ImGuiColorEditFlags__PickerMask ) ;
ImGui : : SetCursorScreenPos ( backup_pos ) ;
ImVec4 dummy_ref_col ;
memcpy ( & dummy_ref_col . x , ref_col , sizeof ( float ) * ( picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4 ) ) ;
ImGui : : ColorPicker4 ( " ##dummypicker " , & dummy_ref_col . x , picker_flags ) ;
ImGui : : PopID ( ) ;
}
ImGui : : PopItemWidth ( ) ;
}
if ( allow_opt_alpha_bar )
{
if ( allow_opt_picker ) ImGui : : Separator ( ) ;
ImGui : : CheckboxFlags ( " Alpha Bar " , ( unsigned int * ) & g . ColorEditOptions , ImGuiColorEditFlags_AlphaBar ) ;
}
ImGui : : EndPopup ( ) ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Trees
// - TreeNode()
// - TreeNodeV()
// - TreeNodeEx()
// - TreeNodeExV()
// - TreeNodeBehavior() [Internal]
// - TreePush()
// - TreePop()
// - TreeAdvanceToLabelPos()
// - GetTreeNodeToLabelSpacing()
// - SetNextTreeNodeOpen()
// - CollapsingHeader()
//-------------------------------------------------------------------------
2018-08-30 13:03:21 +00:00
bool ImGui : : TreeNode ( const char * str_id , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
bool is_open = TreeNodeExV ( str_id , 0 , fmt , args ) ;
va_end ( args ) ;
return is_open ;
}
bool ImGui : : TreeNode ( const void * ptr_id , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
bool is_open = TreeNodeExV ( ptr_id , 0 , fmt , args ) ;
va_end ( args ) ;
return is_open ;
}
bool ImGui : : TreeNode ( const char * label )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
return TreeNodeBehavior ( window - > GetID ( label ) , 0 , label , NULL ) ;
}
bool ImGui : : TreeNodeV ( const char * str_id , const char * fmt , va_list args )
{
return TreeNodeExV ( str_id , 0 , fmt , args ) ;
}
bool ImGui : : TreeNodeV ( const void * ptr_id , const char * fmt , va_list args )
{
return TreeNodeExV ( ptr_id , 0 , fmt , args ) ;
}
bool ImGui : : TreeNodeEx ( const char * label , ImGuiTreeNodeFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
return TreeNodeBehavior ( window - > GetID ( label ) , flags , label , NULL ) ;
}
bool ImGui : : TreeNodeEx ( const char * str_id , ImGuiTreeNodeFlags flags , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
bool is_open = TreeNodeExV ( str_id , flags , fmt , args ) ;
va_end ( args ) ;
return is_open ;
}
bool ImGui : : TreeNodeEx ( const void * ptr_id , ImGuiTreeNodeFlags flags , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
bool is_open = TreeNodeExV ( ptr_id , flags , fmt , args ) ;
va_end ( args ) ;
return is_open ;
}
bool ImGui : : TreeNodeExV ( const char * str_id , ImGuiTreeNodeFlags flags , const char * fmt , va_list args )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const char * label_end = g . TempBuffer + ImFormatStringV ( g . TempBuffer , IM_ARRAYSIZE ( g . TempBuffer ) , fmt , args ) ;
return TreeNodeBehavior ( window - > GetID ( str_id ) , flags , g . TempBuffer , label_end ) ;
}
bool ImGui : : TreeNodeExV ( const void * ptr_id , ImGuiTreeNodeFlags flags , const char * fmt , va_list args )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const char * label_end = g . TempBuffer + ImFormatStringV ( g . TempBuffer , IM_ARRAYSIZE ( g . TempBuffer ) , fmt , args ) ;
return TreeNodeBehavior ( window - > GetID ( ptr_id ) , flags , g . TempBuffer , label_end ) ;
}
bool ImGui : : TreeNodeBehaviorIsOpen ( ImGuiID id , ImGuiTreeNodeFlags flags )
{
if ( flags & ImGuiTreeNodeFlags_Leaf )
return true ;
// We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
ImGuiStorage * storage = window - > DC . StateStorage ;
bool is_open ;
if ( g . NextTreeNodeOpenCond ! = 0 )
{
if ( g . NextTreeNodeOpenCond & ImGuiCond_Always )
{
is_open = g . NextTreeNodeOpenVal ;
storage - > SetInt ( id , is_open ) ;
}
else
{
// We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
const int stored_value = storage - > GetInt ( id , - 1 ) ;
if ( stored_value = = - 1 )
{
is_open = g . NextTreeNodeOpenVal ;
storage - > SetInt ( id , is_open ) ;
}
else
{
is_open = stored_value ! = 0 ;
}
}
g . NextTreeNodeOpenCond = 0 ;
}
else
{
is_open = storage - > GetInt ( id , ( flags & ImGuiTreeNodeFlags_DefaultOpen ) ? 1 : 0 ) ! = 0 ;
}
// When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
// NB- If we are above max depth we still allow manually opened nodes to be logged.
if ( g . LogEnabled & & ! ( flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog ) & & window - > DC . TreeDepth < g . LogAutoExpandMaxDepth )
is_open = true ;
return is_open ;
}
bool ImGui : : TreeNodeBehavior ( ImGuiID id , ImGuiTreeNodeFlags flags , const char * label , const char * label_end )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const bool display_frame = ( flags & ImGuiTreeNodeFlags_Framed ) ! = 0 ;
const ImVec2 padding = ( display_frame | | ( flags & ImGuiTreeNodeFlags_FramePadding ) ) ? style . FramePadding : ImVec2 ( style . FramePadding . x , 0.0f ) ;
if ( ! label_end )
label_end = FindRenderedTextEnd ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , label_end , false ) ;
// We vertically grow up to current line height up the typical widget height.
const float text_base_offset_y = ImMax ( padding . y , window - > DC . CurrentLineTextBaseOffset ) ; // Latch before ItemSize changes it
const float frame_height = ImMax ( ImMin ( window - > DC . CurrentLineSize . y , g . FontSize + style . FramePadding . y * 2 ) , label_size . y + padding . y * 2 ) ;
ImRect frame_bb = ImRect ( window - > DC . CursorPos , ImVec2 ( window - > Pos . x + GetContentRegionMax ( ) . x , window - > DC . CursorPos . y + frame_height ) ) ;
if ( display_frame )
{
// Framed header expand a little outside the default padding
frame_bb . Min . x - = ( float ) ( int ) ( window - > WindowPadding . x * 0.5f ) - 1 ;
frame_bb . Max . x + = ( float ) ( int ) ( window - > WindowPadding . x * 0.5f ) - 1 ;
}
const float text_offset_x = ( g . FontSize + ( display_frame ? padding . x * 3 : padding . x * 2 ) ) ; // Collapser arrow width + Spacing
const float text_width = g . FontSize + ( label_size . x > 0.0f ? label_size . x + padding . x * 2 : 0.0f ) ; // Include collapser
ItemSize ( ImVec2 ( text_width , frame_height ) , text_base_offset_y ) ;
// For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
// (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
const ImRect interact_bb = display_frame ? frame_bb : ImRect ( frame_bb . Min . x , frame_bb . Min . y , frame_bb . Min . x + text_width + style . ItemSpacing . x * 2 , frame_bb . Max . y ) ;
bool is_open = TreeNodeBehaviorIsOpen ( id , flags ) ;
// Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
if ( is_open & & ! g . NavIdIsAlive & & ( flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere ) & & ! ( flags & ImGuiTreeNodeFlags_NoTreePushOnOpen ) )
window - > DC . TreeDepthMayJumpToParentOnPop | = ( 1 < < window - > DC . TreeDepth ) ;
bool item_add = ItemAdd ( interact_bb , id ) ;
window - > DC . LastItemStatusFlags | = ImGuiItemStatusFlags_HasDisplayRect ;
window - > DC . LastItemDisplayRect = frame_bb ;
if ( ! item_add )
{
if ( is_open & & ! ( flags & ImGuiTreeNodeFlags_NoTreePushOnOpen ) )
TreePushRawID ( id ) ;
return is_open ;
}
// Flags that affects opening behavior:
// - 0(default) ..................... single-click anywhere to open
// - OpenOnDoubleClick .............. double-click anywhere to open
// - OpenOnArrow .................... single-click on arrow to open
// - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ( ( flags & ImGuiTreeNodeFlags_AllowItemOverlap ) ? ImGuiButtonFlags_AllowItemOverlap : 0 ) ;
if ( ! ( flags & ImGuiTreeNodeFlags_Leaf ) )
button_flags | = ImGuiButtonFlags_PressedOnDragDropHold ;
if ( flags & ImGuiTreeNodeFlags_OpenOnDoubleClick )
button_flags | = ImGuiButtonFlags_PressedOnDoubleClick | ( ( flags & ImGuiTreeNodeFlags_OpenOnArrow ) ? ImGuiButtonFlags_PressedOnClickRelease : 0 ) ;
bool hovered , held , pressed = ButtonBehavior ( interact_bb , id , & hovered , & held , button_flags ) ;
if ( ! ( flags & ImGuiTreeNodeFlags_Leaf ) )
{
bool toggled = false ;
if ( pressed )
{
toggled = ! ( flags & ( ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick ) ) | | ( g . NavActivateId = = id ) ;
if ( flags & ImGuiTreeNodeFlags_OpenOnArrow )
toggled | = IsMouseHoveringRect ( interact_bb . Min , ImVec2 ( interact_bb . Min . x + text_offset_x , interact_bb . Max . y ) ) & & ( ! g . NavDisableMouseHover ) ;
if ( flags & ImGuiTreeNodeFlags_OpenOnDoubleClick )
toggled | = g . IO . MouseDoubleClicked [ 0 ] ;
if ( g . DragDropActive & & is_open ) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
toggled = false ;
}
if ( g . NavId = = id & & g . NavMoveRequest & & g . NavMoveDir = = ImGuiDir_Left & & is_open )
{
toggled = true ;
NavMoveRequestCancel ( ) ;
}
if ( g . NavId = = id & & g . NavMoveRequest & & g . NavMoveDir = = ImGuiDir_Right & & ! is_open ) // If there's something upcoming on the line we may want to give it the priority?
{
toggled = true ;
NavMoveRequestCancel ( ) ;
}
if ( toggled )
{
is_open = ! is_open ;
window - > DC . StateStorage - > SetInt ( id , is_open ) ;
}
}
if ( flags & ImGuiTreeNodeFlags_AllowItemOverlap )
SetItemAllowOverlap ( ) ;
// Render
const ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header ) ;
const ImVec2 text_pos = frame_bb . Min + ImVec2 ( text_offset_x , text_base_offset_y ) ;
if ( display_frame )
{
// Framed type
RenderFrame ( frame_bb . Min , frame_bb . Max , col , true , style . FrameRounding ) ;
RenderNavHighlight ( frame_bb , id , ImGuiNavHighlightFlags_TypeThin ) ;
RenderArrow ( frame_bb . Min + ImVec2 ( padding . x , text_base_offset_y ) , is_open ? ImGuiDir_Down : ImGuiDir_Right , 1.0f ) ;
if ( g . LogEnabled )
{
// NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
const char log_prefix [ ] = " \n ## " ;
const char log_suffix [ ] = " ## " ;
LogRenderedText ( & text_pos , log_prefix , log_prefix + 3 ) ;
RenderTextClipped ( text_pos , frame_bb . Max , label , label_end , & label_size ) ;
LogRenderedText ( & text_pos , log_suffix + 1 , log_suffix + 3 ) ;
}
else
{
RenderTextClipped ( text_pos , frame_bb . Max , label , label_end , & label_size ) ;
}
}
else
{
// Unframed typed for tree nodes
if ( hovered | | ( flags & ImGuiTreeNodeFlags_Selected ) )
{
RenderFrame ( frame_bb . Min , frame_bb . Max , col , false ) ;
RenderNavHighlight ( frame_bb , id , ImGuiNavHighlightFlags_TypeThin ) ;
}
if ( flags & ImGuiTreeNodeFlags_Bullet )
RenderBullet ( frame_bb . Min + ImVec2 ( text_offset_x * 0.5f , g . FontSize * 0.50f + text_base_offset_y ) ) ;
else if ( ! ( flags & ImGuiTreeNodeFlags_Leaf ) )
RenderArrow ( frame_bb . Min + ImVec2 ( padding . x , g . FontSize * 0.15f + text_base_offset_y ) , is_open ? ImGuiDir_Down : ImGuiDir_Right , 0.70f ) ;
if ( g . LogEnabled )
LogRenderedText ( & text_pos , " > " ) ;
RenderText ( text_pos , label , label_end , false ) ;
}
if ( is_open & & ! ( flags & ImGuiTreeNodeFlags_NoTreePushOnOpen ) )
TreePushRawID ( id ) ;
return is_open ;
}
void ImGui : : TreePush ( const char * str_id )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
Indent ( ) ;
window - > DC . TreeDepth + + ;
PushID ( str_id ? str_id : " #TreePush " ) ;
}
void ImGui : : TreePush ( const void * ptr_id )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
Indent ( ) ;
window - > DC . TreeDepth + + ;
PushID ( ptr_id ? ptr_id : ( const void * ) " #TreePush " ) ;
}
void ImGui : : TreePushRawID ( ImGuiID id )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
Indent ( ) ;
window - > DC . TreeDepth + + ;
window - > IDStack . push_back ( id ) ;
}
void ImGui : : TreePop ( )
{
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
Unindent ( ) ;
window - > DC . TreeDepth - - ;
if ( g . NavMoveDir = = ImGuiDir_Left & & g . NavWindow = = window & & NavMoveRequestButNoResultYet ( ) )
if ( g . NavIdIsAlive & & ( window - > DC . TreeDepthMayJumpToParentOnPop & ( 1 < < window - > DC . TreeDepth ) ) )
{
SetNavID ( window - > IDStack . back ( ) , g . NavLayer ) ;
NavMoveRequestCancel ( ) ;
}
window - > DC . TreeDepthMayJumpToParentOnPop & = ( 1 < < window - > DC . TreeDepth ) - 1 ;
IM_ASSERT ( window - > IDStack . Size > 1 ) ; // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
PopID ( ) ;
}
void ImGui : : TreeAdvanceToLabelPos ( )
{
ImGuiContext & g = * GImGui ;
g . CurrentWindow - > DC . CursorPos . x + = GetTreeNodeToLabelSpacing ( ) ;
}
// Horizontal distance preceding label when using TreeNode() or Bullet()
float ImGui : : GetTreeNodeToLabelSpacing ( )
{
ImGuiContext & g = * GImGui ;
return g . FontSize + ( g . Style . FramePadding . x * 2.0f ) ;
}
void ImGui : : SetNextTreeNodeOpen ( bool is_open , ImGuiCond cond )
{
ImGuiContext & g = * GImGui ;
if ( g . CurrentWindow - > SkipItems )
return ;
g . NextTreeNodeOpenVal = is_open ;
g . NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always ;
}
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
bool ImGui : : CollapsingHeader ( const char * label , ImGuiTreeNodeFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
return TreeNodeBehavior ( window - > GetID ( label ) , flags | ImGuiTreeNodeFlags_CollapsingHeader , label ) ;
}
bool ImGui : : CollapsingHeader ( const char * label , bool * p_open , ImGuiTreeNodeFlags flags )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
if ( p_open & & ! * p_open )
return false ;
ImGuiID id = window - > GetID ( label ) ;
bool is_open = TreeNodeBehavior ( id , flags | ImGuiTreeNodeFlags_CollapsingHeader | ( p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0 ) , label ) ;
if ( p_open )
{
// Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
ImGuiContext & g = * GImGui ;
ImGuiItemHoveredDataBackup last_item_backup ;
float button_radius = g . FontSize * 0.5f ;
ImVec2 button_center = ImVec2 ( ImMin ( window - > DC . LastItemRect . Max . x , window - > ClipRect . Max . x ) - g . Style . FramePadding . x - button_radius , window - > DC . LastItemRect . GetCenter ( ) . y ) ;
if ( CloseButton ( window - > GetID ( ( void * ) ( intptr_t ) ( id + 1 ) ) , button_center , button_radius ) )
* p_open = false ;
last_item_backup . Restore ( ) ;
}
return is_open ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Selectables
// - Selectable()
//-------------------------------------------------------------------------
2018-08-30 13:03:21 +00:00
// Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
bool ImGui : : Selectable ( const char * label , bool selected , ImGuiSelectableFlags flags , const ImVec2 & size_arg )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
if ( ( flags & ImGuiSelectableFlags_SpanAllColumns ) & & window - > DC . ColumnsSet ) // FIXME-OPT: Avoid if vertically clipped.
PopClipRect ( ) ;
ImGuiID id = window - > GetID ( label ) ;
ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
ImVec2 size ( size_arg . x ! = 0.0f ? size_arg . x : label_size . x , size_arg . y ! = 0.0f ? size_arg . y : label_size . y ) ;
ImVec2 pos = window - > DC . CursorPos ;
pos . y + = window - > DC . CurrentLineTextBaseOffset ;
ImRect bb_inner ( pos , pos + size ) ;
ItemSize ( bb_inner ) ;
// Fill horizontal space.
ImVec2 window_padding = window - > WindowPadding ;
float max_x = ( flags & ImGuiSelectableFlags_SpanAllColumns ) ? GetWindowContentRegionMax ( ) . x : GetContentRegionMax ( ) . x ;
float w_draw = ImMax ( label_size . x , window - > Pos . x + max_x - window_padding . x - window - > DC . CursorPos . x ) ;
ImVec2 size_draw ( ( size_arg . x ! = 0 & & ! ( flags & ImGuiSelectableFlags_DrawFillAvailWidth ) ) ? size_arg . x : w_draw , size_arg . y ! = 0.0f ? size_arg . y : size . y ) ;
ImRect bb ( pos , pos + size_draw ) ;
if ( size_arg . x = = 0.0f | | ( flags & ImGuiSelectableFlags_DrawFillAvailWidth ) )
bb . Max . x + = window_padding . x ;
// Selectables are tightly packed together, we extend the box to cover spacing between selectable.
float spacing_L = ( float ) ( int ) ( style . ItemSpacing . x * 0.5f ) ;
float spacing_U = ( float ) ( int ) ( style . ItemSpacing . y * 0.5f ) ;
float spacing_R = style . ItemSpacing . x - spacing_L ;
float spacing_D = style . ItemSpacing . y - spacing_U ;
bb . Min . x - = spacing_L ;
bb . Min . y - = spacing_U ;
bb . Max . x + = spacing_R ;
bb . Max . y + = spacing_D ;
if ( ! ItemAdd ( bb , ( flags & ImGuiSelectableFlags_Disabled ) ? 0 : id ) )
{
if ( ( flags & ImGuiSelectableFlags_SpanAllColumns ) & & window - > DC . ColumnsSet )
PushColumnClipRect ( ) ;
return false ;
}
// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
ImGuiButtonFlags button_flags = 0 ;
if ( flags & ImGuiSelectableFlags_NoHoldingActiveID ) button_flags | = ImGuiButtonFlags_NoHoldingActiveID ;
if ( flags & ImGuiSelectableFlags_PressedOnClick ) button_flags | = ImGuiButtonFlags_PressedOnClick ;
if ( flags & ImGuiSelectableFlags_PressedOnRelease ) button_flags | = ImGuiButtonFlags_PressedOnRelease ;
if ( flags & ImGuiSelectableFlags_Disabled ) button_flags | = ImGuiButtonFlags_Disabled ;
if ( flags & ImGuiSelectableFlags_AllowDoubleClick ) button_flags | = ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick ;
bool hovered , held ;
bool pressed = ButtonBehavior ( bb , id , & hovered , & held , button_flags ) ;
if ( flags & ImGuiSelectableFlags_Disabled )
selected = false ;
// Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
if ( pressed | | hovered )
if ( ! g . NavDisableMouseHover & & g . NavWindow = = window & & g . NavLayer = = window - > DC . NavLayerCurrent )
{
g . NavDisableHighlight = true ;
SetNavID ( id , window - > DC . NavLayerCurrent ) ;
}
if ( pressed )
MarkItemEdited ( id ) ;
// Render
if ( hovered | | selected )
{
const ImU32 col = GetColorU32 ( ( held & & hovered ) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header ) ;
RenderFrame ( bb . Min , bb . Max , col , false , 0.0f ) ;
RenderNavHighlight ( bb , id , ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding ) ;
}
if ( ( flags & ImGuiSelectableFlags_SpanAllColumns ) & & window - > DC . ColumnsSet )
{
PushColumnClipRect ( ) ;
bb . Max . x - = ( GetContentRegionMax ( ) . x - max_x ) ;
}
if ( flags & ImGuiSelectableFlags_Disabled ) PushStyleColor ( ImGuiCol_Text , g . Style . Colors [ ImGuiCol_TextDisabled ] ) ;
RenderTextClipped ( bb_inner . Min , bb . Max , label , NULL , & label_size , ImVec2 ( 0.0f , 0.0f ) ) ;
if ( flags & ImGuiSelectableFlags_Disabled ) PopStyleColor ( ) ;
// Automatically close popups
if ( pressed & & ( window - > Flags & ImGuiWindowFlags_Popup ) & & ! ( flags & ImGuiSelectableFlags_DontClosePopups ) & & ! ( window - > DC . ItemFlags & ImGuiItemFlags_SelectableDontClosePopup ) )
CloseCurrentPopup ( ) ;
return pressed ;
}
bool ImGui : : Selectable ( const char * label , bool * p_selected , ImGuiSelectableFlags flags , const ImVec2 & size_arg )
{
if ( Selectable ( label , * p_selected , flags , size_arg ) )
{
* p_selected = ! * p_selected ;
return true ;
}
return false ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: List Box
// - ListBox()
// - ListBoxHeader()
// - ListBoxFooter()
//-------------------------------------------------------------------------
2018-08-30 12:49:28 +00:00
// FIXME: Rename to BeginListBox()
// Helper to calculate the size of a listbox and display a label on the right.
// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
bool ImGui : : ListBoxHeader ( const char * label , const ImVec2 & size_arg )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
const ImGuiStyle & style = GetStyle ( ) ;
const ImGuiID id = GetID ( label ) ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
ImVec2 size = CalcItemSize ( size_arg , CalcItemWidth ( ) , GetTextLineHeightWithSpacing ( ) * 7.4f + style . ItemSpacing . y ) ;
ImVec2 frame_size = ImVec2 ( size . x , ImMax ( size . y , label_size . y ) ) ;
ImRect frame_bb ( window - > DC . CursorPos , window - > DC . CursorPos + frame_size ) ;
ImRect bb ( frame_bb . Min , frame_bb . Max + ImVec2 ( label_size . x > 0.0f ? style . ItemInnerSpacing . x + label_size . x : 0.0f , 0.0f ) ) ;
window - > DC . LastItemRect = bb ; // Forward storage for ListBoxFooter.. dodgy.
BeginGroup ( ) ;
if ( label_size . x > 0 )
RenderText ( ImVec2 ( frame_bb . Max . x + style . ItemInnerSpacing . x , frame_bb . Min . y + style . FramePadding . y ) , label ) ;
BeginChildFrame ( id , frame_bb . GetSize ( ) ) ;
return true ;
}
// FIXME: Rename to BeginListBox()
bool ImGui : : ListBoxHeader ( const char * label , int items_count , int height_in_items )
{
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
// We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
if ( height_in_items < 0 )
height_in_items = ImMin ( items_count , 7 ) ;
float height_in_items_f = height_in_items < items_count ? ( height_in_items + 0.40f ) : ( height_in_items + 0.00f ) ;
// We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
ImVec2 size ;
size . x = 0.0f ;
size . y = GetTextLineHeightWithSpacing ( ) * height_in_items_f + GetStyle ( ) . ItemSpacing . y ;
return ListBoxHeader ( label , size ) ;
}
// FIXME: Rename to EndListBox()
void ImGui : : ListBoxFooter ( )
{
ImGuiWindow * parent_window = GetCurrentWindow ( ) - > ParentWindow ;
const ImRect bb = parent_window - > DC . LastItemRect ;
const ImGuiStyle & style = GetStyle ( ) ;
EndChildFrame ( ) ;
// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
// We call SameLine() to restore DC.CurrentLine* data
SameLine ( ) ;
parent_window - > DC . CursorPos = bb . Min ;
ItemSize ( bb , style . FramePadding . y ) ;
EndGroup ( ) ;
}
bool ImGui : : ListBox ( const char * label , int * current_item , const char * const items [ ] , int items_count , int height_items )
{
const bool value_changed = ListBox ( label , current_item , Items_ArrayGetter , ( void * ) items , items_count , height_items ) ;
return value_changed ;
}
bool ImGui : : ListBox ( const char * label , int * current_item , bool ( * items_getter ) ( void * , int , const char * * ) , void * data , int items_count , int height_in_items )
{
if ( ! ListBoxHeader ( label , items_count , height_in_items ) )
return false ;
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
ImGuiContext & g = * GImGui ;
bool value_changed = false ;
ImGuiListClipper clipper ( items_count , GetTextLineHeightWithSpacing ( ) ) ; // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
while ( clipper . Step ( ) )
for ( int i = clipper . DisplayStart ; i < clipper . DisplayEnd ; i + + )
{
const bool item_selected = ( i = = * current_item ) ;
const char * item_text ;
if ( ! items_getter ( data , i , & item_text ) )
item_text = " *Unknown item* " ;
PushID ( i ) ;
if ( Selectable ( item_text , item_selected ) )
{
* current_item = i ;
value_changed = true ;
}
if ( item_selected )
SetItemDefaultFocus ( ) ;
PopID ( ) ;
}
ListBoxFooter ( ) ;
if ( value_changed )
MarkItemEdited ( g . CurrentWindow - > DC . LastItemId ) ;
return value_changed ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Data Plotting
// - PlotEx() [Internal]
// - PlotLines()
// - PlotHistogram()
//-------------------------------------------------------------------------
2018-08-30 13:00:24 +00:00
void ImGui : : PlotEx ( ImGuiPlotType plot_type , const char * label , float ( * values_getter ) ( void * data , int idx ) , void * data , int values_count , int values_offset , const char * overlay_text , float scale_min , float scale_max , ImVec2 graph_size )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
if ( graph_size . x = = 0.0f )
graph_size . x = CalcItemWidth ( ) ;
if ( graph_size . y = = 0.0f )
graph_size . y = label_size . y + ( style . FramePadding . y * 2 ) ;
const ImRect frame_bb ( window - > DC . CursorPos , window - > DC . CursorPos + ImVec2 ( graph_size . x , graph_size . y ) ) ;
const ImRect inner_bb ( frame_bb . Min + style . FramePadding , frame_bb . Max - style . FramePadding ) ;
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 ) ) ;
ItemSize ( total_bb , style . FramePadding . y ) ;
if ( ! ItemAdd ( total_bb , 0 , & frame_bb ) )
return ;
const bool hovered = ItemHoverable ( inner_bb , 0 ) ;
// Determine scale from values if not specified
if ( scale_min = = FLT_MAX | | scale_max = = FLT_MAX )
{
float v_min = FLT_MAX ;
float v_max = - FLT_MAX ;
for ( int i = 0 ; i < values_count ; i + + )
{
const float v = values_getter ( data , i ) ;
v_min = ImMin ( v_min , v ) ;
v_max = ImMax ( v_max , v ) ;
}
if ( scale_min = = FLT_MAX )
scale_min = v_min ;
if ( scale_max = = FLT_MAX )
scale_max = v_max ;
}
RenderFrame ( frame_bb . Min , frame_bb . Max , GetColorU32 ( ImGuiCol_FrameBg ) , true , style . FrameRounding ) ;
if ( values_count > 0 )
{
int res_w = ImMin ( ( int ) graph_size . x , values_count ) + ( ( plot_type = = ImGuiPlotType_Lines ) ? - 1 : 0 ) ;
int item_count = values_count + ( ( plot_type = = ImGuiPlotType_Lines ) ? - 1 : 0 ) ;
// Tooltip on hover
int v_hovered = - 1 ;
if ( hovered )
{
const float t = ImClamp ( ( g . IO . MousePos . x - inner_bb . Min . x ) / ( inner_bb . Max . x - inner_bb . Min . x ) , 0.0f , 0.9999f ) ;
const int v_idx = ( int ) ( t * item_count ) ;
IM_ASSERT ( v_idx > = 0 & & v_idx < values_count ) ;
const float v0 = values_getter ( data , ( v_idx + values_offset ) % values_count ) ;
const float v1 = values_getter ( data , ( v_idx + 1 + values_offset ) % values_count ) ;
if ( plot_type = = ImGuiPlotType_Lines )
SetTooltip ( " %d: %8.4g \n %d: %8.4g " , v_idx , v0 , v_idx + 1 , v1 ) ;
else if ( plot_type = = ImGuiPlotType_Histogram )
SetTooltip ( " %d: %8.4g " , v_idx , v0 ) ;
v_hovered = v_idx ;
}
const float t_step = 1.0f / ( float ) res_w ;
const float inv_scale = ( scale_min = = scale_max ) ? 0.0f : ( 1.0f / ( scale_max - scale_min ) ) ;
float v0 = values_getter ( data , ( 0 + values_offset ) % values_count ) ;
float t0 = 0.0f ;
ImVec2 tp0 = ImVec2 ( t0 , 1.0f - ImSaturate ( ( v0 - scale_min ) * inv_scale ) ) ; // Point in the normalized space of our target rectangle
float histogram_zero_line_t = ( scale_min * scale_max < 0.0f ) ? ( - scale_min * inv_scale ) : ( scale_min < 0.0f ? 0.0f : 1.0f ) ; // Where does the zero line stands
const ImU32 col_base = GetColorU32 ( ( plot_type = = ImGuiPlotType_Lines ) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram ) ;
const ImU32 col_hovered = GetColorU32 ( ( plot_type = = ImGuiPlotType_Lines ) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered ) ;
for ( int n = 0 ; n < res_w ; n + + )
{
const float t1 = t0 + t_step ;
const int v1_idx = ( int ) ( t0 * item_count + 0.5f ) ;
IM_ASSERT ( v1_idx > = 0 & & v1_idx < values_count ) ;
const float v1 = values_getter ( data , ( v1_idx + values_offset + 1 ) % values_count ) ;
const ImVec2 tp1 = ImVec2 ( t1 , 1.0f - ImSaturate ( ( v1 - scale_min ) * inv_scale ) ) ;
// NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
ImVec2 pos0 = ImLerp ( inner_bb . Min , inner_bb . Max , tp0 ) ;
ImVec2 pos1 = ImLerp ( inner_bb . Min , inner_bb . Max , ( plot_type = = ImGuiPlotType_Lines ) ? tp1 : ImVec2 ( tp1 . x , histogram_zero_line_t ) ) ;
if ( plot_type = = ImGuiPlotType_Lines )
{
window - > DrawList - > AddLine ( pos0 , pos1 , v_hovered = = v1_idx ? col_hovered : col_base ) ;
}
else if ( plot_type = = ImGuiPlotType_Histogram )
{
if ( pos1 . x > = pos0 . x + 2.0f )
pos1 . x - = 1.0f ;
window - > DrawList - > AddRectFilled ( pos0 , pos1 , v_hovered = = v1_idx ? col_hovered : col_base ) ;
}
t0 = t1 ;
tp0 = tp1 ;
}
}
// Text overlay
if ( overlay_text )
RenderTextClipped ( ImVec2 ( frame_bb . Min . x , frame_bb . Min . y + style . FramePadding . y ) , frame_bb . Max , overlay_text , NULL , NULL , ImVec2 ( 0.5f , 0.0f ) ) ;
if ( label_size . x > 0.0f )
RenderText ( ImVec2 ( frame_bb . Max . x + style . ItemInnerSpacing . x , inner_bb . Min . y ) , label ) ;
}
struct ImGuiPlotArrayGetterData
{
const float * Values ;
int Stride ;
ImGuiPlotArrayGetterData ( const float * values , int stride ) { Values = values ; Stride = stride ; }
} ;
static float Plot_ArrayGetter ( void * data , int idx )
{
ImGuiPlotArrayGetterData * plot_data = ( ImGuiPlotArrayGetterData * ) data ;
const float v = * ( const float * ) ( const void * ) ( ( const unsigned char * ) plot_data - > Values + ( size_t ) idx * plot_data - > Stride ) ;
return v ;
}
void ImGui : : PlotLines ( const char * label , const float * values , int values_count , int values_offset , const char * overlay_text , float scale_min , float scale_max , ImVec2 graph_size , int stride )
{
ImGuiPlotArrayGetterData data ( values , stride ) ;
PlotEx ( ImGuiPlotType_Lines , label , & Plot_ArrayGetter , ( void * ) & data , values_count , values_offset , overlay_text , scale_min , scale_max , graph_size ) ;
}
void ImGui : : PlotLines ( const char * label , float ( * values_getter ) ( void * data , int idx ) , void * data , int values_count , int values_offset , const char * overlay_text , float scale_min , float scale_max , ImVec2 graph_size )
{
PlotEx ( ImGuiPlotType_Lines , label , values_getter , data , values_count , values_offset , overlay_text , scale_min , scale_max , graph_size ) ;
}
void ImGui : : PlotHistogram ( const char * label , const float * values , int values_count , int values_offset , const char * overlay_text , float scale_min , float scale_max , ImVec2 graph_size , int stride )
{
ImGuiPlotArrayGetterData data ( values , stride ) ;
PlotEx ( ImGuiPlotType_Histogram , label , & Plot_ArrayGetter , ( void * ) & data , values_count , values_offset , overlay_text , scale_min , scale_max , graph_size ) ;
}
void ImGui : : PlotHistogram ( const char * label , float ( * values_getter ) ( void * data , int idx ) , void * data , int values_count , int values_offset , const char * overlay_text , float scale_min , float scale_max , ImVec2 graph_size )
{
PlotEx ( ImGuiPlotType_Histogram , label , values_getter , data , values_count , values_offset , overlay_text , scale_min , scale_max , graph_size ) ;
}
2018-08-29 13:15:36 +00:00
//-------------------------------------------------------------------------
// WIDGETS: Value() helpers
// - Value()
//-------------------------------------------------------------------------
2018-08-30 13:00:24 +00:00
void ImGui : : Value ( const char * prefix , bool b )
{
Text ( " %s: %s " , prefix , ( b ? " true " : " false " ) ) ;
}
void ImGui : : Value ( const char * prefix , int v )
{
Text ( " %s: %d " , prefix , v ) ;
}
void ImGui : : Value ( const char * prefix , unsigned int v )
{
Text ( " %s: %d " , prefix , v ) ;
}
void ImGui : : Value ( const char * prefix , float v , const char * float_format )
{
if ( float_format )
{
char fmt [ 64 ] ;
ImFormatString ( fmt , IM_ARRAYSIZE ( fmt ) , " %%s: %s " , float_format ) ;
Text ( fmt , prefix , v ) ;
}
else
{
Text ( " %s: %.3f " , prefix , v ) ;
}
}
2018-08-28 19:59:14 +00:00
//-------------------------------------------------------------------------
2018-08-29 13:15:36 +00:00
// WIDGETS: Menus
2018-08-30 12:58:55 +00:00
// - ImGuiMenuColumns
2018-08-29 13:15:36 +00:00
// - BeginMainMenuBar()
// - EndMainMenuBar()
// - BeginMenuBar()
// - EndMenuBar()
// - BeginMenu()
// - EndMenu()
// - MenuItem()
2018-08-28 19:59:14 +00:00
//-------------------------------------------------------------------------
2018-08-30 12:58:55 +00:00
// Helpers for internal use
ImGuiMenuColumns : : ImGuiMenuColumns ( )
{
Count = 0 ;
Spacing = Width = NextWidth = 0.0f ;
memset ( Pos , 0 , sizeof ( Pos ) ) ;
memset ( NextWidths , 0 , sizeof ( NextWidths ) ) ;
}
void ImGuiMenuColumns : : Update ( int count , float spacing , bool clear )
{
IM_ASSERT ( Count < = IM_ARRAYSIZE ( Pos ) ) ;
Count = count ;
Width = NextWidth = 0.0f ;
Spacing = spacing ;
if ( clear ) memset ( NextWidths , 0 , sizeof ( NextWidths ) ) ;
for ( int i = 0 ; i < Count ; i + + )
{
if ( i > 0 & & NextWidths [ i ] > 0.0f )
Width + = Spacing ;
Pos [ i ] = ( float ) ( int ) Width ;
Width + = NextWidths [ i ] ;
NextWidths [ i ] = 0.0f ;
}
}
float ImGuiMenuColumns : : DeclColumns ( float w0 , float w1 , float w2 ) // not using va_arg because they promote float to double
{
NextWidth = 0.0f ;
NextWidths [ 0 ] = ImMax ( NextWidths [ 0 ] , w0 ) ;
NextWidths [ 1 ] = ImMax ( NextWidths [ 1 ] , w1 ) ;
NextWidths [ 2 ] = ImMax ( NextWidths [ 2 ] , w2 ) ;
for ( int i = 0 ; i < 3 ; i + + )
NextWidth + = NextWidths [ i ] + ( ( i > 0 & & NextWidths [ i ] > 0.0f ) ? Spacing : 0.0f ) ;
return ImMax ( Width , NextWidth ) ;
}
float ImGuiMenuColumns : : CalcExtraSpace ( float avail_w )
{
return ImMax ( 0.0f , avail_w - Width ) ;
}
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
bool ImGui : : BeginMainMenuBar ( )
{
ImGuiContext & g = * GImGui ;
g . NextWindowData . MenuBarOffsetMinVal = ImVec2 ( g . Style . DisplaySafeAreaPadding . x , ImMax ( g . Style . DisplaySafeAreaPadding . y - g . Style . FramePadding . y , 0.0f ) ) ;
SetNextWindowPos ( g . Viewports [ 0 ] - > Pos ) ;
SetNextWindowSize ( ImVec2 ( g . Viewports [ 0 ] - > Size . x , g . NextWindowData . MenuBarOffsetMinVal . y + g . FontBaseSize + g . Style . FramePadding . y ) ) ;
PushStyleVar ( ImGuiStyleVar_WindowRounding , 0.0f ) ;
PushStyleVar ( ImGuiStyleVar_WindowMinSize , ImVec2 ( 0 , 0 ) ) ;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar ;
bool is_open = Begin ( " ##MainMenuBar " , NULL , window_flags ) & & BeginMenuBar ( ) ;
PopStyleVar ( 2 ) ;
g . NextWindowData . MenuBarOffsetMinVal = ImVec2 ( 0.0f , 0.0f ) ;
if ( ! is_open )
{
End ( ) ;
return false ;
}
return true ;
}
void ImGui : : EndMainMenuBar ( )
{
EndMenuBar ( ) ;
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
ImGuiContext & g = * GImGui ;
if ( g . CurrentWindow = = g . NavWindow & & g . NavLayer = = 0 )
FocusFrontMostActiveWindowIgnoringOne ( g . NavWindow ) ;
End ( ) ;
}
bool ImGui : : BeginMenuBar ( )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
if ( ! ( window - > Flags & ImGuiWindowFlags_MenuBar ) )
return false ;
IM_ASSERT ( ! window - > DC . MenuBarAppending ) ;
BeginGroup ( ) ; // Backup position on layer 0
PushID ( " ##menubar " ) ;
// We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
// We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
ImRect bar_rect = window - > MenuBarRect ( ) ;
ImRect clip_rect ( ImFloor ( bar_rect . Min . x + 0.5f ) , ImFloor ( bar_rect . Min . y + window - > WindowBorderSize + 0.5f ) , ImFloor ( ImMax ( bar_rect . Min . x , bar_rect . Max . x - window - > WindowRounding ) + 0.5f ) , ImFloor ( bar_rect . Max . y + 0.5f ) ) ;
clip_rect . ClipWith ( window - > OuterRectClipped ) ;
PushClipRect ( clip_rect . Min , clip_rect . Max , false ) ;
window - > DC . CursorPos = ImVec2 ( bar_rect . Min . x + window - > DC . MenuBarOffset . x , bar_rect . Min . y + window - > DC . MenuBarOffset . y ) ;
window - > DC . LayoutType = ImGuiLayoutType_Horizontal ;
window - > DC . NavLayerCurrent + + ;
window - > DC . NavLayerCurrentMask < < = 1 ;
window - > DC . MenuBarAppending = true ;
AlignTextToFramePadding ( ) ;
return true ;
}
void ImGui : : EndMenuBar ( )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return ;
ImGuiContext & g = * GImGui ;
// Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
if ( NavMoveRequestButNoResultYet ( ) & & ( g . NavMoveDir = = ImGuiDir_Left | | g . NavMoveDir = = ImGuiDir_Right ) & & ( g . NavWindow - > Flags & ImGuiWindowFlags_ChildMenu ) )
{
ImGuiWindow * nav_earliest_child = g . NavWindow ;
while ( nav_earliest_child - > ParentWindow & & ( nav_earliest_child - > ParentWindow - > Flags & ImGuiWindowFlags_ChildMenu ) )
nav_earliest_child = nav_earliest_child - > ParentWindow ;
if ( nav_earliest_child - > ParentWindow = = window & & nav_earliest_child - > DC . ParentLayoutType = = ImGuiLayoutType_Horizontal & & g . NavMoveRequestForward = = ImGuiNavForward_None )
{
// To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
// This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
IM_ASSERT ( window - > DC . NavLayerActiveMaskNext & 0x02 ) ; // Sanity check
FocusWindow ( window ) ;
SetNavIDWithRectRel ( window - > NavLastIds [ 1 ] , 1 , window - > NavRectRel [ 1 ] ) ;
g . NavLayer = 1 ;
g . NavDisableHighlight = true ; // Hide highlight for the current frame so we don't see the intermediary selection.
g . NavMoveRequestForward = ImGuiNavForward_ForwardQueued ;
NavMoveRequestCancel ( ) ;
}
}
IM_ASSERT ( window - > Flags & ImGuiWindowFlags_MenuBar ) ;
IM_ASSERT ( window - > DC . MenuBarAppending ) ;
PopClipRect ( ) ;
PopID ( ) ;
window - > DC . MenuBarOffset . x = window - > DC . CursorPos . x - window - > MenuBarRect ( ) . Min . x ; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
window - > DC . GroupStack . back ( ) . AdvanceCursor = false ;
EndGroup ( ) ; // Restore position on layer 0
window - > DC . LayoutType = ImGuiLayoutType_Vertical ;
window - > DC . NavLayerCurrent - - ;
window - > DC . NavLayerCurrentMask > > = 1 ;
window - > DC . MenuBarAppending = false ;
}
bool ImGui : : BeginMenu ( const char * label , bool enabled )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
const ImGuiStyle & style = g . Style ;
const ImGuiID id = window - > GetID ( label ) ;
ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
bool pressed ;
bool menu_is_open = IsPopupOpen ( id ) ;
bool menuset_is_open = ! ( window - > Flags & ImGuiWindowFlags_Popup ) & & ( g . OpenPopupStack . Size > g . CurrentPopupStack . Size & & g . OpenPopupStack [ g . CurrentPopupStack . Size ] . OpenParentId = = window - > IDStack . back ( ) ) ;
ImGuiWindow * backed_nav_window = g . NavWindow ;
if ( menuset_is_open )
g . NavWindow = window ; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
ImVec2 popup_pos , pos = window - > DC . CursorPos ;
if ( window - > DC . LayoutType = = ImGuiLayoutType_Horizontal )
{
// Menu inside an horizontal menu bar
// Selectable extend their highlight by half ItemSpacing in each direction.
// For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
popup_pos = ImVec2 ( pos . x - window - > WindowPadding . x , pos . y - style . FramePadding . y + window - > MenuBarHeight ( ) ) ;
window - > DC . CursorPos . x + = ( float ) ( int ) ( style . ItemSpacing . x * 0.5f ) ;
PushStyleVar ( ImGuiStyleVar_ItemSpacing , style . ItemSpacing * 2.0f ) ;
float w = label_size . x ;
pressed = Selectable ( label , menu_is_open , ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ( ! enabled ? ImGuiSelectableFlags_Disabled : 0 ) , ImVec2 ( w , 0.0f ) ) ;
PopStyleVar ( ) ;
window - > DC . CursorPos . x + = ( float ) ( int ) ( style . ItemSpacing . x * ( - 1.0f + 0.5f ) ) ; // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
}
else
{
// Menu inside a menu
popup_pos = ImVec2 ( pos . x , pos . y - style . WindowPadding . y ) ;
float w = window - > MenuColumns . DeclColumns ( label_size . x , 0.0f , ( float ) ( int ) ( g . FontSize * 1.20f ) ) ; // Feedback to next frame
float extra_w = ImMax ( 0.0f , GetContentRegionAvail ( ) . x - w ) ;
pressed = Selectable ( label , menu_is_open , ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | ( ! enabled ? ImGuiSelectableFlags_Disabled : 0 ) , ImVec2 ( w , 0.0f ) ) ;
if ( ! enabled ) PushStyleColor ( ImGuiCol_Text , g . Style . Colors [ ImGuiCol_TextDisabled ] ) ;
RenderArrow ( pos + ImVec2 ( window - > MenuColumns . Pos [ 2 ] + extra_w + g . FontSize * 0.30f , 0.0f ) , ImGuiDir_Right ) ;
if ( ! enabled ) PopStyleColor ( ) ;
}
const bool hovered = enabled & & ItemHoverable ( window - > DC . LastItemRect , id ) ;
if ( menuset_is_open )
g . NavWindow = backed_nav_window ;
bool want_open = false , want_close = false ;
if ( window - > DC . LayoutType = = ImGuiLayoutType_Vertical ) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
{
// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
bool moving_within_opened_triangle = false ;
if ( g . HoveredWindow = = window & & g . OpenPopupStack . Size > g . CurrentPopupStack . Size & & g . OpenPopupStack [ g . CurrentPopupStack . Size ] . ParentWindow = = window & & ! ( window - > Flags & ImGuiWindowFlags_MenuBar ) )
{
if ( ImGuiWindow * next_window = g . OpenPopupStack [ g . CurrentPopupStack . Size ] . Window )
{
ImRect next_window_rect = next_window - > Rect ( ) ;
ImVec2 ta = g . IO . MousePos - g . IO . MouseDelta ;
ImVec2 tb = ( window - > Pos . x < next_window - > Pos . x ) ? next_window_rect . GetTL ( ) : next_window_rect . GetTR ( ) ;
ImVec2 tc = ( window - > Pos . x < next_window - > Pos . x ) ? next_window_rect . GetBL ( ) : next_window_rect . GetBR ( ) ;
float extra = ImClamp ( ImFabs ( ta . x - tb . x ) * 0.30f , 5.0f , 30.0f ) ; // add a bit of extra slack.
ta . x + = ( window - > Pos . x < next_window - > Pos . x ) ? - 0.5f : + 0.5f ; // to avoid numerical issues
tb . y = ta . y + ImMax ( ( tb . y - extra ) - ta . y , - 100.0f ) ; // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
tc . y = ta . y + ImMin ( ( tc . y + extra ) - ta . y , + 100.0f ) ;
moving_within_opened_triangle = ImTriangleContainsPoint ( ta , tb , tc , g . IO . MousePos ) ;
//window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
}
}
want_close = ( menu_is_open & & ! hovered & & g . HoveredWindow = = window & & g . HoveredIdPreviousFrame ! = 0 & & g . HoveredIdPreviousFrame ! = id & & ! moving_within_opened_triangle ) ;
want_open = ( ! menu_is_open & & hovered & & ! moving_within_opened_triangle ) | | ( ! menu_is_open & & hovered & & pressed ) ;
if ( g . NavActivateId = = id )
{
want_close = menu_is_open ;
want_open = ! menu_is_open ;
}
if ( g . NavId = = id & & g . NavMoveRequest & & g . NavMoveDir = = ImGuiDir_Right ) // Nav-Right to open
{
want_open = true ;
NavMoveRequestCancel ( ) ;
}
}
else
{
// Menu bar
if ( menu_is_open & & pressed & & menuset_is_open ) // Click an open menu again to close it
{
want_close = true ;
want_open = menu_is_open = false ;
}
else if ( pressed | | ( hovered & & menuset_is_open & & ! menu_is_open ) ) // First click to open, then hover to open others
{
want_open = true ;
}
else if ( g . NavId = = id & & g . NavMoveRequest & & g . NavMoveDir = = ImGuiDir_Down ) // Nav-Down to open
{
want_open = true ;
NavMoveRequestCancel ( ) ;
}
}
if ( ! enabled ) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
want_close = true ;
if ( want_close & & IsPopupOpen ( id ) )
ClosePopupToLevel ( g . CurrentPopupStack . Size ) ;
if ( ! menu_is_open & & want_open & & g . OpenPopupStack . Size > g . CurrentPopupStack . Size )
{
// Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
OpenPopup ( label ) ;
return false ;
}
menu_is_open | = want_open ;
if ( want_open )
OpenPopup ( label ) ;
if ( menu_is_open )
{
// Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
SetNextWindowPos ( popup_pos , ImGuiCond_Always ) ;
ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus ;
if ( window - > Flags & ( ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu ) )
flags | = ImGuiWindowFlags_ChildWindow ;
menu_is_open = BeginPopupEx ( id , flags ) ; // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
}
return menu_is_open ;
}
void ImGui : : EndMenu ( )
{
// Nav: When a left move request _within our child menu_ failed, close the menu.
// A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
// However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
ImGuiContext & g = * GImGui ;
ImGuiWindow * window = g . CurrentWindow ;
if ( g . NavWindow & & g . NavWindow - > ParentWindow = = window & & g . NavMoveDir = = ImGuiDir_Left & & NavMoveRequestButNoResultYet ( ) & & window - > DC . LayoutType = = ImGuiLayoutType_Vertical )
{
ClosePopupToLevel ( g . OpenPopupStack . Size - 1 ) ;
NavMoveRequestCancel ( ) ;
}
EndPopup ( ) ;
}
bool ImGui : : MenuItem ( const char * label , const char * shortcut , bool selected , bool enabled )
{
ImGuiWindow * window = GetCurrentWindow ( ) ;
if ( window - > SkipItems )
return false ;
ImGuiContext & g = * GImGui ;
ImGuiStyle & style = g . Style ;
ImVec2 pos = window - > DC . CursorPos ;
ImVec2 label_size = CalcTextSize ( label , NULL , true ) ;
ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | ( enabled ? 0 : ImGuiSelectableFlags_Disabled ) ;
bool pressed ;
if ( window - > DC . LayoutType = = ImGuiLayoutType_Horizontal )
{
// Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
// Note that in this situation we render neither the shortcut neither the selected tick mark
float w = label_size . x ;
window - > DC . CursorPos . x + = ( float ) ( int ) ( style . ItemSpacing . x * 0.5f ) ;
PushStyleVar ( ImGuiStyleVar_ItemSpacing , style . ItemSpacing * 2.0f ) ;
pressed = Selectable ( label , false , flags , ImVec2 ( w , 0.0f ) ) ;
PopStyleVar ( ) ;
window - > DC . CursorPos . x + = ( float ) ( int ) ( style . ItemSpacing . x * ( - 1.0f + 0.5f ) ) ; // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
}
else
{
ImVec2 shortcut_size = shortcut ? CalcTextSize ( shortcut , NULL ) : ImVec2 ( 0.0f , 0.0f ) ;
float w = window - > MenuColumns . DeclColumns ( label_size . x , shortcut_size . x , ( float ) ( int ) ( g . FontSize * 1.20f ) ) ; // Feedback for next frame
float extra_w = ImMax ( 0.0f , GetContentRegionAvail ( ) . x - w ) ;
pressed = Selectable ( label , false , flags | ImGuiSelectableFlags_DrawFillAvailWidth , ImVec2 ( w , 0.0f ) ) ;
if ( shortcut_size . x > 0.0f )
{
PushStyleColor ( ImGuiCol_Text , g . Style . Colors [ ImGuiCol_TextDisabled ] ) ;
RenderText ( pos + ImVec2 ( window - > MenuColumns . Pos [ 1 ] + extra_w , 0.0f ) , shortcut , NULL , false ) ;
PopStyleColor ( ) ;
}
if ( selected )
RenderCheckMark ( pos + ImVec2 ( window - > MenuColumns . Pos [ 2 ] + extra_w + g . FontSize * 0.40f , g . FontSize * 0.134f * 0.5f ) , GetColorU32 ( enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled ) , g . FontSize * 0.866f ) ;
}
return pressed ;
}
bool ImGui : : MenuItem ( const char * label , const char * shortcut , bool * p_selected , bool enabled )
{
if ( MenuItem ( label , shortcut , p_selected ? * p_selected : false , enabled ) )
{
if ( p_selected )
* p_selected = ! * p_selected ;
return true ;
}
return false ;
}