2018-08-29 03:59:14 +08: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 21:15:36 +08: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-29 03:59:14 +08:00
//-------------------------------------------------------------------------
// Forward Declarations
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
2018-08-29 21:15:36 +08:00
// SHARED UTILITIES
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Text
// - TextUnformatted()
// - Text()
// - TextV()
// - TextColored()
// - TextColoredV()
// - TextDisabled()
// - TextDisabledV()
// - TextWrapped()
// - TextWrappedV()
// - LabelText()
// - LabelTextV()
// - BulletText()
// - BulletTextV()
//-------------------------------------------------------------------------
2018-08-30 20:38:44 +08: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 21:15:36 +08:00
//-------------------------------------------------------------------------
// WIDGETS: Main
// - ButtonBehavior() [Internal]
// - Button()
// - SmallButton()
// - InvisibleButton()
// - ArrowButton()
// - CloseButton() [Internal]
// - CollapseButton() [Internal]
// - Image()
// - ImageButton()
// - Checkbox()
// - CheckboxFlags()
// - RadioButton()
// - ProgressBar()
// - Bullet()
//-------------------------------------------------------------------------
2018-08-30 20:41:55 +08: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 21:15:36 +08:00
//-------------------------------------------------------------------------
// WIDGETS: Combo Box
// - BeginCombo()
// - EndCombo()
// - Combo()
//-------------------------------------------------------------------------
2018-08-30 20:49:28 +08: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 21:15:36 +08:00
//-------------------------------------------------------------------------
// WIDGETS: Data Type and Data Formatting Helpers [Internal]
// - PatchFormatStringFloatToInt()
// - DataTypeFormatString()
// - DataTypeApplyOp()
// - DataTypeApplyOpFromText()
// - GetMinimumStepAtDecimalPrecision
// - RoundScalarWithFormat<>()
//-------------------------------------------------------------------------
2018-08-29 03:59:14 +08:00
//-------------------------------------------------------------------------
2018-08-29 21:15:36 +08: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]
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Color Editor / Picker
// - ColorEdit3()
// - ColorEdit4()
// - ColorPicker3()
// - RenderColorRectWithAlphaCheckerboard() [Internal]
// - ColorPicker4()
// - ColorButton()
// - SetColorEditOptions()
// - ColorTooltip() [Internal]
// - ColorEditOptionsPopup() [Internal]
// - ColorPickerOptionsPopup() [Internal]
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Trees
// - TreeNode()
// - TreeNodeV()
// - TreeNodeEx()
// - TreeNodeExV()
// - TreeNodeBehavior() [Internal]
// - TreePush()
// - TreePop()
// - TreeAdvanceToLabelPos()
// - GetTreeNodeToLabelSpacing()
// - SetNextTreeNodeOpen()
// - CollapsingHeader()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Selectables
// - Selectable()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: List Box
// - ListBox()
// - ListBoxHeader()
// - ListBoxFooter()
//-------------------------------------------------------------------------
2018-08-30 20:49:28 +08: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 21:15:36 +08:00
//-------------------------------------------------------------------------
// WIDGETS: Data Plotting
// - PlotEx() [Internal]
// - PlotLines()
// - PlotHistogram()
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// WIDGETS: Value() helpers
// - Value()
//-------------------------------------------------------------------------
2018-08-29 03:59:14 +08:00
//-------------------------------------------------------------------------
2018-08-29 21:15:36 +08:00
// WIDGETS: Menus
2018-08-30 20:58:55 +08:00
// - ImGuiMenuColumns
2018-08-29 21:15:36 +08:00
// - BeginMainMenuBar()
// - EndMainMenuBar()
// - BeginMenuBar()
// - EndMenuBar()
// - BeginMenu()
// - EndMenu()
// - MenuItem()
2018-08-29 03:59:14 +08:00
//-------------------------------------------------------------------------
2018-08-30 20:58:55 +08: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 ;
}