1 // dear imgui, v1.68 WIP
8 // [SECTION] Forward Declarations
9 // [SECTION] Widgets: Text, etc.
10 // [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11 // [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12 // [SECTION] Widgets: ComboBox
13 // [SECTION] Data Type and Data Formatting Helpers
14 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17 // [SECTION] Widgets: InputText, InputTextMultiline
18 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20 // [SECTION] Widgets: Selectable
21 // [SECTION] Widgets: ListBox
22 // [SECTION] Widgets: PlotLines, PlotHistogram
23 // [SECTION] Widgets: Value helpers
24 // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
25 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
26 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
30 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
31 #define _CRT_SECURE_NO_WARNINGS
35 #ifndef IMGUI_DEFINE_MATH_OPERATORS
36 #define IMGUI_DEFINE_MATH_OPERATORS
38 #include "imgui_internal.h"
40 #include <ctype.h> // toupper, isprint
41 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
42 #include <stddef.h> // intptr_t
44 #include <stdint.h> // intptr_t
47 // Visual Studio warnings
49 #pragma warning (disable: 4127) // condition expression is constant
50 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
53 // Clang/GCC warnings with -Weverything
55 #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
56 #pragma clang diagnostic ignored "-Wfloat-equal" // warning : comparing floating point with == or != is unsafe // storing and comparing against same constants (typically 0.0f) is ok.
57 #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.
58 #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
59 #if __has_warning("-Wzero-as-null-pointer-constant")
60 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning : zero as null pointer constant // some standard header variations use #define NULL 0
62 #if __has_warning("-Wdouble-promotion")
63 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double.
65 #elif defined(__GNUC__)
66 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
68 #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
72 //-------------------------------------------------------------------------
74 //-------------------------------------------------------------------------
76 // Those MIN/MAX values are not define because we need to point to them
77 static const ImS32 IM_S32_MIN
= INT_MIN
; // (-2147483647 - 1), (0x80000000);
78 static const ImS32 IM_S32_MAX
= INT_MAX
; // (2147483647), (0x7FFFFFFF)
79 static const ImU32 IM_U32_MIN
= 0;
80 static const ImU32 IM_U32_MAX
= UINT_MAX
; // (0xFFFFFFFF)
82 static const ImS64 IM_S64_MIN
= LLONG_MIN
; // (-9223372036854775807ll - 1ll);
83 static const ImS64 IM_S64_MAX
= LLONG_MAX
; // (9223372036854775807ll);
85 static const ImS64 IM_S64_MIN
= -9223372036854775807LL - 1;
86 static const ImS64 IM_S64_MAX
= 9223372036854775807LL;
88 static const ImU64 IM_U64_MIN
= 0;
90 static const ImU64 IM_U64_MAX
= ULLONG_MAX
; // (0xFFFFFFFFFFFFFFFFull);
92 static const ImU64 IM_U64_MAX
= (2ULL * 9223372036854775807LL + 1);
95 //-------------------------------------------------------------------------
96 // [SECTION] Forward Declarations
97 //-------------------------------------------------------------------------
100 static inline int DataTypeFormatString(char* buf
, int buf_size
, ImGuiDataType data_type
, const void* data_ptr
, const char* format
);
101 static void DataTypeApplyOp(ImGuiDataType data_type
, int op
, void* output
, void* arg_1
, const void* arg_2
);
102 static bool DataTypeApplyOpFromText(const char* buf
, const char* initial_value_buf
, ImGuiDataType data_type
, void* data_ptr
, const char* format
);
105 static bool InputTextFilterCharacter(unsigned int* p_char
, ImGuiInputTextFlags flags
, ImGuiInputTextCallback callback
, void* user_data
);
106 static int InputTextCalcTextLenAndLineCount(const char* text_begin
, const char** out_text_end
);
107 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);
109 //-------------------------------------------------------------------------
110 // [SECTION] Widgets: Text, etc.
111 //-------------------------------------------------------------------------
112 // - TextUnformatted()
125 //-------------------------------------------------------------------------
127 void ImGui::TextUnformatted(const char* text
, const char* text_end
)
129 ImGuiWindow
* window
= GetCurrentWindow();
130 if (window
->SkipItems
)
133 ImGuiContext
& g
= *GImGui
;
134 IM_ASSERT(text
!= NULL
);
135 const char* text_begin
= text
;
136 if (text_end
== NULL
)
137 text_end
= text
+ strlen(text
); // FIXME-OPT
139 const ImVec2
text_pos(window
->DC
.CursorPos
.x
, window
->DC
.CursorPos
.y
+ window
->DC
.CurrentLineTextBaseOffset
);
140 const float wrap_pos_x
= window
->DC
.TextWrapPos
;
141 const bool wrap_enabled
= wrap_pos_x
>= 0.0f
;
142 if (text_end
- text
> 2000 && !wrap_enabled
)
145 // Perform manual coarse clipping to optimize for long multi-line text
146 // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
147 // - 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.
148 // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
149 const char* line
= text
;
150 const float line_height
= GetTextLineHeight();
151 const ImRect clip_rect
= window
->ClipRect
;
152 ImVec2
text_size(0,0);
154 if (text_pos
.y
<= clip_rect
.Max
.y
)
156 ImVec2 pos
= text_pos
;
158 // Lines to skip (can't skip when logging text)
161 int lines_skippable
= (int)((clip_rect
.Min
.y
- text_pos
.y
) / line_height
);
162 if (lines_skippable
> 0)
164 int lines_skipped
= 0;
165 while (line
< text_end
&& lines_skipped
< lines_skippable
)
167 const char* line_end
= (const char*)memchr(line
, '\n', text_end
- line
);
173 pos
.y
+= lines_skipped
* line_height
;
180 ImRect
line_rect(pos
, pos
+ ImVec2(FLT_MAX
, line_height
));
181 while (line
< text_end
)
183 if (IsClippedEx(line_rect
, 0, false))
186 const char* line_end
= (const char*)memchr(line
, '\n', text_end
- line
);
189 const ImVec2 line_size
= CalcTextSize(line
, line_end
, false);
190 text_size
.x
= ImMax(text_size
.x
, line_size
.x
);
191 RenderText(pos
, line
, line_end
, false);
193 line_rect
.Min
.y
+= line_height
;
194 line_rect
.Max
.y
+= line_height
;
195 pos
.y
+= line_height
;
198 // Count remaining lines
199 int lines_skipped
= 0;
200 while (line
< text_end
)
202 const char* line_end
= (const char*)memchr(line
, '\n', text_end
- line
);
208 pos
.y
+= lines_skipped
* line_height
;
211 text_size
.y
+= (pos
- text_pos
).y
;
214 ImRect
bb(text_pos
, text_pos
+ text_size
);
220 const float wrap_width
= wrap_enabled
? CalcWrapWidthForPos(window
->DC
.CursorPos
, wrap_pos_x
) : 0.0f
;
221 const ImVec2 text_size
= CalcTextSize(text_begin
, text_end
, false, wrap_width
);
223 // Account of baseline offset
224 ImRect
bb(text_pos
, text_pos
+ text_size
);
229 // Render (we don't hide text after ## in this end-user function)
230 RenderTextWrapped(bb
.Min
, text_begin
, text_end
, wrap_width
);
234 void ImGui::Text(const char* fmt
, ...)
242 void ImGui::TextV(const char* fmt
, va_list args
)
244 ImGuiWindow
* window
= GetCurrentWindow();
245 if (window
->SkipItems
)
248 ImGuiContext
& g
= *GImGui
;
249 const char* text_end
= g
.TempBuffer
+ ImFormatStringV(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), fmt
, args
);
250 TextUnformatted(g
.TempBuffer
, text_end
);
253 void ImGui::TextColored(const ImVec4
& col
, const char* fmt
, ...)
257 TextColoredV(col
, fmt
, args
);
261 void ImGui::TextColoredV(const ImVec4
& col
, const char* fmt
, va_list args
)
263 PushStyleColor(ImGuiCol_Text
, col
);
268 void ImGui::TextDisabled(const char* fmt
, ...)
272 TextDisabledV(fmt
, args
);
276 void ImGui::TextDisabledV(const char* fmt
, va_list args
)
278 PushStyleColor(ImGuiCol_Text
, GImGui
->Style
.Colors
[ImGuiCol_TextDisabled
]);
283 void ImGui::TextWrapped(const char* fmt
, ...)
287 TextWrappedV(fmt
, args
);
291 void ImGui::TextWrappedV(const char* fmt
, va_list args
)
293 bool need_backup
= (GImGui
->CurrentWindow
->DC
.TextWrapPos
< 0.0f
); // Keep existing wrap position if one is already set
295 PushTextWrapPos(0.0f
);
301 void ImGui::LabelText(const char* label
, const char* fmt
, ...)
305 LabelTextV(label
, fmt
, args
);
309 // Add a label+text combo aligned to other label+value widgets
310 void ImGui::LabelTextV(const char* label
, const char* fmt
, va_list args
)
312 ImGuiWindow
* window
= GetCurrentWindow();
313 if (window
->SkipItems
)
316 ImGuiContext
& g
= *GImGui
;
317 const ImGuiStyle
& style
= g
.Style
;
318 const float w
= CalcItemWidth();
320 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
321 const ImRect
value_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(w
, label_size
.y
+ style
.FramePadding
.y
*2));
322 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
);
323 ItemSize(total_bb
, style
.FramePadding
.y
);
324 if (!ItemAdd(total_bb
, 0))
328 const char* value_text_begin
= &g
.TempBuffer
[0];
329 const char* value_text_end
= value_text_begin
+ ImFormatStringV(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), fmt
, args
);
330 RenderTextClipped(value_bb
.Min
, value_bb
.Max
, value_text_begin
, value_text_end
, NULL
, ImVec2(0.0f
,0.5f
));
331 if (label_size
.x
> 0.0f
)
332 RenderText(ImVec2(value_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, value_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
335 void ImGui::BulletText(const char* fmt
, ...)
339 BulletTextV(fmt
, args
);
343 // Text with a little bullet aligned to the typical tree node.
344 void ImGui::BulletTextV(const char* fmt
, va_list args
)
346 ImGuiWindow
* window
= GetCurrentWindow();
347 if (window
->SkipItems
)
350 ImGuiContext
& g
= *GImGui
;
351 const ImGuiStyle
& style
= g
.Style
;
353 const char* text_begin
= g
.TempBuffer
;
354 const char* text_end
= text_begin
+ ImFormatStringV(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), fmt
, args
);
355 const ImVec2 label_size
= CalcTextSize(text_begin
, text_end
, false);
356 const float text_base_offset_y
= ImMax(0.0f
, window
->DC
.CurrentLineTextBaseOffset
); // Latch before ItemSize changes it
357 const float line_height
= ImMax(ImMin(window
->DC
.CurrentLineSize
.y
, g
.FontSize
+ g
.Style
.FramePadding
.y
*2), g
.FontSize
);
358 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
364 RenderBullet(bb
.Min
+ ImVec2(style
.FramePadding
.x
+ g
.FontSize
*0.5f
, line_height
*0.5f
));
365 RenderText(bb
.Min
+ImVec2(g
.FontSize
+ style
.FramePadding
.x
*2, text_base_offset_y
), text_begin
, text_end
, false);
368 //-------------------------------------------------------------------------
369 // [SECTION] Widgets: Main
370 //-------------------------------------------------------------------------
371 // - ButtonBehavior() [Internal]
374 // - InvisibleButton()
376 // - CloseButton() [Internal]
377 // - CollapseButton() [Internal]
378 // - Scrollbar() [Internal]
386 //-------------------------------------------------------------------------
388 bool ImGui::ButtonBehavior(const ImRect
& bb
, ImGuiID id
, bool* out_hovered
, bool* out_held
, ImGuiButtonFlags flags
)
390 ImGuiContext
& g
= *GImGui
;
391 ImGuiWindow
* window
= GetCurrentWindow();
393 if (flags
& ImGuiButtonFlags_Disabled
)
395 if (out_hovered
) *out_hovered
= false;
396 if (out_held
) *out_held
= false;
397 if (g
.ActiveId
== id
) ClearActiveID();
401 // Default behavior requires click+release on same spot
402 if ((flags
& (ImGuiButtonFlags_PressedOnClickRelease
| ImGuiButtonFlags_PressedOnClick
| ImGuiButtonFlags_PressedOnRelease
| ImGuiButtonFlags_PressedOnDoubleClick
)) == 0)
403 flags
|= ImGuiButtonFlags_PressedOnClickRelease
;
405 ImGuiWindow
* backup_hovered_window
= g
.HoveredWindow
;
406 if ((flags
& ImGuiButtonFlags_FlattenChildren
) && g
.HoveredRootWindow
== window
)
407 g
.HoveredWindow
= window
;
409 #ifdef IMGUI_ENABLE_TEST_ENGINE
410 if (id
!= 0 && window
->DC
.LastItemId
!= id
)
411 ImGuiTestEngineHook_ItemAdd(&g
, bb
, id
);
414 bool pressed
= false;
415 bool hovered
= ItemHoverable(bb
, id
);
417 // Drag source doesn't report as hovered
418 if (hovered
&& g
.DragDropActive
&& g
.DragDropPayload
.SourceId
== id
&& !(g
.DragDropSourceFlags
& ImGuiDragDropFlags_SourceNoDisableHover
))
421 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
422 if (g
.DragDropActive
&& (flags
& ImGuiButtonFlags_PressedOnDragDropHold
) && !(g
.DragDropSourceFlags
& ImGuiDragDropFlags_SourceNoHoldToOpenOthers
))
423 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem
))
427 if (CalcTypematicPressedRepeatAmount(g
.HoveredIdTimer
+ 0.0001f
, g
.HoveredIdTimer
+ 0.0001f
- g
.IO
.DeltaTime
, 0.01f
, 0.70f
)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
434 if ((flags
& ImGuiButtonFlags_FlattenChildren
) && g
.HoveredRootWindow
== window
)
435 g
.HoveredWindow
= backup_hovered_window
;
437 // 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.
438 if (hovered
&& (flags
& ImGuiButtonFlags_AllowItemOverlap
) && (g
.HoveredIdPreviousFrame
!= id
&& g
.HoveredIdPreviousFrame
!= 0))
444 if (!(flags
& ImGuiButtonFlags_NoKeyModifiers
) || (!g
.IO
.KeyCtrl
&& !g
.IO
.KeyShift
&& !g
.IO
.KeyAlt
))
446 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
447 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
448 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
449 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
450 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
451 // FIXME-NAV: We don't honor those different behaviors.
452 if ((flags
& ImGuiButtonFlags_PressedOnClickRelease
) && g
.IO
.MouseClicked
[0])
454 SetActiveID(id
, window
);
455 if (!(flags
& ImGuiButtonFlags_NoNavFocus
))
456 SetFocusID(id
, window
);
459 if (((flags
& ImGuiButtonFlags_PressedOnClick
) && g
.IO
.MouseClicked
[0]) || ((flags
& ImGuiButtonFlags_PressedOnDoubleClick
) && g
.IO
.MouseDoubleClicked
[0]))
462 if (flags
& ImGuiButtonFlags_NoHoldingActiveID
)
465 SetActiveID(id
, window
); // Hold on ID
468 if ((flags
& ImGuiButtonFlags_PressedOnRelease
) && g
.IO
.MouseReleased
[0])
470 if (!((flags
& ImGuiButtonFlags_Repeat
) && g
.IO
.MouseDownDurationPrev
[0] >= g
.IO
.KeyRepeatDelay
)) // Repeat mode trumps <on release>
475 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
476 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
477 if ((flags
& ImGuiButtonFlags_Repeat
) && g
.ActiveId
== id
&& g
.IO
.MouseDownDuration
[0] > 0.0f
&& IsMouseClicked(0, true))
482 g
.NavDisableHighlight
= true;
485 // Gamepad/Keyboard navigation
486 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
487 if (g
.NavId
== id
&& !g
.NavDisableHighlight
&& g
.NavDisableMouseHover
&& (g
.ActiveId
== 0 || g
.ActiveId
== id
|| g
.ActiveId
== window
->MoveId
))
490 if (g
.NavActivateDownId
== id
)
492 bool nav_activated_by_code
= (g
.NavActivateId
== id
);
493 bool nav_activated_by_inputs
= IsNavInputPressed(ImGuiNavInput_Activate
, (flags
& ImGuiButtonFlags_Repeat
) ? ImGuiInputReadMode_Repeat
: ImGuiInputReadMode_Pressed
);
494 if (nav_activated_by_code
|| nav_activated_by_inputs
)
496 if (nav_activated_by_code
|| nav_activated_by_inputs
|| g
.ActiveId
== id
)
498 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
499 g
.NavActivateId
= id
; // This is so SetActiveId assign a Nav source
500 SetActiveID(id
, window
);
501 if ((nav_activated_by_code
|| nav_activated_by_inputs
) && !(flags
& ImGuiButtonFlags_NoNavFocus
))
502 SetFocusID(id
, window
);
503 g
.ActiveIdAllowNavDirFlags
= (1 << ImGuiDir_Left
) | (1 << ImGuiDir_Right
) | (1 << ImGuiDir_Up
) | (1 << ImGuiDir_Down
);
508 if (g
.ActiveId
== id
)
511 g
.ActiveIdHasBeenPressed
= true;
512 if (g
.ActiveIdSource
== ImGuiInputSource_Mouse
)
514 if (g
.ActiveIdIsJustActivated
)
515 g
.ActiveIdClickOffset
= g
.IO
.MousePos
- bb
.Min
;
516 if (g
.IO
.MouseDown
[0])
522 if (hovered
&& (flags
& ImGuiButtonFlags_PressedOnClickRelease
))
523 if (!((flags
& ImGuiButtonFlags_Repeat
) && g
.IO
.MouseDownDurationPrev
[0] >= g
.IO
.KeyRepeatDelay
)) // Repeat mode trumps <on release>
524 if (!g
.DragDropActive
)
528 if (!(flags
& ImGuiButtonFlags_NoNavFocus
))
529 g
.NavDisableHighlight
= true;
531 else if (g
.ActiveIdSource
== ImGuiInputSource_Nav
)
533 if (g
.NavActivateDownId
!= id
)
538 if (out_hovered
) *out_hovered
= hovered
;
539 if (out_held
) *out_held
= held
;
544 bool ImGui::ButtonEx(const char* label
, const ImVec2
& size_arg
, ImGuiButtonFlags flags
)
546 ImGuiWindow
* window
= GetCurrentWindow();
547 if (window
->SkipItems
)
550 ImGuiContext
& g
= *GImGui
;
551 const ImGuiStyle
& style
= g
.Style
;
552 const ImGuiID id
= window
->GetID(label
);
553 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
555 ImVec2 pos
= window
->DC
.CursorPos
;
556 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)
557 pos
.y
+= window
->DC
.CurrentLineTextBaseOffset
- style
.FramePadding
.y
;
558 ImVec2 size
= CalcItemSize(size_arg
, label_size
.x
+ style
.FramePadding
.x
* 2.0f
, label_size
.y
+ style
.FramePadding
.y
* 2.0f
);
560 const ImRect
bb(pos
, pos
+ size
);
561 ItemSize(size
, style
.FramePadding
.y
);
562 if (!ItemAdd(bb
, id
))
565 if (window
->DC
.ItemFlags
& ImGuiItemFlags_ButtonRepeat
)
566 flags
|= ImGuiButtonFlags_Repeat
;
568 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
, flags
);
573 const ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_ButtonActive
: hovered
? ImGuiCol_ButtonHovered
: ImGuiCol_Button
);
574 RenderNavHighlight(bb
, id
);
575 RenderFrame(bb
.Min
, bb
.Max
, col
, true, style
.FrameRounding
);
576 RenderTextClipped(bb
.Min
+ style
.FramePadding
, bb
.Max
- style
.FramePadding
, label
, NULL
, &label_size
, style
.ButtonTextAlign
, &bb
);
578 // Automatically close popups
579 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
580 // CloseCurrentPopup();
582 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.LastItemStatusFlags
);
586 bool ImGui::Button(const char* label
, const ImVec2
& size_arg
)
588 return ButtonEx(label
, size_arg
, 0);
591 // Small buttons fits within text without additional vertical spacing.
592 bool ImGui::SmallButton(const char* label
)
594 ImGuiContext
& g
= *GImGui
;
595 float backup_padding_y
= g
.Style
.FramePadding
.y
;
596 g
.Style
.FramePadding
.y
= 0.0f
;
597 bool pressed
= ButtonEx(label
, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine
);
598 g
.Style
.FramePadding
.y
= backup_padding_y
;
602 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
603 // 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)
604 bool ImGui::InvisibleButton(const char* str_id
, const ImVec2
& size_arg
)
606 ImGuiWindow
* window
= GetCurrentWindow();
607 if (window
->SkipItems
)
610 // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
611 IM_ASSERT(size_arg
.x
!= 0.0f
&& size_arg
.y
!= 0.0f
);
613 const ImGuiID id
= window
->GetID(str_id
);
614 ImVec2 size
= CalcItemSize(size_arg
, 0.0f
, 0.0f
);
615 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
617 if (!ItemAdd(bb
, id
))
621 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
);
626 bool ImGui::ArrowButtonEx(const char* str_id
, ImGuiDir dir
, ImVec2 size
, ImGuiButtonFlags flags
)
628 ImGuiWindow
* window
= GetCurrentWindow();
629 if (window
->SkipItems
)
632 ImGuiContext
& g
= *GImGui
;
633 const ImGuiID id
= window
->GetID(str_id
);
634 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
635 const float default_size
= GetFrameHeight();
636 ItemSize(bb
, (size
.y
>= default_size
) ? g
.Style
.FramePadding
.y
: 0.0f
);
637 if (!ItemAdd(bb
, id
))
640 if (window
->DC
.ItemFlags
& ImGuiItemFlags_ButtonRepeat
)
641 flags
|= ImGuiButtonFlags_Repeat
;
644 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
, flags
);
647 const ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_ButtonActive
: hovered
? ImGuiCol_ButtonHovered
: ImGuiCol_Button
);
648 RenderNavHighlight(bb
, id
);
649 RenderFrame(bb
.Min
, bb
.Max
, col
, true, g
.Style
.FrameRounding
);
650 RenderArrow(bb
.Min
+ ImVec2(ImMax(0.0f
, (size
.x
- g
.FontSize
) * 0.5f
), ImMax(0.0f
, (size
.y
- g
.FontSize
) * 0.5f
)), dir
);
655 bool ImGui::ArrowButton(const char* str_id
, ImGuiDir dir
)
657 float sz
= GetFrameHeight();
658 return ArrowButtonEx(str_id
, dir
, ImVec2(sz
, sz
), 0);
661 // Button to close a window
662 bool ImGui::CloseButton(ImGuiID id
, const ImVec2
& pos
, float radius
)
664 ImGuiContext
& g
= *GImGui
;
665 ImGuiWindow
* window
= g
.CurrentWindow
;
667 // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
668 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
669 const ImRect
bb(pos
- ImVec2(radius
,radius
), pos
+ ImVec2(radius
,radius
));
670 bool is_clipped
= !ItemAdd(bb
, id
);
673 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
);
678 ImVec2 center
= bb
.GetCenter();
680 window
->DrawList
->AddCircleFilled(center
, ImMax(2.0f
, radius
), GetColorU32(held
? ImGuiCol_ButtonActive
: ImGuiCol_ButtonHovered
), 9);
682 float cross_extent
= (radius
* 0.7071f
) - 1.0f
;
683 ImU32 cross_col
= GetColorU32(ImGuiCol_Text
);
684 center
-= ImVec2(0.5f
, 0.5f
);
685 window
->DrawList
->AddLine(center
+ ImVec2(+cross_extent
,+cross_extent
), center
+ ImVec2(-cross_extent
,-cross_extent
), cross_col
, 1.0f
);
686 window
->DrawList
->AddLine(center
+ ImVec2(+cross_extent
,-cross_extent
), center
+ ImVec2(-cross_extent
,+cross_extent
), cross_col
, 1.0f
);
691 bool ImGui::CollapseButton(ImGuiID id
, const ImVec2
& pos
)
693 ImGuiContext
& g
= *GImGui
;
694 ImGuiWindow
* window
= g
.CurrentWindow
;
696 ImRect
bb(pos
, pos
+ ImVec2(g
.FontSize
, g
.FontSize
) + g
.Style
.FramePadding
* 2.0f
);
699 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
, ImGuiButtonFlags_None
);
701 ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_ButtonActive
: hovered
? ImGuiCol_ButtonHovered
: ImGuiCol_Button
);
703 window
->DrawList
->AddCircleFilled(bb
.GetCenter() + ImVec2(0.0f
, -0.5f
), g
.FontSize
* 0.5f
+ 1.0f
, col
, 9);
704 RenderArrow(bb
.Min
+ g
.Style
.FramePadding
, window
->Collapsed
? ImGuiDir_Right
: ImGuiDir_Down
, 1.0f
);
706 // Switch to moving the window after mouse is moved beyond the initial drag threshold
707 if (IsItemActive() && IsMouseDragging())
708 StartMouseMovingWindow(window
);
713 ImGuiID
ImGui::GetScrollbarID(ImGuiLayoutType direction
)
715 ImGuiContext
& g
= *GImGui
;
716 ImGuiWindow
* window
= g
.CurrentWindow
;
717 return window
->GetID((direction
== ImGuiLayoutType_Horizontal
) ? "#SCROLLX" : "#SCROLLY");
720 // Vertical/Horizontal scrollbar
721 // The entire piece of code below is rather confusing because:
722 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
723 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
724 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
725 void ImGui::Scrollbar(ImGuiLayoutType direction
)
727 ImGuiContext
& g
= *GImGui
;
728 ImGuiWindow
* window
= g
.CurrentWindow
;
730 const bool horizontal
= (direction
== ImGuiLayoutType_Horizontal
);
731 const ImGuiStyle
& style
= g
.Style
;
732 const ImGuiID id
= GetScrollbarID(direction
);
735 bool other_scrollbar
= (horizontal
? window
->ScrollbarY
: window
->ScrollbarX
);
736 float other_scrollbar_size_w
= other_scrollbar
? style
.ScrollbarSize
: 0.0f
;
737 const ImRect window_rect
= window
->Rect();
738 const float border_size
= window
->WindowBorderSize
;
739 ImRect bb
= horizontal
740 ? ImRect(window
->Pos
.x
+ border_size
, window_rect
.Max
.y
- style
.ScrollbarSize
, window_rect
.Max
.x
- other_scrollbar_size_w
- border_size
, window_rect
.Max
.y
- border_size
)
741 : ImRect(window_rect
.Max
.x
- style
.ScrollbarSize
, window
->Pos
.y
+ border_size
, window_rect
.Max
.x
- border_size
, window_rect
.Max
.y
- other_scrollbar_size_w
- border_size
);
743 bb
.Min
.y
+= window
->TitleBarHeight() + ((window
->Flags
& ImGuiWindowFlags_MenuBar
) ? window
->MenuBarHeight() : 0.0f
);
745 const float bb_height
= bb
.GetHeight();
746 if (bb
.GetWidth() <= 0.0f
|| bb_height
<= 0.0f
)
749 // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab)
751 if ((direction
== ImGuiLayoutType_Vertical
) && bb_height
< g
.FontSize
+ g
.Style
.FramePadding
.y
* 2.0f
)
753 alpha
= ImSaturate((bb_height
- g
.FontSize
) / (g
.Style
.FramePadding
.y
* 2.0f
));
757 const bool allow_interaction
= (alpha
>= 1.0f
);
759 int window_rounding_corners
;
761 window_rounding_corners
= ImDrawCornerFlags_BotLeft
| (other_scrollbar
? 0 : ImDrawCornerFlags_BotRight
);
763 window_rounding_corners
= (((window
->Flags
& ImGuiWindowFlags_NoTitleBar
) && !(window
->Flags
& ImGuiWindowFlags_MenuBar
)) ? ImDrawCornerFlags_TopRight
: 0) | (other_scrollbar
? 0 : ImDrawCornerFlags_BotRight
);
764 window
->DrawList
->AddRectFilled(bb
.Min
, bb
.Max
, GetColorU32(ImGuiCol_ScrollbarBg
), window
->WindowRounding
, window_rounding_corners
);
765 bb
.Expand(ImVec2(-ImClamp((float)(int)((bb
.Max
.x
- bb
.Min
.x
- 2.0f
) * 0.5f
), 0.0f
, 3.0f
), -ImClamp((float)(int)((bb
.Max
.y
- bb
.Min
.y
- 2.0f
) * 0.5f
), 0.0f
, 3.0f
)));
767 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
768 float scrollbar_size_v
= horizontal
? bb
.GetWidth() : bb
.GetHeight();
769 float scroll_v
= horizontal
? window
->Scroll
.x
: window
->Scroll
.y
;
770 float win_size_avail_v
= (horizontal
? window
->SizeFull
.x
: window
->SizeFull
.y
) - other_scrollbar_size_w
;
771 float win_size_contents_v
= horizontal
? window
->SizeContents
.x
: window
->SizeContents
.y
;
773 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
774 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
775 IM_ASSERT(ImMax(win_size_contents_v
, win_size_avail_v
) > 0.0f
); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
776 const float win_size_v
= ImMax(ImMax(win_size_contents_v
, win_size_avail_v
), 1.0f
);
777 const float grab_h_pixels
= ImClamp(scrollbar_size_v
* (win_size_avail_v
/ win_size_v
), style
.GrabMinSize
, scrollbar_size_v
);
778 const float grab_h_norm
= grab_h_pixels
/ scrollbar_size_v
;
780 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
782 bool hovered
= false;
783 const bool previously_held
= (g
.ActiveId
== id
);
784 ButtonBehavior(bb
, id
, &hovered
, &held
, ImGuiButtonFlags_NoNavFocus
);
786 float scroll_max
= ImMax(1.0f
, win_size_contents_v
- win_size_avail_v
);
787 float scroll_ratio
= ImSaturate(scroll_v
/ scroll_max
);
788 float grab_v_norm
= scroll_ratio
* (scrollbar_size_v
- grab_h_pixels
) / scrollbar_size_v
;
789 if (held
&& allow_interaction
&& grab_h_norm
< 1.0f
)
791 float scrollbar_pos_v
= horizontal
? bb
.Min
.x
: bb
.Min
.y
;
792 float mouse_pos_v
= horizontal
? g
.IO
.MousePos
.x
: g
.IO
.MousePos
.y
;
793 float* click_delta_to_grab_center_v
= horizontal
? &g
.ScrollbarClickDeltaToGrabCenter
.x
: &g
.ScrollbarClickDeltaToGrabCenter
.y
;
795 // Click position in scrollbar normalized space (0.0f->1.0f)
796 const float clicked_v_norm
= ImSaturate((mouse_pos_v
- scrollbar_pos_v
) / scrollbar_size_v
);
799 bool seek_absolute
= false;
800 if (!previously_held
)
802 // On initial click calculate the distance between mouse and the center of the grab
803 if (clicked_v_norm
>= grab_v_norm
&& clicked_v_norm
<= grab_v_norm
+ grab_h_norm
)
805 *click_delta_to_grab_center_v
= clicked_v_norm
- grab_v_norm
- grab_h_norm
*0.5f
;
809 seek_absolute
= true;
810 *click_delta_to_grab_center_v
= 0.0f
;
815 // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
816 const float scroll_v_norm
= ImSaturate((clicked_v_norm
- *click_delta_to_grab_center_v
- grab_h_norm
*0.5f
) / (1.0f
- grab_h_norm
));
817 scroll_v
= (float)(int)(0.5f
+ scroll_v_norm
* scroll_max
);//(win_size_contents_v - win_size_v));
819 window
->Scroll
.x
= scroll_v
;
821 window
->Scroll
.y
= scroll_v
;
823 // Update values for rendering
824 scroll_ratio
= ImSaturate(scroll_v
/ scroll_max
);
825 grab_v_norm
= scroll_ratio
* (scrollbar_size_v
- grab_h_pixels
) / scrollbar_size_v
;
827 // Update distance to grab now that we have seeked and saturated
829 *click_delta_to_grab_center_v
= clicked_v_norm
- grab_v_norm
- grab_h_norm
*0.5f
;
833 const ImU32 grab_col
= GetColorU32(held
? ImGuiCol_ScrollbarGrabActive
: hovered
? ImGuiCol_ScrollbarGrabHovered
: ImGuiCol_ScrollbarGrab
, alpha
);
836 grab_rect
= ImRect(ImLerp(bb
.Min
.x
, bb
.Max
.x
, grab_v_norm
), bb
.Min
.y
, ImMin(ImLerp(bb
.Min
.x
, bb
.Max
.x
, grab_v_norm
) + grab_h_pixels
, window_rect
.Max
.x
), bb
.Max
.y
);
838 grab_rect
= ImRect(bb
.Min
.x
, ImLerp(bb
.Min
.y
, bb
.Max
.y
, grab_v_norm
), bb
.Max
.x
, ImMin(ImLerp(bb
.Min
.y
, bb
.Max
.y
, grab_v_norm
) + grab_h_pixels
, window_rect
.Max
.y
));
839 window
->DrawList
->AddRectFilled(grab_rect
.Min
, grab_rect
.Max
, grab_col
, style
.ScrollbarRounding
);
842 void ImGui::Image(ImTextureID user_texture_id
, const ImVec2
& size
, const ImVec2
& uv0
, const ImVec2
& uv1
, const ImVec4
& tint_col
, const ImVec4
& border_col
)
844 ImGuiWindow
* window
= GetCurrentWindow();
845 if (window
->SkipItems
)
848 ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
849 if (border_col
.w
> 0.0f
)
850 bb
.Max
+= ImVec2(2, 2);
855 if (border_col
.w
> 0.0f
)
857 window
->DrawList
->AddRect(bb
.Min
, bb
.Max
, GetColorU32(border_col
), 0.0f
);
858 window
->DrawList
->AddImage(user_texture_id
, bb
.Min
+ ImVec2(1, 1), bb
.Max
- ImVec2(1, 1), uv0
, uv1
, GetColorU32(tint_col
));
862 window
->DrawList
->AddImage(user_texture_id
, bb
.Min
, bb
.Max
, uv0
, uv1
, GetColorU32(tint_col
));
866 // frame_padding < 0: uses FramePadding from style (default)
867 // frame_padding = 0: no framing
868 // frame_padding > 0: set framing size
869 // The color used are the button colors.
870 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
)
872 ImGuiWindow
* window
= GetCurrentWindow();
873 if (window
->SkipItems
)
876 ImGuiContext
& g
= *GImGui
;
877 const ImGuiStyle
& style
= g
.Style
;
879 // Default to using texture ID as ID. User can still push string/integer prefixes.
880 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
881 PushID((void*)(intptr_t)user_texture_id
);
882 const ImGuiID id
= window
->GetID("#image");
885 const ImVec2 padding
= (frame_padding
>= 0) ? ImVec2((float)frame_padding
, (float)frame_padding
) : style
.FramePadding
;
886 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
+ padding
* 2);
887 const ImRect
image_bb(window
->DC
.CursorPos
+ padding
, window
->DC
.CursorPos
+ padding
+ size
);
889 if (!ItemAdd(bb
, id
))
893 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
);
896 const ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_ButtonActive
: hovered
? ImGuiCol_ButtonHovered
: ImGuiCol_Button
);
897 RenderNavHighlight(bb
, id
);
898 RenderFrame(bb
.Min
, bb
.Max
, col
, true, ImClamp((float)ImMin(padding
.x
, padding
.y
), 0.0f
, style
.FrameRounding
));
900 window
->DrawList
->AddRectFilled(image_bb
.Min
, image_bb
.Max
, GetColorU32(bg_col
));
901 window
->DrawList
->AddImage(user_texture_id
, image_bb
.Min
, image_bb
.Max
, uv0
, uv1
, GetColorU32(tint_col
));
906 bool ImGui::Checkbox(const char* label
, bool* v
)
908 ImGuiWindow
* window
= GetCurrentWindow();
909 if (window
->SkipItems
)
912 ImGuiContext
& g
= *GImGui
;
913 const ImGuiStyle
& style
= g
.Style
;
914 const ImGuiID id
= window
->GetID(label
);
915 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
917 const float square_sz
= GetFrameHeight();
918 const ImVec2 pos
= window
->DC
.CursorPos
;
919 const ImRect
total_bb(pos
, pos
+ ImVec2(square_sz
+ (label_size
.x
> 0.0f
? style
.ItemInnerSpacing
.x
+ label_size
.x
: 0.0f
), label_size
.y
+ style
.FramePadding
.y
* 2.0f
));
920 ItemSize(total_bb
, style
.FramePadding
.y
);
921 if (!ItemAdd(total_bb
, id
))
925 bool pressed
= ButtonBehavior(total_bb
, id
, &hovered
, &held
);
932 const ImRect
check_bb(pos
, pos
+ ImVec2(square_sz
, square_sz
));
933 RenderNavHighlight(total_bb
, id
);
934 RenderFrame(check_bb
.Min
, check_bb
.Max
, GetColorU32((held
&& hovered
) ? ImGuiCol_FrameBgActive
: hovered
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
), true, style
.FrameRounding
);
937 const float pad
= ImMax(1.0f
, (float)(int)(square_sz
/ 6.0f
));
938 RenderCheckMark(check_bb
.Min
+ ImVec2(pad
, pad
), GetColorU32(ImGuiCol_CheckMark
), square_sz
- pad
*2.0f
);
942 LogRenderedText(&total_bb
.Min
, *v
? "[x]" : "[ ]");
943 if (label_size
.x
> 0.0f
)
944 RenderText(ImVec2(check_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, check_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
946 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
| ImGuiItemStatusFlags_Checkable
| (*v
? ImGuiItemStatusFlags_Checked
: 0));
950 bool ImGui::CheckboxFlags(const char* label
, unsigned int* flags
, unsigned int flags_value
)
952 bool v
= ((*flags
& flags_value
) == flags_value
);
953 bool pressed
= Checkbox(label
, &v
);
957 *flags
|= flags_value
;
959 *flags
&= ~flags_value
;
965 bool ImGui::RadioButton(const char* label
, bool active
)
967 ImGuiWindow
* window
= GetCurrentWindow();
968 if (window
->SkipItems
)
971 ImGuiContext
& g
= *GImGui
;
972 const ImGuiStyle
& style
= g
.Style
;
973 const ImGuiID id
= window
->GetID(label
);
974 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
976 const float square_sz
= GetFrameHeight();
977 const ImVec2 pos
= window
->DC
.CursorPos
;
978 const ImRect
check_bb(pos
, pos
+ ImVec2(square_sz
, square_sz
));
979 const ImRect
total_bb(pos
, pos
+ ImVec2(square_sz
+ (label_size
.x
> 0.0f
? style
.ItemInnerSpacing
.x
+ label_size
.x
: 0.0f
), label_size
.y
+ style
.FramePadding
.y
* 2.0f
));
980 ItemSize(total_bb
, style
.FramePadding
.y
);
981 if (!ItemAdd(total_bb
, id
))
984 ImVec2 center
= check_bb
.GetCenter();
985 center
.x
= (float)(int)center
.x
+ 0.5f
;
986 center
.y
= (float)(int)center
.y
+ 0.5f
;
987 const float radius
= (square_sz
- 1.0f
) * 0.5f
;
990 bool pressed
= ButtonBehavior(total_bb
, id
, &hovered
, &held
);
994 RenderNavHighlight(total_bb
, id
);
995 window
->DrawList
->AddCircleFilled(center
, radius
, GetColorU32((held
&& hovered
) ? ImGuiCol_FrameBgActive
: hovered
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
), 16);
998 const float pad
= ImMax(1.0f
, (float)(int)(square_sz
/ 6.0f
));
999 window
->DrawList
->AddCircleFilled(center
, radius
- pad
, GetColorU32(ImGuiCol_CheckMark
), 16);
1002 if (style
.FrameBorderSize
> 0.0f
)
1004 window
->DrawList
->AddCircle(center
+ ImVec2(1,1), radius
, GetColorU32(ImGuiCol_BorderShadow
), 16, style
.FrameBorderSize
);
1005 window
->DrawList
->AddCircle(center
, radius
, GetColorU32(ImGuiCol_Border
), 16, style
.FrameBorderSize
);
1009 LogRenderedText(&total_bb
.Min
, active
? "(x)" : "( )");
1010 if (label_size
.x
> 0.0f
)
1011 RenderText(ImVec2(check_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, check_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
1016 bool ImGui::RadioButton(const char* label
, int* v
, int v_button
)
1018 const bool pressed
= RadioButton(label
, *v
== v_button
);
1024 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1025 void ImGui::ProgressBar(float fraction
, const ImVec2
& size_arg
, const char* overlay
)
1027 ImGuiWindow
* window
= GetCurrentWindow();
1028 if (window
->SkipItems
)
1031 ImGuiContext
& g
= *GImGui
;
1032 const ImGuiStyle
& style
= g
.Style
;
1034 ImVec2 pos
= window
->DC
.CursorPos
;
1035 ImRect
bb(pos
, pos
+ CalcItemSize(size_arg
, CalcItemWidth(), g
.FontSize
+ style
.FramePadding
.y
*2.0f
));
1036 ItemSize(bb
, style
.FramePadding
.y
);
1037 if (!ItemAdd(bb
, 0))
1041 fraction
= ImSaturate(fraction
);
1042 RenderFrame(bb
.Min
, bb
.Max
, GetColorU32(ImGuiCol_FrameBg
), true, style
.FrameRounding
);
1043 bb
.Expand(ImVec2(-style
.FrameBorderSize
, -style
.FrameBorderSize
));
1044 const ImVec2 fill_br
= ImVec2(ImLerp(bb
.Min
.x
, bb
.Max
.x
, fraction
), bb
.Max
.y
);
1045 RenderRectFilledRangeH(window
->DrawList
, bb
, GetColorU32(ImGuiCol_PlotHistogram
), 0.0f
, fraction
, style
.FrameRounding
);
1047 // Default displaying the fraction as percentage string, but user can override it
1048 char overlay_buf
[32];
1051 ImFormatString(overlay_buf
, IM_ARRAYSIZE(overlay_buf
), "%.0f%%", fraction
*100+0.01f
);
1052 overlay
= overlay_buf
;
1055 ImVec2 overlay_size
= CalcTextSize(overlay
, NULL
);
1056 if (overlay_size
.x
> 0.0f
)
1057 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
);
1060 void ImGui::Bullet()
1062 ImGuiWindow
* window
= GetCurrentWindow();
1063 if (window
->SkipItems
)
1066 ImGuiContext
& g
= *GImGui
;
1067 const ImGuiStyle
& style
= g
.Style
;
1068 const float line_height
= ImMax(ImMin(window
->DC
.CurrentLineSize
.y
, g
.FontSize
+ g
.Style
.FramePadding
.y
*2), g
.FontSize
);
1069 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(g
.FontSize
, line_height
));
1071 if (!ItemAdd(bb
, 0))
1073 SameLine(0, style
.FramePadding
.x
*2);
1077 // Render and stay on same line
1078 RenderBullet(bb
.Min
+ ImVec2(style
.FramePadding
.x
+ g
.FontSize
*0.5f
, line_height
*0.5f
));
1079 SameLine(0, style
.FramePadding
.x
*2);
1082 //-------------------------------------------------------------------------
1083 // [SECTION] Widgets: Low-level Layout helpers
1084 //-------------------------------------------------------------------------
1088 // - AlignTextToFramePadding()
1090 // - VerticalSeparator() [Internal]
1091 // - SplitterBehavior() [Internal]
1092 //-------------------------------------------------------------------------
1094 void ImGui::Spacing()
1096 ImGuiWindow
* window
= GetCurrentWindow();
1097 if (window
->SkipItems
)
1099 ItemSize(ImVec2(0,0));
1102 void ImGui::Dummy(const ImVec2
& size
)
1104 ImGuiWindow
* window
= GetCurrentWindow();
1105 if (window
->SkipItems
)
1108 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
1113 void ImGui::NewLine()
1115 ImGuiWindow
* window
= GetCurrentWindow();
1116 if (window
->SkipItems
)
1119 ImGuiContext
& g
= *GImGui
;
1120 const ImGuiLayoutType backup_layout_type
= window
->DC
.LayoutType
;
1121 window
->DC
.LayoutType
= ImGuiLayoutType_Vertical
;
1122 if (window
->DC
.CurrentLineSize
.y
> 0.0f
) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
1123 ItemSize(ImVec2(0,0));
1125 ItemSize(ImVec2(0.0f
, g
.FontSize
));
1126 window
->DC
.LayoutType
= backup_layout_type
;
1129 void ImGui::AlignTextToFramePadding()
1131 ImGuiWindow
* window
= GetCurrentWindow();
1132 if (window
->SkipItems
)
1135 ImGuiContext
& g
= *GImGui
;
1136 window
->DC
.CurrentLineSize
.y
= ImMax(window
->DC
.CurrentLineSize
.y
, g
.FontSize
+ g
.Style
.FramePadding
.y
* 2);
1137 window
->DC
.CurrentLineTextBaseOffset
= ImMax(window
->DC
.CurrentLineTextBaseOffset
, g
.Style
.FramePadding
.y
);
1140 // Horizontal/vertical separating line
1141 void ImGui::Separator()
1143 ImGuiWindow
* window
= GetCurrentWindow();
1144 if (window
->SkipItems
)
1146 ImGuiContext
& g
= *GImGui
;
1148 // Those flags should eventually be overridable by the user
1149 ImGuiSeparatorFlags flags
= (window
->DC
.LayoutType
== ImGuiLayoutType_Horizontal
) ? ImGuiSeparatorFlags_Vertical
: ImGuiSeparatorFlags_Horizontal
;
1150 IM_ASSERT(ImIsPowerOfTwo((int)(flags
& (ImGuiSeparatorFlags_Horizontal
| ImGuiSeparatorFlags_Vertical
)))); // Check that only 1 option is selected
1151 if (flags
& ImGuiSeparatorFlags_Vertical
)
1153 VerticalSeparator();
1157 // Horizontal Separator
1158 if (window
->DC
.ColumnsSet
)
1161 float x1
= window
->Pos
.x
;
1162 float x2
= window
->Pos
.x
+ window
->Size
.x
;
1163 if (!window
->DC
.GroupStack
.empty())
1164 x1
+= window
->DC
.Indent
.x
;
1166 const ImRect
bb(ImVec2(x1
, window
->DC
.CursorPos
.y
), ImVec2(x2
, window
->DC
.CursorPos
.y
+1.0f
));
1167 ItemSize(ImVec2(0.0f
, 0.0f
)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
1168 if (!ItemAdd(bb
, 0))
1170 if (window
->DC
.ColumnsSet
)
1171 PushColumnClipRect();
1175 window
->DrawList
->AddLine(bb
.Min
, ImVec2(bb
.Max
.x
,bb
.Min
.y
), GetColorU32(ImGuiCol_Separator
));
1178 LogRenderedText(&bb
.Min
, "--------------------------------");
1180 if (window
->DC
.ColumnsSet
)
1182 PushColumnClipRect();
1183 window
->DC
.ColumnsSet
->LineMinY
= window
->DC
.CursorPos
.y
;
1187 void ImGui::VerticalSeparator()
1189 ImGuiWindow
* window
= GetCurrentWindow();
1190 if (window
->SkipItems
)
1192 ImGuiContext
& g
= *GImGui
;
1194 float y1
= window
->DC
.CursorPos
.y
;
1195 float y2
= window
->DC
.CursorPos
.y
+ window
->DC
.CurrentLineSize
.y
;
1196 const ImRect
bb(ImVec2(window
->DC
.CursorPos
.x
, y1
), ImVec2(window
->DC
.CursorPos
.x
+ 1.0f
, y2
));
1197 ItemSize(ImVec2(bb
.GetWidth(), 0.0f
));
1198 if (!ItemAdd(bb
, 0))
1201 window
->DrawList
->AddLine(ImVec2(bb
.Min
.x
, bb
.Min
.y
), ImVec2(bb
.Min
.x
, bb
.Max
.y
), GetColorU32(ImGuiCol_Separator
));
1206 // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
1207 bool ImGui::SplitterBehavior(const ImRect
& bb
, ImGuiID id
, ImGuiAxis axis
, float* size1
, float* size2
, float min_size1
, float min_size2
, float hover_extend
, float hover_visibility_delay
)
1209 ImGuiContext
& g
= *GImGui
;
1210 ImGuiWindow
* window
= g
.CurrentWindow
;
1212 const ImGuiItemFlags item_flags_backup
= window
->DC
.ItemFlags
;
1213 window
->DC
.ItemFlags
|= ImGuiItemFlags_NoNav
| ImGuiItemFlags_NoNavDefaultFocus
;
1214 bool item_add
= ItemAdd(bb
, id
);
1215 window
->DC
.ItemFlags
= item_flags_backup
;
1220 ImRect bb_interact
= bb
;
1221 bb_interact
.Expand(axis
== ImGuiAxis_Y
? ImVec2(0.0f
, hover_extend
) : ImVec2(hover_extend
, 0.0f
));
1222 ButtonBehavior(bb_interact
, id
, &hovered
, &held
, ImGuiButtonFlags_FlattenChildren
| ImGuiButtonFlags_AllowItemOverlap
);
1223 if (g
.ActiveId
!= id
)
1224 SetItemAllowOverlap();
1226 if (held
|| (g
.HoveredId
== id
&& g
.HoveredIdPreviousFrame
== id
&& g
.HoveredIdTimer
>= hover_visibility_delay
))
1227 SetMouseCursor(axis
== ImGuiAxis_Y
? ImGuiMouseCursor_ResizeNS
: ImGuiMouseCursor_ResizeEW
);
1229 ImRect bb_render
= bb
;
1232 ImVec2 mouse_delta_2d
= g
.IO
.MousePos
- g
.ActiveIdClickOffset
- bb_interact
.Min
;
1233 float mouse_delta
= (axis
== ImGuiAxis_Y
) ? mouse_delta_2d
.y
: mouse_delta_2d
.x
;
1235 // Minimum pane size
1236 float size_1_maximum_delta
= ImMax(0.0f
, *size1
- min_size1
);
1237 float size_2_maximum_delta
= ImMax(0.0f
, *size2
- min_size2
);
1238 if (mouse_delta
< -size_1_maximum_delta
)
1239 mouse_delta
= -size_1_maximum_delta
;
1240 if (mouse_delta
> size_2_maximum_delta
)
1241 mouse_delta
= size_2_maximum_delta
;
1244 if (mouse_delta
!= 0.0f
)
1246 if (mouse_delta
< 0.0f
)
1247 IM_ASSERT(*size1
+ mouse_delta
>= min_size1
);
1248 if (mouse_delta
> 0.0f
)
1249 IM_ASSERT(*size2
- mouse_delta
>= min_size2
);
1250 *size1
+= mouse_delta
;
1251 *size2
-= mouse_delta
;
1252 bb_render
.Translate((axis
== ImGuiAxis_X
) ? ImVec2(mouse_delta
, 0.0f
) : ImVec2(0.0f
, mouse_delta
));
1258 const ImU32 col
= GetColorU32(held
? ImGuiCol_SeparatorActive
: (hovered
&& g
.HoveredIdTimer
>= hover_visibility_delay
) ? ImGuiCol_SeparatorHovered
: ImGuiCol_Separator
);
1259 window
->DrawList
->AddRectFilled(bb_render
.Min
, bb_render
.Max
, col
, g
.Style
.FrameRounding
);
1264 //-------------------------------------------------------------------------
1265 // [SECTION] Widgets: ComboBox
1266 //-------------------------------------------------------------------------
1270 //-------------------------------------------------------------------------
1272 static float CalcMaxPopupHeightFromItemCount(int items_count
)
1274 ImGuiContext
& g
= *GImGui
;
1275 if (items_count
<= 0)
1277 return (g
.FontSize
+ g
.Style
.ItemSpacing
.y
) * items_count
- g
.Style
.ItemSpacing
.y
+ (g
.Style
.WindowPadding
.y
* 2);
1280 bool ImGui::BeginCombo(const char* label
, const char* preview_value
, ImGuiComboFlags flags
)
1282 // Always consume the SetNextWindowSizeConstraint() call in our early return paths
1283 ImGuiContext
& g
= *GImGui
;
1284 ImGuiCond backup_next_window_size_constraint
= g
.NextWindowData
.SizeConstraintCond
;
1285 g
.NextWindowData
.SizeConstraintCond
= 0;
1287 ImGuiWindow
* window
= GetCurrentWindow();
1288 if (window
->SkipItems
)
1291 IM_ASSERT((flags
& (ImGuiComboFlags_NoArrowButton
| ImGuiComboFlags_NoPreview
)) != (ImGuiComboFlags_NoArrowButton
| ImGuiComboFlags_NoPreview
)); // Can't use both flags together
1293 const ImGuiStyle
& style
= g
.Style
;
1294 const ImGuiID id
= window
->GetID(label
);
1296 const float arrow_size
= (flags
& ImGuiComboFlags_NoArrowButton
) ? 0.0f
: GetFrameHeight();
1297 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
1298 const float w
= (flags
& ImGuiComboFlags_NoPreview
) ? arrow_size
: CalcItemWidth();
1299 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(w
, label_size
.y
+ style
.FramePadding
.y
*2.0f
));
1300 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
));
1301 ItemSize(total_bb
, style
.FramePadding
.y
);
1302 if (!ItemAdd(total_bb
, id
, &frame_bb
))
1306 bool pressed
= ButtonBehavior(frame_bb
, id
, &hovered
, &held
);
1307 bool popup_open
= IsPopupOpen(id
);
1309 const ImRect
value_bb(frame_bb
.Min
, frame_bb
.Max
- ImVec2(arrow_size
, 0.0f
));
1310 const ImU32 frame_col
= GetColorU32(hovered
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
);
1311 RenderNavHighlight(frame_bb
, id
);
1312 if (!(flags
& ImGuiComboFlags_NoPreview
))
1313 window
->DrawList
->AddRectFilled(frame_bb
.Min
, ImVec2(frame_bb
.Max
.x
- arrow_size
, frame_bb
.Max
.y
), frame_col
, style
.FrameRounding
, ImDrawCornerFlags_Left
);
1314 if (!(flags
& ImGuiComboFlags_NoArrowButton
))
1316 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
);
1317 RenderArrow(ImVec2(frame_bb
.Max
.x
- arrow_size
+ style
.FramePadding
.y
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), ImGuiDir_Down
);
1319 RenderFrameBorder(frame_bb
.Min
, frame_bb
.Max
, style
.FrameRounding
);
1320 if (preview_value
!= NULL
&& !(flags
& ImGuiComboFlags_NoPreview
))
1321 RenderTextClipped(frame_bb
.Min
+ style
.FramePadding
, value_bb
.Max
, preview_value
, NULL
, NULL
, ImVec2(0.0f
,0.0f
));
1322 if (label_size
.x
> 0)
1323 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
1325 if ((pressed
|| g
.NavActivateId
== id
) && !popup_open
)
1327 if (window
->DC
.NavLayerCurrent
== 0)
1328 window
->NavLastIds
[0] = id
;
1336 if (backup_next_window_size_constraint
)
1338 g
.NextWindowData
.SizeConstraintCond
= backup_next_window_size_constraint
;
1339 g
.NextWindowData
.SizeConstraintRect
.Min
.x
= ImMax(g
.NextWindowData
.SizeConstraintRect
.Min
.x
, w
);
1343 if ((flags
& ImGuiComboFlags_HeightMask_
) == 0)
1344 flags
|= ImGuiComboFlags_HeightRegular
;
1345 IM_ASSERT(ImIsPowerOfTwo(flags
& ImGuiComboFlags_HeightMask_
)); // Only one
1346 int popup_max_height_in_items
= -1;
1347 if (flags
& ImGuiComboFlags_HeightRegular
) popup_max_height_in_items
= 8;
1348 else if (flags
& ImGuiComboFlags_HeightSmall
) popup_max_height_in_items
= 4;
1349 else if (flags
& ImGuiComboFlags_HeightLarge
) popup_max_height_in_items
= 20;
1350 SetNextWindowSizeConstraints(ImVec2(w
, 0.0f
), ImVec2(FLT_MAX
, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items
)));
1354 ImFormatString(name
, IM_ARRAYSIZE(name
), "##Combo_%02d", g
.BeginPopupStack
.Size
); // Recycle windows based on depth
1356 // Peak into expected window size so we can position it
1357 if (ImGuiWindow
* popup_window
= FindWindowByName(name
))
1358 if (popup_window
->WasActive
)
1360 ImVec2 size_expected
= CalcWindowExpectedSize(popup_window
);
1361 if (flags
& ImGuiComboFlags_PopupAlignLeft
)
1362 popup_window
->AutoPosLastDirection
= ImGuiDir_Left
;
1363 ImRect r_outer
= GetWindowAllowedExtentRect(popup_window
);
1364 ImVec2 pos
= FindBestWindowPosForPopupEx(frame_bb
.GetBL(), size_expected
, &popup_window
->AutoPosLastDirection
, r_outer
, frame_bb
, ImGuiPopupPositionPolicy_ComboBox
);
1365 SetNextWindowPos(pos
);
1368 // Horizontally align ourselves with the framed text
1369 ImGuiWindowFlags window_flags
= ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_Popup
| ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoSavedSettings
;
1370 PushStyleVar(ImGuiStyleVar_WindowPadding
, ImVec2(style
.FramePadding
.x
, style
.WindowPadding
.y
));
1371 bool ret
= Begin(name
, NULL
, window_flags
);
1376 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1382 void ImGui::EndCombo()
1387 // Getter for the old Combo() API: const char*[]
1388 static bool Items_ArrayGetter(void* data
, int idx
, const char** out_text
)
1390 const char* const* items
= (const char* const*)data
;
1392 *out_text
= items
[idx
];
1396 // Getter for the old Combo() API: "item1\0item2\0item3\0"
1397 static bool Items_SingleStringGetter(void* data
, int idx
, const char** out_text
)
1399 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
1400 const char* items_separated_by_zeros
= (const char*)data
;
1401 int items_count
= 0;
1402 const char* p
= items_separated_by_zeros
;
1405 if (idx
== items_count
)
1417 // Old API, prefer using BeginCombo() nowadays if you can.
1418 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
)
1420 ImGuiContext
& g
= *GImGui
;
1422 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1423 const char* preview_value
= NULL
;
1424 if (*current_item
>= 0 && *current_item
< items_count
)
1425 items_getter(data
, *current_item
, &preview_value
);
1427 // 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.
1428 if (popup_max_height_in_items
!= -1 && !g
.NextWindowData
.SizeConstraintCond
)
1429 SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX
, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items
)));
1431 if (!BeginCombo(label
, preview_value
, ImGuiComboFlags_None
))
1435 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
1436 bool value_changed
= false;
1437 for (int i
= 0; i
< items_count
; i
++)
1439 PushID((void*)(intptr_t)i
);
1440 const bool item_selected
= (i
== *current_item
);
1441 const char* item_text
;
1442 if (!items_getter(data
, i
, &item_text
))
1443 item_text
= "*Unknown item*";
1444 if (Selectable(item_text
, item_selected
))
1446 value_changed
= true;
1450 SetItemDefaultFocus();
1455 return value_changed
;
1458 // Combo box helper allowing to pass an array of strings.
1459 bool ImGui::Combo(const char* label
, int* current_item
, const char* const items
[], int items_count
, int height_in_items
)
1461 const bool value_changed
= Combo(label
, current_item
, Items_ArrayGetter
, (void*)items
, items_count
, height_in_items
);
1462 return value_changed
;
1465 // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1466 bool ImGui::Combo(const char* label
, int* current_item
, const char* items_separated_by_zeros
, int height_in_items
)
1468 int items_count
= 0;
1469 const char* p
= items_separated_by_zeros
; // FIXME-OPT: Avoid computing this, or at least only when combo is open
1475 bool value_changed
= Combo(label
, current_item
, Items_SingleStringGetter
, (void*)items_separated_by_zeros
, items_count
, height_in_items
);
1476 return value_changed
;
1479 //-------------------------------------------------------------------------
1480 // [SECTION] Data Type and Data Formatting Helpers [Internal]
1481 //-------------------------------------------------------------------------
1482 // - PatchFormatStringFloatToInt()
1483 // - DataTypeFormatString()
1484 // - DataTypeApplyOp()
1485 // - DataTypeApplyOpFromText()
1486 // - GetMinimumStepAtDecimalPrecision
1487 // - RoundScalarWithFormat<>()
1488 //-------------------------------------------------------------------------
1490 struct ImGuiDataTypeInfo
1493 const char* PrintFmt
; // Unused
1494 const char* ScanFmt
;
1497 static const ImGuiDataTypeInfo GDataTypeInfo
[] =
1499 { sizeof(int), "%d", "%d" },
1500 { sizeof(unsigned int), "%u", "%u" },
1502 { sizeof(ImS64
), "%I64d","%I64d" },
1503 { sizeof(ImU64
), "%I64u","%I64u" },
1505 { sizeof(ImS64
), "%lld", "%lld" },
1506 { sizeof(ImU64
), "%llu", "%llu" },
1508 { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg
1509 { sizeof(double), "%f", "%lf" },
1511 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo
) == ImGuiDataType_COUNT
);
1513 // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
1514 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
1515 // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
1516 static const char* PatchFormatStringFloatToInt(const char* fmt
)
1518 if (fmt
[0] == '%' && fmt
[1] == '.' && fmt
[2] == '0' && fmt
[3] == 'f' && fmt
[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
1520 const char* fmt_start
= ImParseFormatFindStart(fmt
); // Find % (if any, and ignore %%)
1521 const char* fmt_end
= ImParseFormatFindEnd(fmt_start
); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
1522 if (fmt_end
> fmt_start
&& fmt_end
[-1] == 'f')
1524 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1525 if (fmt_start
== fmt
&& fmt_end
[0] == 0)
1527 ImGuiContext
& g
= *GImGui
;
1528 ImFormatString(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), "%.*s%%d%s", (int)(fmt_start
- fmt
), fmt
, fmt_end
); // Honor leading and trailing decorations, but lose alignment/precision.
1529 return g
.TempBuffer
;
1531 IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1537 static inline int DataTypeFormatString(char* buf
, int buf_size
, ImGuiDataType data_type
, const void* data_ptr
, const char* format
)
1539 if (data_type
== ImGuiDataType_S32
|| data_type
== ImGuiDataType_U32
) // Signedness doesn't matter when pushing the argument
1540 return ImFormatString(buf
, buf_size
, format
, *(const ImU32
*)data_ptr
);
1541 if (data_type
== ImGuiDataType_S64
|| data_type
== ImGuiDataType_U64
) // Signedness doesn't matter when pushing the argument
1542 return ImFormatString(buf
, buf_size
, format
, *(const ImU64
*)data_ptr
);
1543 if (data_type
== ImGuiDataType_Float
)
1544 return ImFormatString(buf
, buf_size
, format
, *(const float*)data_ptr
);
1545 if (data_type
== ImGuiDataType_Double
)
1546 return ImFormatString(buf
, buf_size
, format
, *(const double*)data_ptr
);
1551 // FIXME: Adding support for clamping on boundaries of the data type would be nice.
1552 static void DataTypeApplyOp(ImGuiDataType data_type
, int op
, void* output
, void* arg1
, const void* arg2
)
1554 IM_ASSERT(op
== '+' || op
== '-');
1557 case ImGuiDataType_S32
:
1558 if (op
== '+') *(int*)output
= *(const int*)arg1
+ *(const int*)arg2
;
1559 else if (op
== '-') *(int*)output
= *(const int*)arg1
- *(const int*)arg2
;
1561 case ImGuiDataType_U32
:
1562 if (op
== '+') *(unsigned int*)output
= *(const unsigned int*)arg1
+ *(const ImU32
*)arg2
;
1563 else if (op
== '-') *(unsigned int*)output
= *(const unsigned int*)arg1
- *(const ImU32
*)arg2
;
1565 case ImGuiDataType_S64
:
1566 if (op
== '+') *(ImS64
*)output
= *(const ImS64
*)arg1
+ *(const ImS64
*)arg2
;
1567 else if (op
== '-') *(ImS64
*)output
= *(const ImS64
*)arg1
- *(const ImS64
*)arg2
;
1569 case ImGuiDataType_U64
:
1570 if (op
== '+') *(ImU64
*)output
= *(const ImU64
*)arg1
+ *(const ImU64
*)arg2
;
1571 else if (op
== '-') *(ImU64
*)output
= *(const ImU64
*)arg1
- *(const ImU64
*)arg2
;
1573 case ImGuiDataType_Float
:
1574 if (op
== '+') *(float*)output
= *(const float*)arg1
+ *(const float*)arg2
;
1575 else if (op
== '-') *(float*)output
= *(const float*)arg1
- *(const float*)arg2
;
1577 case ImGuiDataType_Double
:
1578 if (op
== '+') *(double*)output
= *(const double*)arg1
+ *(const double*)arg2
;
1579 else if (op
== '-') *(double*)output
= *(const double*)arg1
- *(const double*)arg2
;
1581 case ImGuiDataType_COUNT
: break;
1586 // User can input math operators (e.g. +100) to edit a numerical values.
1587 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
1588 static bool DataTypeApplyOpFromText(const char* buf
, const char* initial_value_buf
, ImGuiDataType data_type
, void* data_ptr
, const char* format
)
1590 while (ImCharIsBlankA(*buf
))
1593 // We don't support '-' op because it would conflict with inputing negative value.
1594 // Instead you can use +-100 to subtract from an existing value
1596 if (op
== '+' || op
== '*' || op
== '/')
1599 while (ImCharIsBlankA(*buf
))
1609 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
1610 IM_ASSERT(data_type
< ImGuiDataType_COUNT
);
1612 IM_ASSERT(GDataTypeInfo
[data_type
].Size
<= sizeof(data_backup
));
1613 memcpy(data_backup
, data_ptr
, GDataTypeInfo
[data_type
].Size
);
1616 format
= GDataTypeInfo
[data_type
].ScanFmt
;
1619 if (data_type
== ImGuiDataType_S32
)
1621 int* v
= (int*)data_ptr
;
1624 if (op
&& sscanf(initial_value_buf
, format
, &arg0i
) < 1)
1626 // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
1627 if (op
== '+') { if (sscanf(buf
, "%d", &arg1i
)) *v
= (int)(arg0i
+ arg1i
); } // Add (use "+-" to subtract)
1628 else if (op
== '*') { if (sscanf(buf
, "%f", &arg1f
)) *v
= (int)(arg0i
* arg1f
); } // Multiply
1629 else if (op
== '/') { if (sscanf(buf
, "%f", &arg1f
) && arg1f
!= 0.0f
) *v
= (int)(arg0i
/ arg1f
); } // Divide
1630 else { if (sscanf(buf
, format
, &arg1i
) == 1) *v
= arg1i
; } // Assign constant
1632 else if (data_type
== ImGuiDataType_U32
|| data_type
== ImGuiDataType_S64
|| data_type
== ImGuiDataType_U64
)
1635 // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
1636 sscanf(buf
, format
, data_ptr
);
1638 else if (data_type
== ImGuiDataType_Float
)
1640 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
1642 float* v
= (float*)data_ptr
;
1643 float arg0f
= *v
, arg1f
= 0.0f
;
1644 if (op
&& sscanf(initial_value_buf
, format
, &arg0f
) < 1)
1646 if (sscanf(buf
, format
, &arg1f
) < 1)
1648 if (op
== '+') { *v
= arg0f
+ arg1f
; } // Add (use "+-" to subtract)
1649 else if (op
== '*') { *v
= arg0f
* arg1f
; } // Multiply
1650 else if (op
== '/') { if (arg1f
!= 0.0f
) *v
= arg0f
/ arg1f
; } // Divide
1651 else { *v
= arg1f
; } // Assign constant
1653 else if (data_type
== ImGuiDataType_Double
)
1655 format
= "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
1656 double* v
= (double*)data_ptr
;
1657 double arg0f
= *v
, arg1f
= 0.0;
1658 if (op
&& sscanf(initial_value_buf
, format
, &arg0f
) < 1)
1660 if (sscanf(buf
, format
, &arg1f
) < 1)
1662 if (op
== '+') { *v
= arg0f
+ arg1f
; } // Add (use "+-" to subtract)
1663 else if (op
== '*') { *v
= arg0f
* arg1f
; } // Multiply
1664 else if (op
== '/') { if (arg1f
!= 0.0f
) *v
= arg0f
/ arg1f
; } // Divide
1665 else { *v
= arg1f
; } // Assign constant
1667 return memcmp(data_backup
, data_ptr
, GDataTypeInfo
[data_type
].Size
) != 0;
1670 static float GetMinimumStepAtDecimalPrecision(int decimal_precision
)
1672 static const float min_steps
[10] = { 1.0f
, 0.1f
, 0.01f
, 0.001f
, 0.0001f
, 0.00001f
, 0.000001f
, 0.0000001f
, 0.00000001f
, 0.000000001f
};
1673 if (decimal_precision
< 0)
1675 return (decimal_precision
< IM_ARRAYSIZE(min_steps
)) ? min_steps
[decimal_precision
] : ImPow(10.0f
, (float)-decimal_precision
);
1678 template<typename TYPE
>
1679 static const char* ImAtoi(const char* src
, TYPE
* output
)
1682 if (*src
== '-') { negative
= 1; src
++; }
1683 if (*src
== '+') { src
++; }
1685 while (*src
>= '0' && *src
<= '9')
1686 v
= (v
* 10) + (*src
++ - '0');
1687 *output
= negative
? -v
: v
;
1691 template<typename TYPE
, typename SIGNEDTYPE
>
1692 TYPE
ImGui::RoundScalarWithFormatT(const char* format
, ImGuiDataType data_type
, TYPE v
)
1694 const char* fmt_start
= ImParseFormatFindStart(format
);
1695 if (fmt_start
[0] != '%' || fmt_start
[1] == '%') // Don't apply if the value is not visible in the format string
1698 ImFormatString(v_str
, IM_ARRAYSIZE(v_str
), fmt_start
, v
);
1699 const char* p
= v_str
;
1702 if (data_type
== ImGuiDataType_Float
|| data_type
== ImGuiDataType_Double
)
1703 v
= (TYPE
)ImAtof(p
);
1705 ImAtoi(p
, (SIGNEDTYPE
*)&v
);
1709 //-------------------------------------------------------------------------
1710 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1711 //-------------------------------------------------------------------------
1712 // - DragBehaviorT<>() [Internal]
1713 // - DragBehavior() [Internal]
1720 // - DragFloatRange2()
1725 // - DragIntRange2()
1726 //-------------------------------------------------------------------------
1728 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
1729 template<typename TYPE
, typename SIGNEDTYPE
, typename FLOATTYPE
>
1730 bool ImGui::DragBehaviorT(ImGuiDataType data_type
, TYPE
* v
, float v_speed
, const TYPE v_min
, const TYPE v_max
, const char* format
, float power
, ImGuiDragFlags flags
)
1732 ImGuiContext
& g
= *GImGui
;
1733 const ImGuiAxis axis
= (flags
& ImGuiDragFlags_Vertical
) ? ImGuiAxis_Y
: ImGuiAxis_X
;
1734 const bool is_decimal
= (data_type
== ImGuiDataType_Float
) || (data_type
== ImGuiDataType_Double
);
1735 const bool has_min_max
= (v_min
!= v_max
);
1736 const bool is_power
= (power
!= 1.0f
&& is_decimal
&& has_min_max
&& (v_max
- v_min
< FLT_MAX
));
1738 // Default tweak speed
1739 if (v_speed
== 0.0f
&& has_min_max
&& (v_max
- v_min
< FLT_MAX
))
1740 v_speed
= (float)((v_max
- v_min
) * g
.DragSpeedDefaultRatio
);
1742 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
1743 float adjust_delta
= 0.0f
;
1744 if (g
.ActiveIdSource
== ImGuiInputSource_Mouse
&& IsMousePosValid() && g
.IO
.MouseDragMaxDistanceSqr
[0] > 1.0f
*1.0f
)
1746 adjust_delta
= g
.IO
.MouseDelta
[axis
];
1748 adjust_delta
*= 1.0f
/ 100.0f
;
1750 adjust_delta
*= 10.0f
;
1752 else if (g
.ActiveIdSource
== ImGuiInputSource_Nav
)
1754 int decimal_precision
= is_decimal
? ImParseFormatPrecision(format
, 3) : 0;
1755 adjust_delta
= GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard
| ImGuiNavDirSourceFlags_PadDPad
, ImGuiInputReadMode_RepeatFast
, 1.0f
/ 10.0f
, 10.0f
)[axis
];
1756 v_speed
= ImMax(v_speed
, GetMinimumStepAtDecimalPrecision(decimal_precision
));
1758 adjust_delta
*= v_speed
;
1760 // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
1761 if (axis
== ImGuiAxis_Y
)
1762 adjust_delta
= -adjust_delta
;
1764 // Clear current value on activation
1765 // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
1766 bool is_just_activated
= g
.ActiveIdIsJustActivated
;
1767 bool is_already_past_limits_and_pushing_outward
= has_min_max
&& ((*v
>= v_max
&& adjust_delta
> 0.0f
) || (*v
<= v_min
&& adjust_delta
< 0.0f
));
1768 bool is_drag_direction_change_with_power
= is_power
&& ((adjust_delta
< 0 && g
.DragCurrentAccum
> 0) || (adjust_delta
> 0 && g
.DragCurrentAccum
< 0));
1769 if (is_just_activated
|| is_already_past_limits_and_pushing_outward
|| is_drag_direction_change_with_power
)
1771 g
.DragCurrentAccum
= 0.0f
;
1772 g
.DragCurrentAccumDirty
= false;
1774 else if (adjust_delta
!= 0.0f
)
1776 g
.DragCurrentAccum
+= adjust_delta
;
1777 g
.DragCurrentAccumDirty
= true;
1780 if (!g
.DragCurrentAccumDirty
)
1784 FLOATTYPE v_old_ref_for_accum_remainder
= (FLOATTYPE
)0.0f
;
1788 // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
1789 FLOATTYPE v_old_norm_curved
= ImPow((FLOATTYPE
)(v_cur
- v_min
) / (FLOATTYPE
)(v_max
- v_min
), (FLOATTYPE
)1.0f
/ power
);
1790 FLOATTYPE v_new_norm_curved
= v_old_norm_curved
+ (g
.DragCurrentAccum
/ (v_max
- v_min
));
1791 v_cur
= v_min
+ (TYPE
)ImPow(ImSaturate((float)v_new_norm_curved
), power
) * (v_max
- v_min
);
1792 v_old_ref_for_accum_remainder
= v_old_norm_curved
;
1796 v_cur
+= (TYPE
)g
.DragCurrentAccum
;
1799 // Round to user desired precision based on format string
1800 v_cur
= RoundScalarWithFormatT
<TYPE
, SIGNEDTYPE
>(format
, data_type
, v_cur
);
1802 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1803 g
.DragCurrentAccumDirty
= false;
1806 FLOATTYPE v_cur_norm_curved
= ImPow((FLOATTYPE
)(v_cur
- v_min
) / (FLOATTYPE
)(v_max
- v_min
), (FLOATTYPE
)1.0f
/ power
);
1807 g
.DragCurrentAccum
-= (float)(v_cur_norm_curved
- v_old_ref_for_accum_remainder
);
1811 g
.DragCurrentAccum
-= (float)((SIGNEDTYPE
)v_cur
- (SIGNEDTYPE
)*v
);
1814 // Lose zero sign for float/double
1815 if (v_cur
== (TYPE
)-0)
1818 // Clamp values (+ handle overflow/wrap-around for integer types)
1819 if (*v
!= v_cur
&& has_min_max
)
1821 if (v_cur
< v_min
|| (v_cur
> *v
&& adjust_delta
< 0.0f
&& !is_decimal
))
1823 if (v_cur
> v_max
|| (v_cur
< *v
&& adjust_delta
> 0.0f
&& !is_decimal
))
1834 bool ImGui::DragBehavior(ImGuiID id
, ImGuiDataType data_type
, void* v
, float v_speed
, const void* v_min
, const void* v_max
, const char* format
, float power
, ImGuiDragFlags flags
)
1836 ImGuiContext
& g
= *GImGui
;
1837 if (g
.ActiveId
== id
)
1839 if (g
.ActiveIdSource
== ImGuiInputSource_Mouse
&& !g
.IO
.MouseDown
[0])
1841 else if (g
.ActiveIdSource
== ImGuiInputSource_Nav
&& g
.NavActivatePressedId
== id
&& !g
.ActiveIdIsJustActivated
)
1844 if (g
.ActiveId
!= id
)
1849 case ImGuiDataType_S32
: return DragBehaviorT
<ImS32
, ImS32
, float >(data_type
, (ImS32
*)v
, v_speed
, v_min
? *(const ImS32
* )v_min
: IM_S32_MIN
, v_max
? *(const ImS32
* )v_max
: IM_S32_MAX
, format
, power
, flags
);
1850 case ImGuiDataType_U32
: return DragBehaviorT
<ImU32
, ImS32
, float >(data_type
, (ImU32
*)v
, v_speed
, v_min
? *(const ImU32
* )v_min
: IM_U32_MIN
, v_max
? *(const ImU32
* )v_max
: IM_U32_MAX
, format
, power
, flags
);
1851 case ImGuiDataType_S64
: return DragBehaviorT
<ImS64
, ImS64
, double>(data_type
, (ImS64
*)v
, v_speed
, v_min
? *(const ImS64
* )v_min
: IM_S64_MIN
, v_max
? *(const ImS64
* )v_max
: IM_S64_MAX
, format
, power
, flags
);
1852 case ImGuiDataType_U64
: return DragBehaviorT
<ImU64
, ImS64
, double>(data_type
, (ImU64
*)v
, v_speed
, v_min
? *(const ImU64
* )v_min
: IM_U64_MIN
, v_max
? *(const ImU64
* )v_max
: IM_U64_MAX
, format
, power
, flags
);
1853 case ImGuiDataType_Float
: return DragBehaviorT
<float, float, float >(data_type
, (float*)v
, v_speed
, v_min
? *(const float* )v_min
: -FLT_MAX
, v_max
? *(const float* )v_max
: FLT_MAX
, format
, power
, flags
);
1854 case ImGuiDataType_Double
: return DragBehaviorT
<double,double,double>(data_type
, (double*)v
, v_speed
, v_min
? *(const double*)v_min
: -DBL_MAX
, v_max
? *(const double*)v_max
: DBL_MAX
, format
, power
, flags
);
1855 case ImGuiDataType_COUNT
: break;
1861 bool ImGui::DragScalar(const char* label
, ImGuiDataType data_type
, void* v
, float v_speed
, const void* v_min
, const void* v_max
, const char* format
, float power
)
1863 ImGuiWindow
* window
= GetCurrentWindow();
1864 if (window
->SkipItems
)
1868 IM_ASSERT(v_min
!= NULL
&& v_max
!= NULL
); // When using a power curve the drag needs to have known bounds
1870 ImGuiContext
& g
= *GImGui
;
1871 const ImGuiStyle
& style
= g
.Style
;
1872 const ImGuiID id
= window
->GetID(label
);
1873 const float w
= CalcItemWidth();
1875 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
1876 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(w
, label_size
.y
+ style
.FramePadding
.y
*2.0f
));
1877 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
));
1879 ItemSize(total_bb
, style
.FramePadding
.y
);
1880 if (!ItemAdd(total_bb
, id
, &frame_bb
))
1883 const bool hovered
= ItemHoverable(frame_bb
, id
);
1885 // Default format string when passing NULL
1886 // Patch old "%.0f" format string to use "%d", read function comments for more details.
1887 IM_ASSERT(data_type
>= 0 && data_type
< ImGuiDataType_COUNT
);
1889 format
= GDataTypeInfo
[data_type
].PrintFmt
;
1890 else if (data_type
== ImGuiDataType_S32
&& strcmp(format
, "%d") != 0)
1891 format
= PatchFormatStringFloatToInt(format
);
1893 // Tabbing or CTRL-clicking on Drag turns it into an input box
1894 bool start_text_input
= false;
1895 const bool tab_focus_requested
= FocusableItemRegister(window
, id
);
1896 if (tab_focus_requested
|| (hovered
&& (g
.IO
.MouseClicked
[0] || g
.IO
.MouseDoubleClicked
[0])) || g
.NavActivateId
== id
|| (g
.NavInputId
== id
&& g
.ScalarAsInputTextId
!= id
))
1898 SetActiveID(id
, window
);
1899 SetFocusID(id
, window
);
1900 FocusWindow(window
);
1901 g
.ActiveIdAllowNavDirFlags
= (1 << ImGuiDir_Up
) | (1 << ImGuiDir_Down
);
1902 if (tab_focus_requested
|| g
.IO
.KeyCtrl
|| g
.IO
.MouseDoubleClicked
[0] || g
.NavInputId
== id
)
1904 start_text_input
= true;
1905 g
.ScalarAsInputTextId
= 0;
1908 if (start_text_input
|| (g
.ActiveId
== id
&& g
.ScalarAsInputTextId
== id
))
1910 window
->DC
.CursorPos
= frame_bb
.Min
;
1911 FocusableItemUnregister(window
);
1912 return InputScalarAsWidgetReplacement(frame_bb
, id
, label
, data_type
, v
, format
);
1915 // Actual drag behavior
1916 const bool value_changed
= DragBehavior(id
, data_type
, v
, v_speed
, v_min
, v_max
, format
, power
, ImGuiDragFlags_None
);
1921 const ImU32 frame_col
= GetColorU32(g
.ActiveId
== id
? ImGuiCol_FrameBgActive
: g
.HoveredId
== id
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
);
1922 RenderNavHighlight(frame_bb
, id
);
1923 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, frame_col
, true, style
.FrameRounding
);
1925 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1927 const char* value_buf_end
= value_buf
+ DataTypeFormatString(value_buf
, IM_ARRAYSIZE(value_buf
), data_type
, v
, format
);
1928 RenderTextClipped(frame_bb
.Min
, frame_bb
.Max
, value_buf
, value_buf_end
, NULL
, ImVec2(0.5f
, 0.5f
));
1930 if (label_size
.x
> 0.0f
)
1931 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
1933 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
);
1934 return value_changed
;
1937 bool ImGui::DragScalarN(const char* label
, ImGuiDataType data_type
, void* v
, int components
, float v_speed
, const void* v_min
, const void* v_max
, const char* format
, float power
)
1939 ImGuiWindow
* window
= GetCurrentWindow();
1940 if (window
->SkipItems
)
1943 ImGuiContext
& g
= *GImGui
;
1944 bool value_changed
= false;
1947 PushMultiItemsWidths(components
);
1948 size_t type_size
= GDataTypeInfo
[data_type
].Size
;
1949 for (int i
= 0; i
< components
; i
++)
1952 value_changed
|= DragScalar("", data_type
, v
, v_speed
, v_min
, v_max
, format
, power
);
1953 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
1956 v
= (void*)((char*)v
+ type_size
);
1960 TextUnformatted(label
, FindRenderedTextEnd(label
));
1962 return value_changed
;
1965 bool ImGui::DragFloat(const char* label
, float* v
, float v_speed
, float v_min
, float v_max
, const char* format
, float power
)
1967 return DragScalar(label
, ImGuiDataType_Float
, v
, v_speed
, &v_min
, &v_max
, format
, power
);
1970 bool ImGui::DragFloat2(const char* label
, float v
[2], float v_speed
, float v_min
, float v_max
, const char* format
, float power
)
1972 return DragScalarN(label
, ImGuiDataType_Float
, v
, 2, v_speed
, &v_min
, &v_max
, format
, power
);
1975 bool ImGui::DragFloat3(const char* label
, float v
[3], float v_speed
, float v_min
, float v_max
, const char* format
, float power
)
1977 return DragScalarN(label
, ImGuiDataType_Float
, v
, 3, v_speed
, &v_min
, &v_max
, format
, power
);
1980 bool ImGui::DragFloat4(const char* label
, float v
[4], float v_speed
, float v_min
, float v_max
, const char* format
, float power
)
1982 return DragScalarN(label
, ImGuiDataType_Float
, v
, 4, v_speed
, &v_min
, &v_max
, format
, power
);
1985 bool ImGui::DragFloatRange2(const char* label
, float* v_current_min
, float* v_current_max
, float v_speed
, float v_min
, float v_max
, const char* format
, const char* format_max
, float power
)
1987 ImGuiWindow
* window
= GetCurrentWindow();
1988 if (window
->SkipItems
)
1991 ImGuiContext
& g
= *GImGui
;
1994 PushMultiItemsWidths(2);
1996 bool value_changed
= DragFloat("##min", v_current_min
, v_speed
, (v_min
>= v_max
) ? -FLT_MAX
: v_min
, (v_min
>= v_max
) ? *v_current_max
: ImMin(v_max
, *v_current_max
), format
, power
);
1998 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
1999 value_changed
|= DragFloat("##max", v_current_max
, v_speed
, (v_min
>= v_max
) ? *v_current_min
: ImMax(v_min
, *v_current_min
), (v_min
>= v_max
) ? FLT_MAX
: v_max
, format_max
? format_max
: format
, power
);
2001 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
2003 TextUnformatted(label
, FindRenderedTextEnd(label
));
2006 return value_changed
;
2009 // NB: v_speed is float to allow adjusting the drag speed with more precision
2010 bool ImGui::DragInt(const char* label
, int* v
, float v_speed
, int v_min
, int v_max
, const char* format
)
2012 return DragScalar(label
, ImGuiDataType_S32
, v
, v_speed
, &v_min
, &v_max
, format
);
2015 bool ImGui::DragInt2(const char* label
, int v
[2], float v_speed
, int v_min
, int v_max
, const char* format
)
2017 return DragScalarN(label
, ImGuiDataType_S32
, v
, 2, v_speed
, &v_min
, &v_max
, format
);
2020 bool ImGui::DragInt3(const char* label
, int v
[3], float v_speed
, int v_min
, int v_max
, const char* format
)
2022 return DragScalarN(label
, ImGuiDataType_S32
, v
, 3, v_speed
, &v_min
, &v_max
, format
);
2025 bool ImGui::DragInt4(const char* label
, int v
[4], float v_speed
, int v_min
, int v_max
, const char* format
)
2027 return DragScalarN(label
, ImGuiDataType_S32
, v
, 4, v_speed
, &v_min
, &v_max
, format
);
2030 bool ImGui::DragIntRange2(const char* label
, int* v_current_min
, int* v_current_max
, float v_speed
, int v_min
, int v_max
, const char* format
, const char* format_max
)
2032 ImGuiWindow
* window
= GetCurrentWindow();
2033 if (window
->SkipItems
)
2036 ImGuiContext
& g
= *GImGui
;
2039 PushMultiItemsWidths(2);
2041 bool value_changed
= DragInt("##min", v_current_min
, v_speed
, (v_min
>= v_max
) ? INT_MIN
: v_min
, (v_min
>= v_max
) ? *v_current_max
: ImMin(v_max
, *v_current_max
), format
);
2043 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
2044 value_changed
|= DragInt("##max", v_current_max
, v_speed
, (v_min
>= v_max
) ? *v_current_min
: ImMax(v_min
, *v_current_min
), (v_min
>= v_max
) ? INT_MAX
: v_max
, format_max
? format_max
: format
);
2046 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
2048 TextUnformatted(label
, FindRenderedTextEnd(label
));
2052 return value_changed
;
2055 //-------------------------------------------------------------------------
2056 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2057 //-------------------------------------------------------------------------
2058 // - SliderBehaviorT<>() [Internal]
2059 // - SliderBehavior() [Internal]
2061 // - SliderScalarN()
2071 // - VSliderScalar()
2074 //-------------------------------------------------------------------------
2076 template<typename TYPE
, typename FLOATTYPE
>
2077 float ImGui::SliderCalcRatioFromValueT(ImGuiDataType data_type
, TYPE v
, TYPE v_min
, TYPE v_max
, float power
, float linear_zero_pos
)
2082 const bool is_power
= (power
!= 1.0f
) && (data_type
== ImGuiDataType_Float
|| data_type
== ImGuiDataType_Double
);
2083 const TYPE v_clamped
= (v_min
< v_max
) ? ImClamp(v
, v_min
, v_max
) : ImClamp(v
, v_max
, v_min
);
2086 if (v_clamped
< 0.0f
)
2088 const float f
= 1.0f
- (float)((v_clamped
- v_min
) / (ImMin((TYPE
)0, v_max
) - v_min
));
2089 return (1.0f
- ImPow(f
, 1.0f
/power
)) * linear_zero_pos
;
2093 const float f
= (float)((v_clamped
- ImMax((TYPE
)0, v_min
)) / (v_max
- ImMax((TYPE
)0, v_min
)));
2094 return linear_zero_pos
+ ImPow(f
, 1.0f
/power
) * (1.0f
- linear_zero_pos
);
2099 return (float)((FLOATTYPE
)(v_clamped
- v_min
) / (FLOATTYPE
)(v_max
- v_min
));
2102 // FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
2103 template<typename TYPE
, typename SIGNEDTYPE
, typename FLOATTYPE
>
2104 bool ImGui::SliderBehaviorT(const ImRect
& bb
, ImGuiID id
, ImGuiDataType data_type
, TYPE
* v
, const TYPE v_min
, const TYPE v_max
, const char* format
, float power
, ImGuiSliderFlags flags
, ImRect
* out_grab_bb
)
2106 ImGuiContext
& g
= *GImGui
;
2107 const ImGuiStyle
& style
= g
.Style
;
2109 const ImGuiAxis axis
= (flags
& ImGuiSliderFlags_Vertical
) ? ImGuiAxis_Y
: ImGuiAxis_X
;
2110 const bool is_decimal
= (data_type
== ImGuiDataType_Float
) || (data_type
== ImGuiDataType_Double
);
2111 const bool is_power
= (power
!= 1.0f
) && is_decimal
;
2113 const float grab_padding
= 2.0f
;
2114 const float slider_sz
= (bb
.Max
[axis
] - bb
.Min
[axis
]) - grab_padding
* 2.0f
;
2115 float grab_sz
= style
.GrabMinSize
;
2116 SIGNEDTYPE v_range
= (v_min
< v_max
? v_max
- v_min
: v_min
- v_max
);
2117 if (!is_decimal
&& v_range
>= 0) // v_range < 0 may happen on integer overflows
2118 grab_sz
= ImMax((float)(slider_sz
/ (v_range
+ 1)), style
.GrabMinSize
); // For integer sliders: if possible have the grab size represent 1 unit
2119 grab_sz
= ImMin(grab_sz
, slider_sz
);
2120 const float slider_usable_sz
= slider_sz
- grab_sz
;
2121 const float slider_usable_pos_min
= bb
.Min
[axis
] + grab_padding
+ grab_sz
*0.5f
;
2122 const float slider_usable_pos_max
= bb
.Max
[axis
] - grab_padding
- grab_sz
*0.5f
;
2124 // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
2125 float linear_zero_pos
; // 0.0->1.0f
2126 if (is_power
&& v_min
* v_max
< 0.0f
)
2129 const FLOATTYPE linear_dist_min_to_0
= ImPow(v_min
>= 0 ? (FLOATTYPE
)v_min
: -(FLOATTYPE
)v_min
, (FLOATTYPE
)1.0f
/power
);
2130 const FLOATTYPE linear_dist_max_to_0
= ImPow(v_max
>= 0 ? (FLOATTYPE
)v_max
: -(FLOATTYPE
)v_max
, (FLOATTYPE
)1.0f
/power
);
2131 linear_zero_pos
= (float)(linear_dist_min_to_0
/ (linear_dist_min_to_0
+ linear_dist_max_to_0
));
2136 linear_zero_pos
= v_min
< 0.0f
? 1.0f
: 0.0f
;
2139 // Process interacting with the slider
2140 bool value_changed
= false;
2141 if (g
.ActiveId
== id
)
2143 bool set_new_value
= false;
2144 float clicked_t
= 0.0f
;
2145 if (g
.ActiveIdSource
== ImGuiInputSource_Mouse
)
2147 if (!g
.IO
.MouseDown
[0])
2153 const float mouse_abs_pos
= g
.IO
.MousePos
[axis
];
2154 clicked_t
= (slider_usable_sz
> 0.0f
) ? ImClamp((mouse_abs_pos
- slider_usable_pos_min
) / slider_usable_sz
, 0.0f
, 1.0f
) : 0.0f
;
2155 if (axis
== ImGuiAxis_Y
)
2156 clicked_t
= 1.0f
- clicked_t
;
2157 set_new_value
= true;
2160 else if (g
.ActiveIdSource
== ImGuiInputSource_Nav
)
2162 const ImVec2 delta2
= GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard
| ImGuiNavDirSourceFlags_PadDPad
, ImGuiInputReadMode_RepeatFast
, 0.0f
, 0.0f
);
2163 float delta
= (axis
== ImGuiAxis_X
) ? delta2
.x
: -delta2
.y
;
2164 if (g
.NavActivatePressedId
== id
&& !g
.ActiveIdIsJustActivated
)
2168 else if (delta
!= 0.0f
)
2170 clicked_t
= SliderCalcRatioFromValueT
<TYPE
,FLOATTYPE
>(data_type
, *v
, v_min
, v_max
, power
, linear_zero_pos
);
2171 const int decimal_precision
= is_decimal
? ImParseFormatPrecision(format
, 3) : 0;
2172 if ((decimal_precision
> 0) || is_power
)
2174 delta
/= 100.0f
; // Gamepad/keyboard tweak speeds in % of slider bounds
2175 if (IsNavInputDown(ImGuiNavInput_TweakSlow
))
2180 if ((v_range
>= -100.0f
&& v_range
<= 100.0f
) || IsNavInputDown(ImGuiNavInput_TweakSlow
))
2181 delta
= ((delta
< 0.0f
) ? -1.0f
: +1.0f
) / (float)v_range
; // Gamepad/keyboard tweak speeds in integer steps
2185 if (IsNavInputDown(ImGuiNavInput_TweakFast
))
2187 set_new_value
= true;
2188 if ((clicked_t
>= 1.0f
&& delta
> 0.0f
) || (clicked_t
<= 0.0f
&& delta
< 0.0f
)) // This is to avoid applying the saturation when already past the limits
2189 set_new_value
= false;
2191 clicked_t
= ImSaturate(clicked_t
+ delta
);
2200 // Account for power curve scale on both sides of the zero
2201 if (clicked_t
< linear_zero_pos
)
2203 // Negative: rescale to the negative range before powering
2204 float a
= 1.0f
- (clicked_t
/ linear_zero_pos
);
2205 a
= ImPow(a
, power
);
2206 v_new
= ImLerp(ImMin(v_max
, (TYPE
)0), v_min
, a
);
2210 // Positive: rescale to the positive range before powering
2212 if (ImFabs(linear_zero_pos
- 1.0f
) > 1.e
-6f
)
2213 a
= (clicked_t
- linear_zero_pos
) / (1.0f
- linear_zero_pos
);
2216 a
= ImPow(a
, power
);
2217 v_new
= ImLerp(ImMax(v_min
, (TYPE
)0), v_max
, a
);
2225 v_new
= ImLerp(v_min
, v_max
, clicked_t
);
2229 // For integer values we want the clicking position to match the grab box so we round above
2230 // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2231 FLOATTYPE v_new_off_f
= (v_max
- v_min
) * clicked_t
;
2232 TYPE v_new_off_floor
= (TYPE
)(v_new_off_f
);
2233 TYPE v_new_off_round
= (TYPE
)(v_new_off_f
+ (FLOATTYPE
)0.5);
2234 if (!is_decimal
&& v_new_off_floor
< v_new_off_round
)
2235 v_new
= v_min
+ v_new_off_round
;
2237 v_new
= v_min
+ v_new_off_floor
;
2241 // Round to user desired precision based on format string
2242 v_new
= RoundScalarWithFormatT
<TYPE
,SIGNEDTYPE
>(format
, data_type
, v_new
);
2248 value_changed
= true;
2253 // Output grab position so it can be displayed by the caller
2254 float grab_t
= SliderCalcRatioFromValueT
<TYPE
,FLOATTYPE
>(data_type
, *v
, v_min
, v_max
, power
, linear_zero_pos
);
2255 if (axis
== ImGuiAxis_Y
)
2256 grab_t
= 1.0f
- grab_t
;
2257 const float grab_pos
= ImLerp(slider_usable_pos_min
, slider_usable_pos_max
, grab_t
);
2258 if (axis
== ImGuiAxis_X
)
2259 *out_grab_bb
= ImRect(grab_pos
- grab_sz
*0.5f
, bb
.Min
.y
+ grab_padding
, grab_pos
+ grab_sz
*0.5f
, bb
.Max
.y
- grab_padding
);
2261 *out_grab_bb
= ImRect(bb
.Min
.x
+ grab_padding
, grab_pos
- grab_sz
*0.5f
, bb
.Max
.x
- grab_padding
, grab_pos
+ grab_sz
*0.5f
);
2263 return value_changed
;
2266 // For 32-bits and larger types, slider bounds are limited to half the natural type range.
2267 // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
2268 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
2269 bool ImGui::SliderBehavior(const ImRect
& bb
, ImGuiID id
, ImGuiDataType data_type
, void* v
, const void* v_min
, const void* v_max
, const char* format
, float power
, ImGuiSliderFlags flags
, ImRect
* out_grab_bb
)
2273 case ImGuiDataType_S32
:
2274 IM_ASSERT(*(const ImS32
*)v_min
>= IM_S32_MIN
/2 && *(const ImS32
*)v_max
<= IM_S32_MAX
/2);
2275 return SliderBehaviorT
<ImS32
, ImS32
, float >(bb
, id
, data_type
, (ImS32
*)v
, *(const ImS32
*)v_min
, *(const ImS32
*)v_max
, format
, power
, flags
, out_grab_bb
);
2276 case ImGuiDataType_U32
:
2277 IM_ASSERT(*(const ImU32
*)v_min
<= IM_U32_MAX
/2);
2278 return SliderBehaviorT
<ImU32
, ImS32
, float >(bb
, id
, data_type
, (ImU32
*)v
, *(const ImU32
*)v_min
, *(const ImU32
*)v_max
, format
, power
, flags
, out_grab_bb
);
2279 case ImGuiDataType_S64
:
2280 IM_ASSERT(*(const ImS64
*)v_min
>= IM_S64_MIN
/2 && *(const ImS64
*)v_max
<= IM_S64_MAX
/2);
2281 return SliderBehaviorT
<ImS64
, ImS64
, double>(bb
, id
, data_type
, (ImS64
*)v
, *(const ImS64
*)v_min
, *(const ImS64
*)v_max
, format
, power
, flags
, out_grab_bb
);
2282 case ImGuiDataType_U64
:
2283 IM_ASSERT(*(const ImU64
*)v_min
<= IM_U64_MAX
/2);
2284 return SliderBehaviorT
<ImU64
, ImS64
, double>(bb
, id
, data_type
, (ImU64
*)v
, *(const ImU64
*)v_min
, *(const ImU64
*)v_max
, format
, power
, flags
, out_grab_bb
);
2285 case ImGuiDataType_Float
:
2286 IM_ASSERT(*(const float*)v_min
>= -FLT_MAX
/2.0f
&& *(const float*)v_max
<= FLT_MAX
/2.0f
);
2287 return SliderBehaviorT
<float, float, float >(bb
, id
, data_type
, (float*)v
, *(const float*)v_min
, *(const float*)v_max
, format
, power
, flags
, out_grab_bb
);
2288 case ImGuiDataType_Double
:
2289 IM_ASSERT(*(const double*)v_min
>= -DBL_MAX
/2.0f
&& *(const double*)v_max
<= DBL_MAX
/2.0f
);
2290 return SliderBehaviorT
<double,double,double>(bb
, id
, data_type
, (double*)v
, *(const double*)v_min
, *(const double*)v_max
, format
, power
, flags
, out_grab_bb
);
2291 case ImGuiDataType_COUNT
: break;
2297 bool ImGui::SliderScalar(const char* label
, ImGuiDataType data_type
, void* v
, const void* v_min
, const void* v_max
, const char* format
, float power
)
2299 ImGuiWindow
* window
= GetCurrentWindow();
2300 if (window
->SkipItems
)
2303 ImGuiContext
& g
= *GImGui
;
2304 const ImGuiStyle
& style
= g
.Style
;
2305 const ImGuiID id
= window
->GetID(label
);
2306 const float w
= CalcItemWidth();
2308 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
2309 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(w
, label_size
.y
+ style
.FramePadding
.y
*2.0f
));
2310 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
));
2312 ItemSize(total_bb
, style
.FramePadding
.y
);
2313 if (!ItemAdd(total_bb
, id
, &frame_bb
))
2316 // Default format string when passing NULL
2317 // Patch old "%.0f" format string to use "%d", read function comments for more details.
2318 IM_ASSERT(data_type
>= 0 && data_type
< ImGuiDataType_COUNT
);
2320 format
= GDataTypeInfo
[data_type
].PrintFmt
;
2321 else if (data_type
== ImGuiDataType_S32
&& strcmp(format
, "%d") != 0)
2322 format
= PatchFormatStringFloatToInt(format
);
2324 // Tabbing or CTRL-clicking on Slider turns it into an input box
2325 bool start_text_input
= false;
2326 const bool tab_focus_requested
= FocusableItemRegister(window
, id
);
2327 const bool hovered
= ItemHoverable(frame_bb
, id
);
2328 if (tab_focus_requested
|| (hovered
&& g
.IO
.MouseClicked
[0]) || g
.NavActivateId
== id
|| (g
.NavInputId
== id
&& g
.ScalarAsInputTextId
!= id
))
2330 SetActiveID(id
, window
);
2331 SetFocusID(id
, window
);
2332 FocusWindow(window
);
2333 g
.ActiveIdAllowNavDirFlags
= (1 << ImGuiDir_Up
) | (1 << ImGuiDir_Down
);
2334 if (tab_focus_requested
|| g
.IO
.KeyCtrl
|| g
.NavInputId
== id
)
2336 start_text_input
= true;
2337 g
.ScalarAsInputTextId
= 0;
2340 if (start_text_input
|| (g
.ActiveId
== id
&& g
.ScalarAsInputTextId
== id
))
2342 window
->DC
.CursorPos
= frame_bb
.Min
;
2343 FocusableItemUnregister(window
);
2344 return InputScalarAsWidgetReplacement(frame_bb
, id
, label
, data_type
, v
, format
);
2348 const ImU32 frame_col
= GetColorU32(g
.ActiveId
== id
? ImGuiCol_FrameBgActive
: g
.HoveredId
== id
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
);
2349 RenderNavHighlight(frame_bb
, id
);
2350 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, frame_col
, true, g
.Style
.FrameRounding
);
2354 const bool value_changed
= SliderBehavior(frame_bb
, id
, data_type
, v
, v_min
, v_max
, format
, power
, ImGuiSliderFlags_None
, &grab_bb
);
2359 window
->DrawList
->AddRectFilled(grab_bb
.Min
, grab_bb
.Max
, GetColorU32(g
.ActiveId
== id
? ImGuiCol_SliderGrabActive
: ImGuiCol_SliderGrab
), style
.GrabRounding
);
2361 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2363 const char* value_buf_end
= value_buf
+ DataTypeFormatString(value_buf
, IM_ARRAYSIZE(value_buf
), data_type
, v
, format
);
2364 RenderTextClipped(frame_bb
.Min
, frame_bb
.Max
, value_buf
, value_buf_end
, NULL
, ImVec2(0.5f
,0.5f
));
2366 if (label_size
.x
> 0.0f
)
2367 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
2369 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
);
2370 return value_changed
;
2373 // Add multiple sliders on 1 line for compact edition of multiple components
2374 bool ImGui::SliderScalarN(const char* label
, ImGuiDataType data_type
, void* v
, int components
, const void* v_min
, const void* v_max
, const char* format
, float power
)
2376 ImGuiWindow
* window
= GetCurrentWindow();
2377 if (window
->SkipItems
)
2380 ImGuiContext
& g
= *GImGui
;
2381 bool value_changed
= false;
2384 PushMultiItemsWidths(components
);
2385 size_t type_size
= GDataTypeInfo
[data_type
].Size
;
2386 for (int i
= 0; i
< components
; i
++)
2389 value_changed
|= SliderScalar("", data_type
, v
, v_min
, v_max
, format
, power
);
2390 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
2393 v
= (void*)((char*)v
+ type_size
);
2397 TextUnformatted(label
, FindRenderedTextEnd(label
));
2399 return value_changed
;
2402 bool ImGui::SliderFloat(const char* label
, float* v
, float v_min
, float v_max
, const char* format
, float power
)
2404 return SliderScalar(label
, ImGuiDataType_Float
, v
, &v_min
, &v_max
, format
, power
);
2407 bool ImGui::SliderFloat2(const char* label
, float v
[2], float v_min
, float v_max
, const char* format
, float power
)
2409 return SliderScalarN(label
, ImGuiDataType_Float
, v
, 2, &v_min
, &v_max
, format
, power
);
2412 bool ImGui::SliderFloat3(const char* label
, float v
[3], float v_min
, float v_max
, const char* format
, float power
)
2414 return SliderScalarN(label
, ImGuiDataType_Float
, v
, 3, &v_min
, &v_max
, format
, power
);
2417 bool ImGui::SliderFloat4(const char* label
, float v
[4], float v_min
, float v_max
, const char* format
, float power
)
2419 return SliderScalarN(label
, ImGuiDataType_Float
, v
, 4, &v_min
, &v_max
, format
, power
);
2422 bool ImGui::SliderAngle(const char* label
, float* v_rad
, float v_degrees_min
, float v_degrees_max
, const char* format
)
2425 format
= "%.0f deg";
2426 float v_deg
= (*v_rad
) * 360.0f
/ (2*IM_PI
);
2427 bool value_changed
= SliderFloat(label
, &v_deg
, v_degrees_min
, v_degrees_max
, format
, 1.0f
);
2428 *v_rad
= v_deg
* (2*IM_PI
) / 360.0f
;
2429 return value_changed
;
2432 bool ImGui::SliderInt(const char* label
, int* v
, int v_min
, int v_max
, const char* format
)
2434 return SliderScalar(label
, ImGuiDataType_S32
, v
, &v_min
, &v_max
, format
);
2437 bool ImGui::SliderInt2(const char* label
, int v
[2], int v_min
, int v_max
, const char* format
)
2439 return SliderScalarN(label
, ImGuiDataType_S32
, v
, 2, &v_min
, &v_max
, format
);
2442 bool ImGui::SliderInt3(const char* label
, int v
[3], int v_min
, int v_max
, const char* format
)
2444 return SliderScalarN(label
, ImGuiDataType_S32
, v
, 3, &v_min
, &v_max
, format
);
2447 bool ImGui::SliderInt4(const char* label
, int v
[4], int v_min
, int v_max
, const char* format
)
2449 return SliderScalarN(label
, ImGuiDataType_S32
, v
, 4, &v_min
, &v_max
, format
);
2452 bool ImGui::VSliderScalar(const char* label
, const ImVec2
& size
, ImGuiDataType data_type
, void* v
, const void* v_min
, const void* v_max
, const char* format
, float power
)
2454 ImGuiWindow
* window
= GetCurrentWindow();
2455 if (window
->SkipItems
)
2458 ImGuiContext
& g
= *GImGui
;
2459 const ImGuiStyle
& style
= g
.Style
;
2460 const ImGuiID id
= window
->GetID(label
);
2462 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
2463 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
2464 const ImRect
bb(frame_bb
.Min
, frame_bb
.Max
+ ImVec2(label_size
.x
> 0.0f
? style
.ItemInnerSpacing
.x
+ label_size
.x
: 0.0f
, 0.0f
));
2466 ItemSize(bb
, style
.FramePadding
.y
);
2467 if (!ItemAdd(frame_bb
, id
))
2470 // Default format string when passing NULL
2471 // Patch old "%.0f" format string to use "%d", read function comments for more details.
2472 IM_ASSERT(data_type
>= 0 && data_type
< ImGuiDataType_COUNT
);
2474 format
= GDataTypeInfo
[data_type
].PrintFmt
;
2475 else if (data_type
== ImGuiDataType_S32
&& strcmp(format
, "%d") != 0)
2476 format
= PatchFormatStringFloatToInt(format
);
2478 const bool hovered
= ItemHoverable(frame_bb
, id
);
2479 if ((hovered
&& g
.IO
.MouseClicked
[0]) || g
.NavActivateId
== id
|| g
.NavInputId
== id
)
2481 SetActiveID(id
, window
);
2482 SetFocusID(id
, window
);
2483 FocusWindow(window
);
2484 g
.ActiveIdAllowNavDirFlags
= (1 << ImGuiDir_Left
) | (1 << ImGuiDir_Right
);
2488 const ImU32 frame_col
= GetColorU32(g
.ActiveId
== id
? ImGuiCol_FrameBgActive
: g
.HoveredId
== id
? ImGuiCol_FrameBgHovered
: ImGuiCol_FrameBg
);
2489 RenderNavHighlight(frame_bb
, id
);
2490 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, frame_col
, true, g
.Style
.FrameRounding
);
2494 const bool value_changed
= SliderBehavior(frame_bb
, id
, data_type
, v
, v_min
, v_max
, format
, power
, ImGuiSliderFlags_Vertical
, &grab_bb
);
2499 window
->DrawList
->AddRectFilled(grab_bb
.Min
, grab_bb
.Max
, GetColorU32(g
.ActiveId
== id
? ImGuiCol_SliderGrabActive
: ImGuiCol_SliderGrab
), style
.GrabRounding
);
2501 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2502 // For the vertical slider we allow centered text to overlap the frame padding
2504 const char* value_buf_end
= value_buf
+ DataTypeFormatString(value_buf
, IM_ARRAYSIZE(value_buf
), data_type
, v
, format
);
2505 RenderTextClipped(ImVec2(frame_bb
.Min
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), frame_bb
.Max
, value_buf
, value_buf_end
, NULL
, ImVec2(0.5f
,0.0f
));
2506 if (label_size
.x
> 0.0f
)
2507 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
2509 return value_changed
;
2512 bool ImGui::VSliderFloat(const char* label
, const ImVec2
& size
, float* v
, float v_min
, float v_max
, const char* format
, float power
)
2514 return VSliderScalar(label
, size
, ImGuiDataType_Float
, v
, &v_min
, &v_max
, format
, power
);
2517 bool ImGui::VSliderInt(const char* label
, const ImVec2
& size
, int* v
, int v_min
, int v_max
, const char* format
)
2519 return VSliderScalar(label
, size
, ImGuiDataType_S32
, v
, &v_min
, &v_max
, format
);
2522 //-------------------------------------------------------------------------
2523 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
2524 //-------------------------------------------------------------------------
2525 // - ImParseFormatFindStart() [Internal]
2526 // - ImParseFormatFindEnd() [Internal]
2527 // - ImParseFormatTrimDecorations() [Internal]
2528 // - ImParseFormatPrecision() [Internal]
2529 // - InputScalarAsWidgetReplacement() [Internal]
2541 //-------------------------------------------------------------------------
2543 // We don't use strchr() because our strings are usually very short and often start with '%'
2544 const char* ImParseFormatFindStart(const char* fmt
)
2546 while (char c
= fmt
[0])
2548 if (c
== '%' && fmt
[1] != '%')
2557 const char* ImParseFormatFindEnd(const char* fmt
)
2559 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
2562 const unsigned int ignored_uppercase_mask
= (1 << ('I'-'A')) | (1 << ('L'-'A'));
2563 const unsigned int ignored_lowercase_mask
= (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
2564 for (char c
; (c
= *fmt
) != 0; fmt
++)
2566 if (c
>= 'A' && c
<= 'Z' && ((1 << (c
- 'A')) & ignored_uppercase_mask
) == 0)
2568 if (c
>= 'a' && c
<= 'z' && ((1 << (c
- 'a')) & ignored_lowercase_mask
) == 0)
2574 // Extract the format out of a format string with leading or trailing decorations
2575 // fmt = "blah blah" -> return fmt
2576 // fmt = "%.3f" -> return fmt
2577 // fmt = "hello %.3f" -> return fmt + 6
2578 // fmt = "%.3f hello" -> return buf written with "%.3f"
2579 const char* ImParseFormatTrimDecorations(const char* fmt
, char* buf
, size_t buf_size
)
2581 const char* fmt_start
= ImParseFormatFindStart(fmt
);
2582 if (fmt_start
[0] != '%')
2584 const char* fmt_end
= ImParseFormatFindEnd(fmt_start
);
2585 if (fmt_end
[0] == 0) // If we only have leading decoration, we don't need to copy the data.
2587 ImStrncpy(buf
, fmt_start
, ImMin((size_t)(fmt_end
- fmt_start
) + 1, buf_size
));
2591 // Parse display precision back from the display format string
2592 // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
2593 int ImParseFormatPrecision(const char* fmt
, int default_precision
)
2595 fmt
= ImParseFormatFindStart(fmt
);
2597 return default_precision
;
2599 while (*fmt
>= '0' && *fmt
<= '9')
2601 int precision
= INT_MAX
;
2604 fmt
= ImAtoi
<int>(fmt
+ 1, &precision
);
2605 if (precision
< 0 || precision
> 99)
2606 precision
= default_precision
;
2608 if (*fmt
== 'e' || *fmt
== 'E') // Maximum precision with scientific notation
2610 if ((*fmt
== 'g' || *fmt
== 'G') && precision
== INT_MAX
)
2612 return (precision
== INT_MAX
) ? default_precision
: precision
;
2615 // Create text input in place of an active drag/slider (used when doing a CTRL+Click on drag/slider widgets)
2616 // FIXME: Facilitate using this in variety of other situations.
2617 bool ImGui::InputScalarAsWidgetReplacement(const ImRect
& bb
, ImGuiID id
, const char* label
, ImGuiDataType data_type
, void* data_ptr
, const char* format
)
2619 ImGuiContext
& g
= *GImGui
;
2621 // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id.
2622 // We clear ActiveID on the first frame to allow the InputText() taking it back.
2623 if (g
.ScalarAsInputTextId
== 0)
2628 format
= ImParseFormatTrimDecorations(format
, fmt_buf
, IM_ARRAYSIZE(fmt_buf
));
2629 DataTypeFormatString(data_buf
, IM_ARRAYSIZE(data_buf
), data_type
, data_ptr
, format
);
2630 ImStrTrimBlanks(data_buf
);
2631 ImGuiInputTextFlags flags
= ImGuiInputTextFlags_AutoSelectAll
| ((data_type
== ImGuiDataType_Float
|| data_type
== ImGuiDataType_Double
) ? ImGuiInputTextFlags_CharsScientific
: ImGuiInputTextFlags_CharsDecimal
);
2632 bool value_changed
= InputTextEx(label
, data_buf
, IM_ARRAYSIZE(data_buf
), bb
.GetSize(), flags
);
2633 if (g
.ScalarAsInputTextId
== 0)
2635 // First frame we started displaying the InputText widget, we expect it to take the active id.
2636 IM_ASSERT(g
.ActiveId
== id
);
2637 g
.ScalarAsInputTextId
= g
.ActiveId
;
2640 return DataTypeApplyOpFromText(data_buf
, g
.InputTextState
.InitialText
.Data
, data_type
, data_ptr
, NULL
);
2644 bool ImGui::InputScalar(const char* label
, ImGuiDataType data_type
, void* data_ptr
, const void* step
, const void* step_fast
, const char* format
, ImGuiInputTextFlags flags
)
2646 ImGuiWindow
* window
= GetCurrentWindow();
2647 if (window
->SkipItems
)
2650 ImGuiContext
& g
= *GImGui
;
2651 const ImGuiStyle
& style
= g
.Style
;
2653 IM_ASSERT(data_type
>= 0 && data_type
< ImGuiDataType_COUNT
);
2655 format
= GDataTypeInfo
[data_type
].PrintFmt
;
2658 DataTypeFormatString(buf
, IM_ARRAYSIZE(buf
), data_type
, data_ptr
, format
);
2660 bool value_changed
= false;
2661 if ((flags
& (ImGuiInputTextFlags_CharsHexadecimal
| ImGuiInputTextFlags_CharsScientific
)) == 0)
2662 flags
|= ImGuiInputTextFlags_CharsDecimal
;
2663 flags
|= ImGuiInputTextFlags_AutoSelectAll
;
2667 const float button_size
= GetFrameHeight();
2669 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
2671 PushItemWidth(ImMax(1.0f
, CalcItemWidth() - (button_size
+ style
.ItemInnerSpacing
.x
) * 2));
2672 if (InputText("", buf
, IM_ARRAYSIZE(buf
), flags
)) // PushId(label) + "" gives us the expected ID from outside point of view
2673 value_changed
= DataTypeApplyOpFromText(buf
, g
.InputTextState
.InitialText
.Data
, data_type
, data_ptr
, format
);
2677 ImGuiButtonFlags button_flags
= ImGuiButtonFlags_Repeat
| ImGuiButtonFlags_DontClosePopups
;
2678 if (flags
& ImGuiInputTextFlags_ReadOnly
)
2679 button_flags
|= ImGuiButtonFlags_Disabled
;
2680 SameLine(0, style
.ItemInnerSpacing
.x
);
2681 if (ButtonEx("-", ImVec2(button_size
, button_size
), button_flags
))
2683 DataTypeApplyOp(data_type
, '-', data_ptr
, data_ptr
, g
.IO
.KeyCtrl
&& step_fast
? step_fast
: step
);
2684 value_changed
= true;
2686 SameLine(0, style
.ItemInnerSpacing
.x
);
2687 if (ButtonEx("+", ImVec2(button_size
, button_size
), button_flags
))
2689 DataTypeApplyOp(data_type
, '+', data_ptr
, data_ptr
, g
.IO
.KeyCtrl
&& step_fast
? step_fast
: step
);
2690 value_changed
= true;
2692 SameLine(0, style
.ItemInnerSpacing
.x
);
2693 TextUnformatted(label
, FindRenderedTextEnd(label
));
2700 if (InputText(label
, buf
, IM_ARRAYSIZE(buf
), flags
))
2701 value_changed
= DataTypeApplyOpFromText(buf
, g
.InputTextState
.InitialText
.Data
, data_type
, data_ptr
, format
);
2704 return value_changed
;
2707 bool ImGui::InputScalarN(const char* label
, ImGuiDataType data_type
, void* v
, int components
, const void* step
, const void* step_fast
, const char* format
, ImGuiInputTextFlags flags
)
2709 ImGuiWindow
* window
= GetCurrentWindow();
2710 if (window
->SkipItems
)
2713 ImGuiContext
& g
= *GImGui
;
2714 bool value_changed
= false;
2717 PushMultiItemsWidths(components
);
2718 size_t type_size
= GDataTypeInfo
[data_type
].Size
;
2719 for (int i
= 0; i
< components
; i
++)
2722 value_changed
|= InputScalar("", data_type
, v
, step
, step_fast
, format
, flags
);
2723 SameLine(0, g
.Style
.ItemInnerSpacing
.x
);
2726 v
= (void*)((char*)v
+ type_size
);
2730 TextUnformatted(label
, FindRenderedTextEnd(label
));
2732 return value_changed
;
2735 bool ImGui::InputFloat(const char* label
, float* v
, float step
, float step_fast
, const char* format
, ImGuiInputTextFlags flags
)
2737 flags
|= ImGuiInputTextFlags_CharsScientific
;
2738 return InputScalar(label
, ImGuiDataType_Float
, (void*)v
, (void*)(step
>0.0f
? &step
: NULL
), (void*)(step_fast
>0.0f
? &step_fast
: NULL
), format
, flags
);
2741 bool ImGui::InputFloat2(const char* label
, float v
[2], const char* format
, ImGuiInputTextFlags flags
)
2743 return InputScalarN(label
, ImGuiDataType_Float
, v
, 2, NULL
, NULL
, format
, flags
);
2746 bool ImGui::InputFloat3(const char* label
, float v
[3], const char* format
, ImGuiInputTextFlags flags
)
2748 return InputScalarN(label
, ImGuiDataType_Float
, v
, 3, NULL
, NULL
, format
, flags
);
2751 bool ImGui::InputFloat4(const char* label
, float v
[4], const char* format
, ImGuiInputTextFlags flags
)
2753 return InputScalarN(label
, ImGuiDataType_Float
, v
, 4, NULL
, NULL
, format
, flags
);
2756 // Prefer using "const char* format" directly, which is more flexible and consistent with other API.
2757 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2758 bool ImGui::InputFloat(const char* label
, float* v
, float step
, float step_fast
, int decimal_precision
, ImGuiInputTextFlags flags
)
2760 char format
[16] = "%f";
2761 if (decimal_precision
>= 0)
2762 ImFormatString(format
, IM_ARRAYSIZE(format
), "%%.%df", decimal_precision
);
2763 return InputFloat(label
, v
, step
, step_fast
, format
, flags
);
2766 bool ImGui::InputFloat2(const char* label
, float v
[2], int decimal_precision
, ImGuiInputTextFlags flags
)
2768 char format
[16] = "%f";
2769 if (decimal_precision
>= 0)
2770 ImFormatString(format
, IM_ARRAYSIZE(format
), "%%.%df", decimal_precision
);
2771 return InputScalarN(label
, ImGuiDataType_Float
, v
, 2, NULL
, NULL
, format
, flags
);
2774 bool ImGui::InputFloat3(const char* label
, float v
[3], int decimal_precision
, ImGuiInputTextFlags flags
)
2776 char format
[16] = "%f";
2777 if (decimal_precision
>= 0)
2778 ImFormatString(format
, IM_ARRAYSIZE(format
), "%%.%df", decimal_precision
);
2779 return InputScalarN(label
, ImGuiDataType_Float
, v
, 3, NULL
, NULL
, format
, flags
);
2782 bool ImGui::InputFloat4(const char* label
, float v
[4], int decimal_precision
, ImGuiInputTextFlags flags
)
2784 char format
[16] = "%f";
2785 if (decimal_precision
>= 0)
2786 ImFormatString(format
, IM_ARRAYSIZE(format
), "%%.%df", decimal_precision
);
2787 return InputScalarN(label
, ImGuiDataType_Float
, v
, 4, NULL
, NULL
, format
, flags
);
2789 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2791 bool ImGui::InputInt(const char* label
, int* v
, int step
, int step_fast
, ImGuiInputTextFlags flags
)
2793 // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
2794 const char* format
= (flags
& ImGuiInputTextFlags_CharsHexadecimal
) ? "%08X" : "%d";
2795 return InputScalar(label
, ImGuiDataType_S32
, (void*)v
, (void*)(step
>0 ? &step
: NULL
), (void*)(step_fast
>0 ? &step_fast
: NULL
), format
, flags
);
2798 bool ImGui::InputInt2(const char* label
, int v
[2], ImGuiInputTextFlags flags
)
2800 return InputScalarN(label
, ImGuiDataType_S32
, v
, 2, NULL
, NULL
, "%d", flags
);
2803 bool ImGui::InputInt3(const char* label
, int v
[3], ImGuiInputTextFlags flags
)
2805 return InputScalarN(label
, ImGuiDataType_S32
, v
, 3, NULL
, NULL
, "%d", flags
);
2808 bool ImGui::InputInt4(const char* label
, int v
[4], ImGuiInputTextFlags flags
)
2810 return InputScalarN(label
, ImGuiDataType_S32
, v
, 4, NULL
, NULL
, "%d", flags
);
2813 bool ImGui::InputDouble(const char* label
, double* v
, double step
, double step_fast
, const char* format
, ImGuiInputTextFlags flags
)
2815 flags
|= ImGuiInputTextFlags_CharsScientific
;
2816 return InputScalar(label
, ImGuiDataType_Double
, (void*)v
, (void*)(step
>0.0 ? &step
: NULL
), (void*)(step_fast
>0.0 ? &step_fast
: NULL
), format
, flags
);
2819 //-------------------------------------------------------------------------
2820 // [SECTION] Widgets: InputText, InputTextMultiline
2821 //-------------------------------------------------------------------------
2823 // - InputTextMultiline()
2824 // - InputTextEx() [Internal]
2825 //-------------------------------------------------------------------------
2827 bool ImGui::InputText(const char* label
, char* buf
, size_t buf_size
, ImGuiInputTextFlags flags
, ImGuiInputTextCallback callback
, void* user_data
)
2829 IM_ASSERT(!(flags
& ImGuiInputTextFlags_Multiline
)); // call InputTextMultiline()
2830 return InputTextEx(label
, buf
, (int)buf_size
, ImVec2(0,0), flags
, callback
, user_data
);
2833 bool ImGui::InputTextMultiline(const char* label
, char* buf
, size_t buf_size
, const ImVec2
& size
, ImGuiInputTextFlags flags
, ImGuiInputTextCallback callback
, void* user_data
)
2835 return InputTextEx(label
, buf
, (int)buf_size
, size
, flags
| ImGuiInputTextFlags_Multiline
, callback
, user_data
);
2838 static int InputTextCalcTextLenAndLineCount(const char* text_begin
, const char** out_text_end
)
2841 const char* s
= text_begin
;
2842 while (char c
= *s
++) // We are only matching for \n so we can ignore UTF-8 decoding
2846 if (s
[0] != '\n' && s
[0] != '\r')
2852 static ImVec2
InputTextCalcTextSizeW(const ImWchar
* text_begin
, const ImWchar
* text_end
, const ImWchar
** remaining
, ImVec2
* out_offset
, bool stop_on_new_line
)
2854 ImGuiContext
& g
= *GImGui
;
2855 ImFont
* font
= g
.Font
;
2856 const float line_height
= g
.FontSize
;
2857 const float scale
= line_height
/ font
->FontSize
;
2859 ImVec2 text_size
= ImVec2(0,0);
2860 float line_width
= 0.0f
;
2862 const ImWchar
* s
= text_begin
;
2863 while (s
< text_end
)
2865 unsigned int c
= (unsigned int)(*s
++);
2868 text_size
.x
= ImMax(text_size
.x
, line_width
);
2869 text_size
.y
+= line_height
;
2871 if (stop_on_new_line
)
2878 const float char_width
= font
->GetCharAdvance((ImWchar
)c
) * scale
;
2879 line_width
+= char_width
;
2882 if (text_size
.x
< line_width
)
2883 text_size
.x
= line_width
;
2886 *out_offset
= ImVec2(line_width
, text_size
.y
+ line_height
); // offset allow for the possibility of sitting after a trailing \n
2888 if (line_width
> 0 || text_size
.y
== 0.0f
) // whereas size.y will ignore the trailing \n
2889 text_size
.y
+= line_height
;
2897 // 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)
2901 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING
* obj
) { return obj
->CurLenW
; }
2902 static ImWchar
STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING
* obj
, int idx
) { return obj
->TextW
[idx
]; }
2903 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
); }
2904 static int STB_TEXTEDIT_KEYTOTEXT(int key
) { return key
>= 0x10000 ? 0 : key
; }
2905 static ImWchar STB_TEXTEDIT_NEWLINE
= '\n';
2906 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow
* r
, STB_TEXTEDIT_STRING
* obj
, int line_start_idx
)
2908 const ImWchar
* text
= obj
->TextW
.Data
;
2909 const ImWchar
* text_remaining
= NULL
;
2910 const ImVec2 size
= InputTextCalcTextSizeW(text
+ line_start_idx
, text
+ obj
->CurLenW
, &text_remaining
, NULL
, true);
2913 r
->baseline_y_delta
= size
.y
;
2916 r
->num_chars
= (int)(text_remaining
- (text
+ line_start_idx
));
2919 static bool is_separator(unsigned int c
) { return ImCharIsBlankW(c
) || c
==',' || c
==';' || c
=='(' || c
==')' || c
=='{' || c
=='}' || c
=='[' || c
==']' || c
=='|'; }
2920 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; }
2921 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
; }
2922 #ifdef __APPLE__ // FIXME: Move setting to IO structure
2923 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; }
2924 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
; }
2926 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
; }
2928 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
2929 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
2931 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING
* obj
, int pos
, int n
)
2933 ImWchar
* dst
= obj
->TextW
.Data
+ pos
;
2935 // We maintain our buffer length in both UTF-8 and wchar formats
2936 obj
->CurLenA
-= ImTextCountUtf8BytesFromStr(dst
, dst
+ n
);
2939 // Offset remaining text (FIXME-OPT: Use memmove)
2940 const ImWchar
* src
= obj
->TextW
.Data
+ pos
+ n
;
2941 while (ImWchar c
= *src
++)
2946 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING
* obj
, int pos
, const ImWchar
* new_text
, int new_text_len
)
2948 const bool is_resizable
= (obj
->UserFlags
& ImGuiInputTextFlags_CallbackResize
) != 0;
2949 const int text_len
= obj
->CurLenW
;
2950 IM_ASSERT(pos
<= text_len
);
2952 const int new_text_len_utf8
= ImTextCountUtf8BytesFromStr(new_text
, new_text
+ new_text_len
);
2953 if (!is_resizable
&& (new_text_len_utf8
+ obj
->CurLenA
+ 1 > obj
->BufCapacityA
))
2956 // Grow internal buffer if needed
2957 if (new_text_len
+ text_len
+ 1 > obj
->TextW
.Size
)
2961 IM_ASSERT(text_len
< obj
->TextW
.Size
);
2962 obj
->TextW
.resize(text_len
+ ImClamp(new_text_len
* 4, 32, ImMax(256, new_text_len
)) + 1);
2965 ImWchar
* text
= obj
->TextW
.Data
;
2966 if (pos
!= text_len
)
2967 memmove(text
+ pos
+ new_text_len
, text
+ pos
, (size_t)(text_len
- pos
) * sizeof(ImWchar
));
2968 memcpy(text
+ pos
, new_text
, (size_t)new_text_len
* sizeof(ImWchar
));
2970 obj
->CurLenW
+= new_text_len
;
2971 obj
->CurLenA
+= new_text_len_utf8
;
2972 obj
->TextW
[obj
->CurLenW
] = '\0';
2977 // 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)
2978 #define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
2979 #define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
2980 #define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
2981 #define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
2982 #define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
2983 #define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
2984 #define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
2985 #define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
2986 #define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
2987 #define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
2988 #define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
2989 #define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
2990 #define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
2991 #define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
2992 #define STB_TEXTEDIT_K_SHIFT 0x20000
2994 #define STB_TEXTEDIT_IMPLEMENTATION
2995 #include "imstb_textedit.h"
2999 void ImGuiInputTextState::OnKeyPressed(int key
)
3001 stb_textedit_key(this, &StbState
, key
);
3002 CursorFollow
= true;
3006 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3008 memset(this, 0, sizeof(*this));
3011 // Public API to manipulate UTF-8 text
3012 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
3013 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
3014 void ImGuiInputTextCallbackData::DeleteChars(int pos
, int bytes_count
)
3016 IM_ASSERT(pos
+ bytes_count
<= BufTextLen
);
3017 char* dst
= Buf
+ pos
;
3018 const char* src
= Buf
+ pos
+ bytes_count
;
3019 while (char c
= *src
++)
3023 if (CursorPos
+ bytes_count
>= pos
)
3024 CursorPos
-= bytes_count
;
3025 else if (CursorPos
>= pos
)
3027 SelectionStart
= SelectionEnd
= CursorPos
;
3029 BufTextLen
-= bytes_count
;
3032 void ImGuiInputTextCallbackData::InsertChars(int pos
, const char* new_text
, const char* new_text_end
)
3034 const bool is_resizable
= (Flags
& ImGuiInputTextFlags_CallbackResize
) != 0;
3035 const int new_text_len
= new_text_end
? (int)(new_text_end
- new_text
) : (int)strlen(new_text
);
3036 if (new_text_len
+ BufTextLen
>= BufSize
)
3041 // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
3042 ImGuiContext
& g
= *GImGui
;
3043 ImGuiInputTextState
* edit_state
= &g
.InputTextState
;
3044 IM_ASSERT(edit_state
->ID
!= 0 && g
.ActiveId
== edit_state
->ID
);
3045 IM_ASSERT(Buf
== edit_state
->TempBuffer
.Data
);
3046 int new_buf_size
= BufTextLen
+ ImClamp(new_text_len
* 4, 32, ImMax(256, new_text_len
)) + 1;
3047 edit_state
->TempBuffer
.reserve(new_buf_size
+ 1);
3048 Buf
= edit_state
->TempBuffer
.Data
;
3049 BufSize
= edit_state
->BufCapacityA
= new_buf_size
;
3052 if (BufTextLen
!= pos
)
3053 memmove(Buf
+ pos
+ new_text_len
, Buf
+ pos
, (size_t)(BufTextLen
- pos
));
3054 memcpy(Buf
+ pos
, new_text
, (size_t)new_text_len
* sizeof(char));
3055 Buf
[BufTextLen
+ new_text_len
] = '\0';
3057 if (CursorPos
>= pos
)
3058 CursorPos
+= new_text_len
;
3059 SelectionStart
= SelectionEnd
= CursorPos
;
3061 BufTextLen
+= new_text_len
;
3064 // Return false to discard a character.
3065 static bool InputTextFilterCharacter(unsigned int* p_char
, ImGuiInputTextFlags flags
, ImGuiInputTextCallback callback
, void* user_data
)
3067 unsigned int c
= *p_char
;
3069 if (c
< 128 && c
!= ' ' && !isprint((int)(c
& 0xFF)))
3072 pass
|= (c
== '\n' && (flags
& ImGuiInputTextFlags_Multiline
));
3073 pass
|= (c
== '\t' && (flags
& ImGuiInputTextFlags_AllowTabInput
));
3078 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.
3081 if (flags
& (ImGuiInputTextFlags_CharsDecimal
| ImGuiInputTextFlags_CharsHexadecimal
| ImGuiInputTextFlags_CharsUppercase
| ImGuiInputTextFlags_CharsNoBlank
| ImGuiInputTextFlags_CharsScientific
))
3083 if (flags
& ImGuiInputTextFlags_CharsDecimal
)
3084 if (!(c
>= '0' && c
<= '9') && (c
!= '.') && (c
!= '-') && (c
!= '+') && (c
!= '*') && (c
!= '/'))
3087 if (flags
& ImGuiInputTextFlags_CharsScientific
)
3088 if (!(c
>= '0' && c
<= '9') && (c
!= '.') && (c
!= '-') && (c
!= '+') && (c
!= '*') && (c
!= '/') && (c
!= 'e') && (c
!= 'E'))
3091 if (flags
& ImGuiInputTextFlags_CharsHexadecimal
)
3092 if (!(c
>= '0' && c
<= '9') && !(c
>= 'a' && c
<= 'f') && !(c
>= 'A' && c
<= 'F'))
3095 if (flags
& ImGuiInputTextFlags_CharsUppercase
)
3096 if (c
>= 'a' && c
<= 'z')
3097 *p_char
= (c
+= (unsigned int)('A'-'a'));
3099 if (flags
& ImGuiInputTextFlags_CharsNoBlank
)
3100 if (ImCharIsBlankW(c
))
3104 if (flags
& ImGuiInputTextFlags_CallbackCharFilter
)
3106 ImGuiInputTextCallbackData callback_data
;
3107 memset(&callback_data
, 0, sizeof(ImGuiInputTextCallbackData
));
3108 callback_data
.EventFlag
= ImGuiInputTextFlags_CallbackCharFilter
;
3109 callback_data
.EventChar
= (ImWchar
)c
;
3110 callback_data
.Flags
= flags
;
3111 callback_data
.UserData
= user_data
;
3112 if (callback(&callback_data
) != 0)
3114 *p_char
= callback_data
.EventChar
;
3115 if (!callback_data
.EventChar
)
3122 // Edit a string of text
3123 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3124 // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3125 // Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3126 // - 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.
3127 // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3128 // (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)
3129 bool ImGui::InputTextEx(const char* label
, char* buf
, int buf_size
, const ImVec2
& size_arg
, ImGuiInputTextFlags flags
, ImGuiInputTextCallback callback
, void* callback_user_data
)
3131 ImGuiWindow
* window
= GetCurrentWindow();
3132 if (window
->SkipItems
)
3135 IM_ASSERT(!((flags
& ImGuiInputTextFlags_CallbackHistory
) && (flags
& ImGuiInputTextFlags_Multiline
))); // Can't use both together (they both use up/down keys)
3136 IM_ASSERT(!((flags
& ImGuiInputTextFlags_CallbackCompletion
) && (flags
& ImGuiInputTextFlags_AllowTabInput
))); // Can't use both together (they both use tab key)
3138 ImGuiContext
& g
= *GImGui
;
3140 const ImGuiStyle
& style
= g
.Style
;
3142 const bool is_multiline
= (flags
& ImGuiInputTextFlags_Multiline
) != 0;
3143 const bool is_editable
= (flags
& ImGuiInputTextFlags_ReadOnly
) == 0;
3144 const bool is_password
= (flags
& ImGuiInputTextFlags_Password
) != 0;
3145 const bool is_undoable
= (flags
& ImGuiInputTextFlags_NoUndoRedo
) == 0;
3146 const bool is_resizable
= (flags
& ImGuiInputTextFlags_CallbackResize
) != 0;
3148 IM_ASSERT(callback
!= NULL
); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3150 if (is_multiline
) // Open group before calling GetID() because groups tracks id created within their scope,
3152 const ImGuiID id
= window
->GetID(label
);
3153 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
3154 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
3155 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
3156 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
));
3158 ImGuiWindow
* draw_window
= window
;
3161 if (!ItemAdd(total_bb
, id
, &frame_bb
))
3163 ItemSize(total_bb
, style
.FramePadding
.y
);
3167 if (!BeginChildFrame(id
, frame_bb
.GetSize()))
3173 draw_window
= GetCurrentWindow();
3174 draw_window
->DC
.NavLayerActiveMaskNext
|= draw_window
->DC
.NavLayerCurrentMask
; // This is to ensure that EndChild() will display a navigation highlight
3175 size
.x
-= draw_window
->ScrollbarSizes
.x
;
3179 ItemSize(total_bb
, style
.FramePadding
.y
);
3180 if (!ItemAdd(total_bb
, id
, &frame_bb
))
3183 const bool hovered
= ItemHoverable(frame_bb
, id
);
3185 g
.MouseCursor
= ImGuiMouseCursor_TextInput
;
3187 // Password pushes a temporary font with only a fallback glyph
3190 const ImFontGlyph
* glyph
= g
.Font
->FindGlyph('*');
3191 ImFont
* password_font
= &g
.InputTextPasswordFont
;
3192 password_font
->FontSize
= g
.Font
->FontSize
;
3193 password_font
->Scale
= g
.Font
->Scale
;
3194 password_font
->DisplayOffset
= g
.Font
->DisplayOffset
;
3195 password_font
->Ascent
= g
.Font
->Ascent
;
3196 password_font
->Descent
= g
.Font
->Descent
;
3197 password_font
->ContainerAtlas
= g
.Font
->ContainerAtlas
;
3198 password_font
->FallbackGlyph
= glyph
;
3199 password_font
->FallbackAdvanceX
= glyph
->AdvanceX
;
3200 IM_ASSERT(password_font
->Glyphs
.empty() && password_font
->IndexAdvanceX
.empty() && password_font
->IndexLookup
.empty());
3201 PushFont(password_font
);
3204 // NB: we are only allowed to access 'edit_state' if we are the active widget.
3205 ImGuiInputTextState
& edit_state
= g
.InputTextState
;
3207 const bool focus_requested
= FocusableItemRegister(window
, id
, (flags
& (ImGuiInputTextFlags_CallbackCompletion
|ImGuiInputTextFlags_AllowTabInput
)) == 0); // Using completion callback disable keyboard tabbing
3208 const bool focus_requested_by_code
= focus_requested
&& (window
->FocusIdxAllCounter
== window
->FocusIdxAllRequestCurrent
);
3209 const bool focus_requested_by_tab
= focus_requested
&& !focus_requested_by_code
;
3211 const bool user_clicked
= hovered
&& io
.MouseClicked
[0];
3212 const bool user_scrolled
= is_multiline
&& g
.ActiveId
== 0 && edit_state
.ID
== id
&& g
.ActiveIdPreviousFrame
== draw_window
->GetIDNoKeepAlive("#SCROLLY");
3213 const bool user_nav_input_start
= (g
.ActiveId
!= id
) && ((g
.NavInputId
== id
) || (g
.NavActivateId
== id
&& g
.NavInputSource
== ImGuiInputSource_NavKeyboard
));
3215 bool clear_active_id
= false;
3217 bool select_all
= (g
.ActiveId
!= id
) && ((flags
& ImGuiInputTextFlags_AutoSelectAll
) != 0 || user_nav_input_start
) && (!is_multiline
);
3218 if (focus_requested
|| user_clicked
|| user_scrolled
|| user_nav_input_start
)
3220 if (g
.ActiveId
!= id
)
3223 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
3224 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
3225 const int prev_len_w
= edit_state
.CurLenW
;
3226 const int init_buf_len
= (int)strlen(buf
);
3227 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.
3228 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.
3229 memcpy(edit_state
.InitialText
.Data
, buf
, init_buf_len
+ 1);
3230 const char* buf_end
= NULL
;
3231 edit_state
.CurLenW
= ImTextStrFromUtf8(edit_state
.TextW
.Data
, buf_size
, buf
, NULL
, &buf_end
);
3232 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.
3233 edit_state
.CursorAnimReset();
3235 // Preserve cursor position and undo/redo stack if we come back to same widget
3236 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
3237 const bool recycle_state
= (edit_state
.ID
== id
) && (prev_len_w
== edit_state
.CurLenW
);
3240 // Recycle existing cursor/selection/undo stack but clamp position
3241 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
3242 edit_state
.CursorClamp();
3247 edit_state
.ScrollX
= 0.0f
;
3248 stb_textedit_initialize_state(&edit_state
.StbState
, !is_multiline
);
3249 if (!is_multiline
&& focus_requested_by_code
)
3252 if (flags
& ImGuiInputTextFlags_AlwaysInsertMode
)
3253 edit_state
.StbState
.insert_mode
= 1;
3254 if (!is_multiline
&& (focus_requested_by_tab
|| (user_clicked
&& io
.KeyCtrl
)))
3257 SetActiveID(id
, window
);
3258 SetFocusID(id
, window
);
3259 FocusWindow(window
);
3260 g
.ActiveIdBlockNavInputFlags
= (1 << ImGuiNavInput_Cancel
);
3261 if (!is_multiline
&& !(flags
& ImGuiInputTextFlags_CallbackHistory
))
3262 g
.ActiveIdAllowNavDirFlags
= ((1 << ImGuiDir_Up
) | (1 << ImGuiDir_Down
));
3264 else if (io
.MouseClicked
[0])
3266 // Release focus when we click outside
3267 clear_active_id
= true;
3270 bool value_changed
= false;
3271 bool enter_pressed
= false;
3272 int backup_current_text_length
= 0;
3274 if (g
.ActiveId
== id
)
3276 if (!is_editable
&& !g
.ActiveIdIsJustActivated
)
3278 // When read-only we always use the live data passed to the function
3279 edit_state
.TextW
.resize(buf_size
+1);
3280 const char* buf_end
= NULL
;
3281 edit_state
.CurLenW
= ImTextStrFromUtf8(edit_state
.TextW
.Data
, edit_state
.TextW
.Size
, buf
, NULL
, &buf_end
);
3282 edit_state
.CurLenA
= (int)(buf_end
- buf
);
3283 edit_state
.CursorClamp();
3286 backup_current_text_length
= edit_state
.CurLenA
;
3287 edit_state
.BufCapacityA
= buf_size
;
3288 edit_state
.UserFlags
= flags
;
3289 edit_state
.UserCallback
= callback
;
3290 edit_state
.UserCallbackData
= callback_user_data
;
3292 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
3293 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
3294 g
.ActiveIdAllowOverlap
= !io
.MouseDown
[0];
3295 g
.WantTextInputNextFrame
= 1;
3298 const float mouse_x
= (io
.MousePos
.x
- frame_bb
.Min
.x
- style
.FramePadding
.x
) + edit_state
.ScrollX
;
3299 const float mouse_y
= (is_multiline
? (io
.MousePos
.y
- draw_window
->DC
.CursorPos
.y
- style
.FramePadding
.y
) : (g
.FontSize
*0.5f
));
3301 const bool is_osx
= io
.ConfigMacOSXBehaviors
;
3302 if (select_all
|| (hovered
&& !is_osx
&& io
.MouseDoubleClicked
[0]))
3304 edit_state
.SelectAll();
3305 edit_state
.SelectedAllMouseLock
= true;
3307 else if (hovered
&& is_osx
&& io
.MouseDoubleClicked
[0])
3309 // Double-click select a word only, OS X style (by simulating keystrokes)
3310 edit_state
.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT
);
3311 edit_state
.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT
| STB_TEXTEDIT_K_SHIFT
);
3313 else if (io
.MouseClicked
[0] && !edit_state
.SelectedAllMouseLock
)
3317 stb_textedit_click(&edit_state
, &edit_state
.StbState
, mouse_x
, mouse_y
);
3318 edit_state
.CursorAnimReset();
3321 else if (io
.MouseDown
[0] && !edit_state
.SelectedAllMouseLock
&& (io
.MouseDelta
.x
!= 0.0f
|| io
.MouseDelta
.y
!= 0.0f
))
3323 stb_textedit_drag(&edit_state
, &edit_state
.StbState
, mouse_x
, mouse_y
);
3324 edit_state
.CursorAnimReset();
3325 edit_state
.CursorFollow
= true;
3327 if (edit_state
.SelectedAllMouseLock
&& !io
.MouseDown
[0])
3328 edit_state
.SelectedAllMouseLock
= false;
3330 if (io
.InputQueueCharacters
.Size
> 0)
3332 // Process text input (before we check for Return because using some IME will effectively send a Return?)
3333 // 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.
3334 bool ignore_inputs
= (io
.KeyCtrl
&& !io
.KeyAlt
) || (is_osx
&& io
.KeySuper
);
3335 if (!ignore_inputs
&& is_editable
&& !user_nav_input_start
)
3336 for (int n
= 0; n
< io
.InputQueueCharacters
.Size
; n
++)
3338 // Insert character if they pass filtering
3339 unsigned int c
= (unsigned int)io
.InputQueueCharacters
[n
];
3340 if (InputTextFilterCharacter(&c
, flags
, callback
, callback_user_data
))
3341 edit_state
.OnKeyPressed((int)c
);
3344 // Consume characters
3345 io
.InputQueueCharacters
.resize(0);
3349 bool cancel_edit
= false;
3350 if (g
.ActiveId
== id
&& !g
.ActiveIdIsJustActivated
&& !clear_active_id
)
3352 // Handle key-presses
3353 const int k_mask
= (io
.KeyShift
? STB_TEXTEDIT_K_SHIFT
: 0);
3354 const bool is_osx
= io
.ConfigMacOSXBehaviors
;
3355 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
3356 const bool is_osx_shift_shortcut
= is_osx
&& io
.KeySuper
&& io
.KeyShift
&& !io
.KeyCtrl
&& !io
.KeyAlt
;
3357 const bool is_wordmove_key_down
= is_osx
? io
.KeyAlt
: io
.KeyCtrl
; // OS X style: Text editing cursor movement using Alt instead of Ctrl
3358 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
3359 const bool is_ctrl_key_only
= io
.KeyCtrl
&& !io
.KeyShift
&& !io
.KeyAlt
&& !io
.KeySuper
;
3360 const bool is_shift_key_only
= io
.KeyShift
&& !io
.KeyCtrl
&& !io
.KeyAlt
&& !io
.KeySuper
;
3362 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());
3363 const bool is_copy
= ((is_shortcut_key
&& IsKeyPressedMap(ImGuiKey_C
)) || (is_ctrl_key_only
&& IsKeyPressedMap(ImGuiKey_Insert
))) && !is_password
&& (!is_multiline
|| edit_state
.HasSelection());
3364 const bool is_paste
= ((is_shortcut_key
&& IsKeyPressedMap(ImGuiKey_V
)) || (is_shift_key_only
&& IsKeyPressedMap(ImGuiKey_Insert
))) && is_editable
;
3365 const bool is_undo
= ((is_shortcut_key
&& IsKeyPressedMap(ImGuiKey_Z
)) && is_editable
&& is_undoable
);
3366 const bool is_redo
= ((is_shortcut_key
&& IsKeyPressedMap(ImGuiKey_Y
)) || (is_osx_shift_shortcut
&& IsKeyPressedMap(ImGuiKey_Z
))) && is_editable
&& is_undoable
;
3368 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
); }
3369 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
); }
3370 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
); }
3371 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
); }
3372 else if (IsKeyPressedMap(ImGuiKey_Home
)) { edit_state
.OnKeyPressed(io
.KeyCtrl
? STB_TEXTEDIT_K_TEXTSTART
| k_mask
: STB_TEXTEDIT_K_LINESTART
| k_mask
); }
3373 else if (IsKeyPressedMap(ImGuiKey_End
)) { edit_state
.OnKeyPressed(io
.KeyCtrl
? STB_TEXTEDIT_K_TEXTEND
| k_mask
: STB_TEXTEDIT_K_LINEEND
| k_mask
); }
3374 else if (IsKeyPressedMap(ImGuiKey_Delete
) && is_editable
) { edit_state
.OnKeyPressed(STB_TEXTEDIT_K_DELETE
| k_mask
); }
3375 else if (IsKeyPressedMap(ImGuiKey_Backspace
) && is_editable
)
3377 if (!edit_state
.HasSelection())
3379 if (is_wordmove_key_down
) edit_state
.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT
|STB_TEXTEDIT_K_SHIFT
);
3380 else if (is_osx
&& io
.KeySuper
&& !io
.KeyAlt
&& !io
.KeyCtrl
) edit_state
.OnKeyPressed(STB_TEXTEDIT_K_LINESTART
|STB_TEXTEDIT_K_SHIFT
);
3382 edit_state
.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE
| k_mask
);
3384 else if (IsKeyPressedMap(ImGuiKey_Enter
))
3386 bool ctrl_enter_for_new_line
= (flags
& ImGuiInputTextFlags_CtrlEnterForNewLine
) != 0;
3387 if (!is_multiline
|| (ctrl_enter_for_new_line
&& !io
.KeyCtrl
) || (!ctrl_enter_for_new_line
&& io
.KeyCtrl
))
3389 enter_pressed
= clear_active_id
= true;
3391 else if (is_editable
)
3393 unsigned int c
= '\n'; // Insert new line
3394 if (InputTextFilterCharacter(&c
, flags
, callback
, callback_user_data
))
3395 edit_state
.OnKeyPressed((int)c
);
3398 else if ((flags
& ImGuiInputTextFlags_AllowTabInput
) && IsKeyPressedMap(ImGuiKey_Tab
) && !io
.KeyCtrl
&& !io
.KeyShift
&& !io
.KeyAlt
&& is_editable
)
3400 unsigned int c
= '\t'; // Insert TAB
3401 if (InputTextFilterCharacter(&c
, flags
, callback
, callback_user_data
))
3402 edit_state
.OnKeyPressed((int)c
);
3404 else if (IsKeyPressedMap(ImGuiKey_Escape
))
3406 clear_active_id
= cancel_edit
= true;
3408 else if (is_undo
|| is_redo
)
3410 edit_state
.OnKeyPressed(is_undo
? STB_TEXTEDIT_K_UNDO
: STB_TEXTEDIT_K_REDO
);
3411 edit_state
.ClearSelection();
3413 else if (is_shortcut_key
&& IsKeyPressedMap(ImGuiKey_A
))
3415 edit_state
.SelectAll();
3416 edit_state
.CursorFollow
= true;
3418 else if (is_cut
|| is_copy
)
3421 if (io
.SetClipboardTextFn
)
3423 const int ib
= edit_state
.HasSelection() ? ImMin(edit_state
.StbState
.select_start
, edit_state
.StbState
.select_end
) : 0;
3424 const int ie
= edit_state
.HasSelection() ? ImMax(edit_state
.StbState
.select_start
, edit_state
.StbState
.select_end
) : edit_state
.CurLenW
;
3425 edit_state
.TempBuffer
.resize((ie
-ib
) * 4 + 1);
3426 ImTextStrToUtf8(edit_state
.TempBuffer
.Data
, edit_state
.TempBuffer
.Size
, edit_state
.TextW
.Data
+ib
, edit_state
.TextW
.Data
+ie
);
3427 SetClipboardText(edit_state
.TempBuffer
.Data
);
3431 if (!edit_state
.HasSelection())
3432 edit_state
.SelectAll();
3433 edit_state
.CursorFollow
= true;
3434 stb_textedit_cut(&edit_state
, &edit_state
.StbState
);
3439 if (const char* clipboard
= GetClipboardText())
3441 // Filter pasted buffer
3442 const int clipboard_len
= (int)strlen(clipboard
);
3443 ImWchar
* clipboard_filtered
= (ImWchar
*)MemAlloc((clipboard_len
+1) * sizeof(ImWchar
));
3444 int clipboard_filtered_len
= 0;
3445 for (const char* s
= clipboard
; *s
; )
3448 s
+= ImTextCharFromUtf8(&c
, s
, NULL
);
3451 if (c
>= 0x10000 || !InputTextFilterCharacter(&c
, flags
, callback
, callback_user_data
))
3453 clipboard_filtered
[clipboard_filtered_len
++] = (ImWchar
)c
;
3455 clipboard_filtered
[clipboard_filtered_len
] = 0;
3456 if (clipboard_filtered_len
> 0) // If everything was filtered, ignore the pasting operation
3458 stb_textedit_paste(&edit_state
, &edit_state
.StbState
, clipboard_filtered
, clipboard_filtered_len
);
3459 edit_state
.CursorFollow
= true;
3461 MemFree(clipboard_filtered
);
3466 if (g
.ActiveId
== id
)
3468 const char* apply_new_text
= NULL
;
3469 int apply_new_text_length
= 0;
3472 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
3473 if (is_editable
&& strcmp(buf
, edit_state
.InitialText
.Data
) != 0)
3475 apply_new_text
= edit_state
.InitialText
.Data
;
3476 apply_new_text_length
= edit_state
.InitialText
.Size
- 1;
3480 // 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.
3481 // 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.
3482 bool apply_edit_back_to_user_buffer
= !cancel_edit
|| (enter_pressed
&& (flags
& ImGuiInputTextFlags_EnterReturnsTrue
) != 0);
3483 if (apply_edit_back_to_user_buffer
)
3485 // Apply new value immediately - copy modified buffer back
3486 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
3487 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
3488 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
3491 edit_state
.TempBuffer
.resize(edit_state
.TextW
.Size
* 4 + 1);
3492 ImTextStrToUtf8(edit_state
.TempBuffer
.Data
, edit_state
.TempBuffer
.Size
, edit_state
.TextW
.Data
, NULL
);
3496 if ((flags
& (ImGuiInputTextFlags_CallbackCompletion
| ImGuiInputTextFlags_CallbackHistory
| ImGuiInputTextFlags_CallbackAlways
)) != 0)
3498 IM_ASSERT(callback
!= NULL
);
3500 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
3501 ImGuiInputTextFlags event_flag
= 0;
3502 ImGuiKey event_key
= ImGuiKey_COUNT
;
3503 if ((flags
& ImGuiInputTextFlags_CallbackCompletion
) != 0 && IsKeyPressedMap(ImGuiKey_Tab
))
3505 event_flag
= ImGuiInputTextFlags_CallbackCompletion
;
3506 event_key
= ImGuiKey_Tab
;
3508 else if ((flags
& ImGuiInputTextFlags_CallbackHistory
) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow
))
3510 event_flag
= ImGuiInputTextFlags_CallbackHistory
;
3511 event_key
= ImGuiKey_UpArrow
;
3513 else if ((flags
& ImGuiInputTextFlags_CallbackHistory
) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow
))
3515 event_flag
= ImGuiInputTextFlags_CallbackHistory
;
3516 event_key
= ImGuiKey_DownArrow
;
3518 else if (flags
& ImGuiInputTextFlags_CallbackAlways
)
3519 event_flag
= ImGuiInputTextFlags_CallbackAlways
;
3523 ImGuiInputTextCallbackData callback_data
;
3524 memset(&callback_data
, 0, sizeof(ImGuiInputTextCallbackData
));
3525 callback_data
.EventFlag
= event_flag
;
3526 callback_data
.Flags
= flags
;
3527 callback_data
.UserData
= callback_user_data
;
3529 callback_data
.EventKey
= event_key
;
3530 callback_data
.Buf
= edit_state
.TempBuffer
.Data
;
3531 callback_data
.BufTextLen
= edit_state
.CurLenA
;
3532 callback_data
.BufSize
= edit_state
.BufCapacityA
;
3533 callback_data
.BufDirty
= false;
3535 // 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)
3536 ImWchar
* text
= edit_state
.TextW
.Data
;
3537 const int utf8_cursor_pos
= callback_data
.CursorPos
= ImTextCountUtf8BytesFromStr(text
, text
+ edit_state
.StbState
.cursor
);
3538 const int utf8_selection_start
= callback_data
.SelectionStart
= ImTextCountUtf8BytesFromStr(text
, text
+ edit_state
.StbState
.select_start
);
3539 const int utf8_selection_end
= callback_data
.SelectionEnd
= ImTextCountUtf8BytesFromStr(text
, text
+ edit_state
.StbState
.select_end
);
3542 callback(&callback_data
);
3544 // Read back what user may have modified
3545 IM_ASSERT(callback_data
.Buf
== edit_state
.TempBuffer
.Data
); // Invalid to modify those fields
3546 IM_ASSERT(callback_data
.BufSize
== edit_state
.BufCapacityA
);
3547 IM_ASSERT(callback_data
.Flags
== flags
);
3548 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; }
3549 if (callback_data
.SelectionStart
!= utf8_selection_start
) { edit_state
.StbState
.select_start
= ImTextCountCharsFromUtf8(callback_data
.Buf
, callback_data
.Buf
+ callback_data
.SelectionStart
); }
3550 if (callback_data
.SelectionEnd
!= utf8_selection_end
) { edit_state
.StbState
.select_end
= ImTextCountCharsFromUtf8(callback_data
.Buf
, callback_data
.Buf
+ callback_data
.SelectionEnd
); }
3551 if (callback_data
.BufDirty
)
3553 IM_ASSERT(callback_data
.BufTextLen
== (int)strlen(callback_data
.Buf
)); // You need to maintain BufTextLen if you change the text!
3554 if (callback_data
.BufTextLen
> backup_current_text_length
&& is_resizable
)
3555 edit_state
.TextW
.resize(edit_state
.TextW
.Size
+ (callback_data
.BufTextLen
- backup_current_text_length
));
3556 edit_state
.CurLenW
= ImTextStrFromUtf8(edit_state
.TextW
.Data
, edit_state
.TextW
.Size
, callback_data
.Buf
, NULL
);
3557 edit_state
.CurLenA
= callback_data
.BufTextLen
; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
3558 edit_state
.CursorAnimReset();
3563 // Will copy result string if modified
3564 if (is_editable
&& strcmp(edit_state
.TempBuffer
.Data
, buf
) != 0)
3566 apply_new_text
= edit_state
.TempBuffer
.Data
;
3567 apply_new_text_length
= edit_state
.CurLenA
;
3571 // Copy result to user buffer
3574 IM_ASSERT(apply_new_text_length
>= 0);
3575 if (backup_current_text_length
!= apply_new_text_length
&& is_resizable
)
3577 ImGuiInputTextCallbackData callback_data
;
3578 callback_data
.EventFlag
= ImGuiInputTextFlags_CallbackResize
;
3579 callback_data
.Flags
= flags
;
3580 callback_data
.Buf
= buf
;
3581 callback_data
.BufTextLen
= apply_new_text_length
;
3582 callback_data
.BufSize
= ImMax(buf_size
, apply_new_text_length
+ 1);
3583 callback_data
.UserData
= callback_user_data
;
3584 callback(&callback_data
);
3585 buf
= callback_data
.Buf
;
3586 buf_size
= callback_data
.BufSize
;
3587 apply_new_text_length
= ImMin(callback_data
.BufTextLen
, buf_size
- 1);
3588 IM_ASSERT(apply_new_text_length
<= buf_size
);
3591 // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
3592 ImStrncpy(buf
, apply_new_text
, ImMin(apply_new_text_length
+ 1, buf_size
));
3593 value_changed
= true;
3596 // Clear temporary user storage
3597 edit_state
.UserFlags
= 0;
3598 edit_state
.UserCallback
= NULL
;
3599 edit_state
.UserCallbackData
= NULL
;
3602 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
3603 if (clear_active_id
&& g
.ActiveId
== id
)
3607 // 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.
3608 const char* buf_display
= (g
.ActiveId
== id
&& is_editable
) ? edit_state
.TempBuffer
.Data
: buf
; buf
= NULL
;
3610 // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
3611 // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
3612 // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
3613 const int buf_display_max_length
= 2 * 1024 * 1024;
3617 RenderNavHighlight(frame_bb
, id
);
3618 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, GetColorU32(ImGuiCol_FrameBg
), true, style
.FrameRounding
);
3621 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
3622 ImVec2 render_pos
= is_multiline
? draw_window
->DC
.CursorPos
: frame_bb
.Min
+ style
.FramePadding
;
3623 ImVec2
text_size(0.f
, 0.f
);
3624 const bool is_currently_scrolling
= (edit_state
.ID
== id
&& is_multiline
&& g
.ActiveId
== draw_window
->GetIDNoKeepAlive("#SCROLLY"));
3625 if (g
.ActiveId
== id
|| is_currently_scrolling
)
3627 edit_state
.CursorAnim
+= io
.DeltaTime
;
3629 // This is going to be messy. We need to:
3630 // - Display the text (this alone can be more easily clipped)
3631 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
3632 // - Measure text height (for scrollbar)
3633 // 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)
3634 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
3635 const ImWchar
* text_begin
= edit_state
.TextW
.Data
;
3636 ImVec2 cursor_offset
, select_start_offset
;
3639 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
3640 const ImWchar
* searches_input_ptr
[2];
3641 searches_input_ptr
[0] = text_begin
+ edit_state
.StbState
.cursor
;
3642 searches_input_ptr
[1] = NULL
;
3643 int searches_remaining
= 1;
3644 int searches_result_line_number
[2] = { -1, -999 };
3645 if (edit_state
.StbState
.select_start
!= edit_state
.StbState
.select_end
)
3647 searches_input_ptr
[1] = text_begin
+ ImMin(edit_state
.StbState
.select_start
, edit_state
.StbState
.select_end
);
3648 searches_result_line_number
[1] = -1;
3649 searches_remaining
++;
3652 // Iterate all lines to find our line numbers
3653 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
3654 searches_remaining
+= is_multiline
? 1 : 0;
3656 //for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bits
3657 for (const ImWchar
* s
= text_begin
; *s
!= 0; s
++)
3661 if (searches_result_line_number
[0] == -1 && s
>= searches_input_ptr
[0]) { searches_result_line_number
[0] = line_count
; if (--searches_remaining
<= 0) break; }
3662 if (searches_result_line_number
[1] == -1 && s
>= searches_input_ptr
[1]) { searches_result_line_number
[1] = line_count
; if (--searches_remaining
<= 0) break; }
3665 if (searches_result_line_number
[0] == -1) searches_result_line_number
[0] = line_count
;
3666 if (searches_result_line_number
[1] == -1) searches_result_line_number
[1] = line_count
;
3668 // Calculate 2d position by finding the beginning of the line and measuring distance
3669 cursor_offset
.x
= InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr
[0], text_begin
), searches_input_ptr
[0]).x
;
3670 cursor_offset
.y
= searches_result_line_number
[0] * g
.FontSize
;
3671 if (searches_result_line_number
[1] >= 0)
3673 select_start_offset
.x
= InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr
[1], text_begin
), searches_input_ptr
[1]).x
;
3674 select_start_offset
.y
= searches_result_line_number
[1] * g
.FontSize
;
3677 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
3679 text_size
= ImVec2(size
.x
, line_count
* g
.FontSize
);
3683 if (edit_state
.CursorFollow
)
3685 // Horizontal scroll in chunks of quarter width
3686 if (!(flags
& ImGuiInputTextFlags_NoHorizontalScroll
))
3688 const float scroll_increment_x
= size
.x
* 0.25f
;
3689 if (cursor_offset
.x
< edit_state
.ScrollX
)
3690 edit_state
.ScrollX
= (float)(int)ImMax(0.0f
, cursor_offset
.x
- scroll_increment_x
);
3691 else if (cursor_offset
.x
- size
.x
>= edit_state
.ScrollX
)
3692 edit_state
.ScrollX
= (float)(int)(cursor_offset
.x
- size
.x
+ scroll_increment_x
);
3696 edit_state
.ScrollX
= 0.0f
;
3702 float scroll_y
= draw_window
->Scroll
.y
;
3703 if (cursor_offset
.y
- g
.FontSize
< scroll_y
)
3704 scroll_y
= ImMax(0.0f
, cursor_offset
.y
- g
.FontSize
);
3705 else if (cursor_offset
.y
- size
.y
>= scroll_y
)
3706 scroll_y
= cursor_offset
.y
- size
.y
;
3707 draw_window
->DC
.CursorPos
.y
+= (draw_window
->Scroll
.y
- scroll_y
); // To avoid a frame of lag
3708 draw_window
->Scroll
.y
= scroll_y
;
3709 render_pos
.y
= draw_window
->DC
.CursorPos
.y
;
3712 edit_state
.CursorFollow
= false;
3713 const ImVec2 render_scroll
= ImVec2(edit_state
.ScrollX
, 0.0f
);
3716 if (edit_state
.StbState
.select_start
!= edit_state
.StbState
.select_end
)
3718 const ImWchar
* text_selected_begin
= text_begin
+ ImMin(edit_state
.StbState
.select_start
, edit_state
.StbState
.select_end
);
3719 const ImWchar
* text_selected_end
= text_begin
+ ImMax(edit_state
.StbState
.select_start
, edit_state
.StbState
.select_end
);
3721 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.
3722 float bg_offy_dn
= is_multiline
? 0.0f
: 2.0f
;
3723 ImU32 bg_color
= GetColorU32(ImGuiCol_TextSelectedBg
);
3724 ImVec2 rect_pos
= render_pos
+ select_start_offset
- render_scroll
;
3725 for (const ImWchar
* p
= text_selected_begin
; p
< text_selected_end
; )
3727 if (rect_pos
.y
> clip_rect
.w
+ g
.FontSize
)
3729 if (rect_pos
.y
< clip_rect
.y
)
3731 //p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bits
3732 //p = p ? p + 1 : text_selected_end;
3733 while (p
< text_selected_end
)
3739 ImVec2 rect_size
= InputTextCalcTextSizeW(p
, text_selected_end
, &p
, NULL
, true);
3740 if (rect_size
.x
<= 0.0f
) rect_size
.x
= (float)(int)(g
.Font
->GetCharAdvance((ImWchar
)' ') * 0.50f
); // So we can see selected empty lines
3741 ImRect
rect(rect_pos
+ ImVec2(0.0f
, bg_offy_up
- g
.FontSize
), rect_pos
+ImVec2(rect_size
.x
, bg_offy_dn
));
3742 rect
.ClipWith(clip_rect
);
3743 if (rect
.Overlaps(clip_rect
))
3744 draw_window
->DrawList
->AddRectFilled(rect
.Min
, rect
.Max
, bg_color
);
3746 rect_pos
.x
= render_pos
.x
- render_scroll
.x
;
3747 rect_pos
.y
+= g
.FontSize
;
3751 const int buf_display_len
= edit_state
.CurLenA
;
3752 if (is_multiline
|| buf_display_len
< buf_display_max_length
)
3753 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
);
3755 // Draw blinking cursor
3756 bool cursor_is_visible
= (!g
.IO
.ConfigInputTextCursorBlink
) || (g
.InputTextState
.CursorAnim
<= 0.0f
) || ImFmod(g
.InputTextState
.CursorAnim
, 1.20f
) <= 0.80f
;
3757 ImVec2 cursor_screen_pos
= render_pos
+ cursor_offset
- render_scroll
;
3758 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
);
3759 if (cursor_is_visible
&& cursor_screen_rect
.Overlaps(clip_rect
))
3760 draw_window
->DrawList
->AddLine(cursor_screen_rect
.Min
, cursor_screen_rect
.GetBL(), GetColorU32(ImGuiCol_Text
));
3762 // 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.)
3764 g
.PlatformImePos
= ImVec2(cursor_screen_pos
.x
- 1, cursor_screen_pos
.y
- g
.FontSize
);
3769 const char* buf_end
= NULL
;
3771 text_size
= ImVec2(size
.x
, InputTextCalcTextLenAndLineCount(buf_display
, &buf_end
) * g
.FontSize
); // We don't need width
3773 buf_end
= buf_display
+ strlen(buf_display
);
3774 if (is_multiline
|| (buf_end
- buf_display
) < buf_display_max_length
)
3775 draw_window
->DrawList
->AddText(g
.Font
, g
.FontSize
, render_pos
, GetColorU32(ImGuiCol_Text
), buf_display
, buf_end
, 0.0f
, is_multiline
? NULL
: &clip_rect
);
3780 Dummy(text_size
+ ImVec2(0.0f
, g
.FontSize
)); // Always add room to scroll an extra line
3789 if (g
.LogEnabled
&& !is_password
)
3790 LogRenderedText(&render_pos
, buf_display
, NULL
);
3792 if (label_size
.x
> 0)
3793 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
3798 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
);
3799 if ((flags
& ImGuiInputTextFlags_EnterReturnsTrue
) != 0)
3800 return enter_pressed
;
3802 return value_changed
;
3805 //-------------------------------------------------------------------------
3806 // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
3807 //-------------------------------------------------------------------------
3811 // - RenderColorRectWithAlphaCheckerboard() [Internal]
3814 // - SetColorEditOptions()
3815 // - ColorTooltip() [Internal]
3816 // - ColorEditOptionsPopup() [Internal]
3817 // - ColorPickerOptionsPopup() [Internal]
3818 //-------------------------------------------------------------------------
3820 bool ImGui::ColorEdit3(const char* label
, float col
[3], ImGuiColorEditFlags flags
)
3822 return ColorEdit4(label
, col
, flags
| ImGuiColorEditFlags_NoAlpha
);
3825 // Edit colors components (each component in 0.0f..1.0f range).
3826 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
3827 // 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.
3828 bool ImGui::ColorEdit4(const char* label
, float col
[4], ImGuiColorEditFlags flags
)
3830 ImGuiWindow
* window
= GetCurrentWindow();
3831 if (window
->SkipItems
)
3834 ImGuiContext
& g
= *GImGui
;
3835 const ImGuiStyle
& style
= g
.Style
;
3836 const float square_sz
= GetFrameHeight();
3837 const float w_extra
= (flags
& ImGuiColorEditFlags_NoSmallPreview
) ? 0.0f
: (square_sz
+ style
.ItemInnerSpacing
.x
);
3838 const float w_items_all
= CalcItemWidth() - w_extra
;
3839 const char* label_display_end
= FindRenderedTextEnd(label
);
3844 // If we're not showing any slider there's no point in doing any HSV conversions
3845 const ImGuiColorEditFlags flags_untouched
= flags
;
3846 if (flags
& ImGuiColorEditFlags_NoInputs
)
3847 flags
= (flags
& (~ImGuiColorEditFlags__InputsMask
)) | ImGuiColorEditFlags_RGB
| ImGuiColorEditFlags_NoOptions
;
3849 // Context menu: display and modify options (before defaults are applied)
3850 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
3851 ColorEditOptionsPopup(col
, flags
);
3853 // Read stored options
3854 if (!(flags
& ImGuiColorEditFlags__InputsMask
))
3855 flags
|= (g
.ColorEditOptions
& ImGuiColorEditFlags__InputsMask
);
3856 if (!(flags
& ImGuiColorEditFlags__DataTypeMask
))
3857 flags
|= (g
.ColorEditOptions
& ImGuiColorEditFlags__DataTypeMask
);
3858 if (!(flags
& ImGuiColorEditFlags__PickerMask
))
3859 flags
|= (g
.ColorEditOptions
& ImGuiColorEditFlags__PickerMask
);
3860 flags
|= (g
.ColorEditOptions
& ~(ImGuiColorEditFlags__InputsMask
| ImGuiColorEditFlags__DataTypeMask
| ImGuiColorEditFlags__PickerMask
));
3862 const bool alpha
= (flags
& ImGuiColorEditFlags_NoAlpha
) == 0;
3863 const bool hdr
= (flags
& ImGuiColorEditFlags_HDR
) != 0;
3864 const int components
= alpha
? 4 : 3;
3866 // Convert to the formats we need
3867 float f
[4] = { col
[0], col
[1], col
[2], alpha
? col
[3] : 1.0f
};
3868 if (flags
& ImGuiColorEditFlags_HSV
)
3869 ColorConvertRGBtoHSV(f
[0], f
[1], f
[2], f
[0], f
[1], f
[2]);
3870 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]) };
3872 bool value_changed
= false;
3873 bool value_changed_as_float
= false;
3875 if ((flags
& (ImGuiColorEditFlags_RGB
| ImGuiColorEditFlags_HSV
)) != 0 && (flags
& ImGuiColorEditFlags_NoInputs
) == 0)
3877 // RGB/HSV 0..255 Sliders
3878 const float w_item_one
= ImMax(1.0f
, (float)(int)((w_items_all
- (style
.ItemInnerSpacing
.x
) * (components
-1)) / (float)components
));
3879 const float w_item_last
= ImMax(1.0f
, (float)(int)(w_items_all
- (w_item_one
+ style
.ItemInnerSpacing
.x
) * (components
-1)));
3881 const bool hide_prefix
= (w_item_one
<= CalcTextSize((flags
& ImGuiColorEditFlags_Float
) ? "M:0.000" : "M:000").x
);
3882 const char* ids
[4] = { "##X", "##Y", "##Z", "##W" };
3883 const char* fmt_table_int
[3][4] =
3885 { "%3d", "%3d", "%3d", "%3d" }, // Short display
3886 { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
3887 { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
3889 const char* fmt_table_float
[3][4] =
3891 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
3892 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
3893 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
3895 const int fmt_idx
= hide_prefix
? 0 : (flags
& ImGuiColorEditFlags_HSV
) ? 2 : 1;
3897 PushItemWidth(w_item_one
);
3898 for (int n
= 0; n
< components
; n
++)
3901 SameLine(0, style
.ItemInnerSpacing
.x
);
3902 if (n
+ 1 == components
)
3903 PushItemWidth(w_item_last
);
3904 if (flags
& ImGuiColorEditFlags_Float
)
3906 value_changed
|= DragFloat(ids
[n
], &f
[n
], 1.0f
/255.0f
, 0.0f
, hdr
? 0.0f
: 1.0f
, fmt_table_float
[fmt_idx
][n
]);
3907 value_changed_as_float
|= value_changed
;
3911 value_changed
|= DragInt(ids
[n
], &i
[n
], 1.0f
, 0, hdr
? 0 : 255, fmt_table_int
[fmt_idx
][n
]);
3913 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
3914 OpenPopupOnItemClick("context");
3919 else if ((flags
& ImGuiColorEditFlags_HEX
) != 0 && (flags
& ImGuiColorEditFlags_NoInputs
) == 0)
3921 // RGB Hexadecimal Input
3924 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));
3926 ImFormatString(buf
, IM_ARRAYSIZE(buf
), "#%02X%02X%02X", ImClamp(i
[0],0,255), ImClamp(i
[1],0,255), ImClamp(i
[2],0,255));
3927 PushItemWidth(w_items_all
);
3928 if (InputText("##Text", buf
, IM_ARRAYSIZE(buf
), ImGuiInputTextFlags_CharsHexadecimal
| ImGuiInputTextFlags_CharsUppercase
))
3930 value_changed
= true;
3932 while (*p
== '#' || ImCharIsBlankA(*p
))
3934 i
[0] = i
[1] = i
[2] = i
[3] = 0;
3936 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)
3938 sscanf(p
, "%02X%02X%02X", (unsigned int*)&i
[0], (unsigned int*)&i
[1], (unsigned int*)&i
[2]);
3940 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
3941 OpenPopupOnItemClick("context");
3945 ImGuiWindow
* picker_active_window
= NULL
;
3946 if (!(flags
& ImGuiColorEditFlags_NoSmallPreview
))
3948 if (!(flags
& ImGuiColorEditFlags_NoInputs
))
3949 SameLine(0, style
.ItemInnerSpacing
.x
);
3951 const ImVec4
col_v4(col
[0], col
[1], col
[2], alpha
? col
[3] : 1.0f
);
3952 if (ColorButton("##ColorButton", col_v4
, flags
))
3954 if (!(flags
& ImGuiColorEditFlags_NoPicker
))
3956 // Store current color and open a picker
3957 g
.ColorPickerRef
= col_v4
;
3958 OpenPopup("picker");
3959 SetNextWindowPos(window
->DC
.LastItemRect
.GetBL() + ImVec2(-1,style
.ItemSpacing
.y
));
3962 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
3963 OpenPopupOnItemClick("context");
3965 if (BeginPopup("picker"))
3967 picker_active_window
= g
.CurrentWindow
;
3968 if (label
!= label_display_end
)
3970 TextUnformatted(label
, label_display_end
);
3973 ImGuiColorEditFlags picker_flags_to_forward
= ImGuiColorEditFlags__DataTypeMask
| ImGuiColorEditFlags__PickerMask
| ImGuiColorEditFlags_HDR
| ImGuiColorEditFlags_NoAlpha
| ImGuiColorEditFlags_AlphaBar
;
3974 ImGuiColorEditFlags picker_flags
= (flags_untouched
& picker_flags_to_forward
) | ImGuiColorEditFlags__InputsMask
| ImGuiColorEditFlags_NoLabel
| ImGuiColorEditFlags_AlphaPreviewHalf
;
3975 PushItemWidth(square_sz
* 12.0f
); // Use 256 + bar sizes?
3976 value_changed
|= ColorPicker4("##picker", col
, picker_flags
, &g
.ColorPickerRef
.x
);
3982 if (label
!= label_display_end
&& !(flags
& ImGuiColorEditFlags_NoLabel
))
3984 SameLine(0, style
.ItemInnerSpacing
.x
);
3985 TextUnformatted(label
, label_display_end
);
3989 if (picker_active_window
== NULL
)
3991 if (!value_changed_as_float
)
3992 for (int n
= 0; n
< 4; n
++)
3993 f
[n
] = i
[n
] / 255.0f
;
3994 if (flags
& ImGuiColorEditFlags_HSV
)
3995 ColorConvertHSVtoRGB(f
[0], f
[1], f
[2], f
[0], f
[1], f
[2]);
4009 // Drag and Drop Target
4010 // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
4011 if ((window
->DC
.LastItemStatusFlags
& ImGuiItemStatusFlags_HoveredRect
) && !(flags
& ImGuiColorEditFlags_NoDragDrop
) && BeginDragDropTarget())
4013 if (const ImGuiPayload
* payload
= AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F
))
4015 memcpy((float*)col
, payload
->Data
, sizeof(float) * 3); // Preserve alpha if any //-V512
4016 value_changed
= true;
4018 if (const ImGuiPayload
* payload
= AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F
))
4020 memcpy((float*)col
, payload
->Data
, sizeof(float) * components
);
4021 value_changed
= true;
4023 EndDragDropTarget();
4026 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
4027 if (picker_active_window
&& g
.ActiveId
!= 0 && g
.ActiveIdWindow
== picker_active_window
)
4028 window
->DC
.LastItemId
= g
.ActiveId
;
4031 MarkItemEdited(window
->DC
.LastItemId
);
4033 return value_changed
;
4036 bool ImGui::ColorPicker3(const char* label
, float col
[3], ImGuiColorEditFlags flags
)
4038 float col4
[4] = { col
[0], col
[1], col
[2], 1.0f
};
4039 if (!ColorPicker4(label
, col4
, flags
| ImGuiColorEditFlags_NoAlpha
))
4041 col
[0] = col4
[0]; col
[1] = col4
[1]; col
[2] = col4
[2];
4045 static inline ImU32
ImAlphaBlendColor(ImU32 col_a
, ImU32 col_b
)
4047 float t
= ((col_b
>> IM_COL32_A_SHIFT
) & 0xFF) / 255.f
;
4048 int r
= ImLerp((int)(col_a
>> IM_COL32_R_SHIFT
) & 0xFF, (int)(col_b
>> IM_COL32_R_SHIFT
) & 0xFF, t
);
4049 int g
= ImLerp((int)(col_a
>> IM_COL32_G_SHIFT
) & 0xFF, (int)(col_b
>> IM_COL32_G_SHIFT
) & 0xFF, t
);
4050 int b
= ImLerp((int)(col_a
>> IM_COL32_B_SHIFT
) & 0xFF, (int)(col_b
>> IM_COL32_B_SHIFT
) & 0xFF, t
);
4051 return IM_COL32(r
, g
, b
, 0xFF);
4054 // Helper for ColorPicker4()
4055 // 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.
4056 // 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.
4057 void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min
, ImVec2 p_max
, ImU32 col
, float grid_step
, ImVec2 grid_off
, float rounding
, int rounding_corners_flags
)
4059 ImGuiWindow
* window
= GetCurrentWindow();
4060 if (((col
& IM_COL32_A_MASK
) >> IM_COL32_A_SHIFT
) < 0xFF)
4062 ImU32 col_bg1
= GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col
));
4063 ImU32 col_bg2
= GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col
));
4064 window
->DrawList
->AddRectFilled(p_min
, p_max
, col_bg1
, rounding
, rounding_corners_flags
);
4067 for (float y
= p_min
.y
+ grid_off
.y
; y
< p_max
.y
; y
+= grid_step
, yi
++)
4069 float y1
= ImClamp(y
, p_min
.y
, p_max
.y
), y2
= ImMin(y
+ grid_step
, p_max
.y
);
4072 for (float x
= p_min
.x
+ grid_off
.x
+ (yi
& 1) * grid_step
; x
< p_max
.x
; x
+= grid_step
* 2.0f
)
4074 float x1
= ImClamp(x
, p_min
.x
, p_max
.x
), x2
= ImMin(x
+ grid_step
, p_max
.x
);
4077 int rounding_corners_flags_cell
= 0;
4078 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
; }
4079 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
; }
4080 rounding_corners_flags_cell
&= rounding_corners_flags
;
4081 window
->DrawList
->AddRectFilled(ImVec2(x1
,y1
), ImVec2(x2
,y2
), col_bg2
, rounding_corners_flags_cell
? rounding
: 0.0f
, rounding_corners_flags_cell
);
4087 window
->DrawList
->AddRectFilled(p_min
, p_max
, col
, rounding
, rounding_corners_flags
);
4091 // Helper for ColorPicker4()
4092 static void RenderArrowsForVerticalBar(ImDrawList
* draw_list
, ImVec2 pos
, ImVec2 half_sz
, float bar_w
)
4094 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
);
4095 ImGui::RenderArrowPointingAt(draw_list
, ImVec2(pos
.x
+ half_sz
.x
, pos
.y
), half_sz
, ImGuiDir_Right
, IM_COL32_WHITE
);
4096 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
);
4097 ImGui::RenderArrowPointingAt(draw_list
, ImVec2(pos
.x
+ bar_w
- half_sz
.x
, pos
.y
), half_sz
, ImGuiDir_Left
, IM_COL32_WHITE
);
4100 // Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4101 // 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..)
4102 bool ImGui::ColorPicker4(const char* label
, float col
[4], ImGuiColorEditFlags flags
, const float* ref_col
)
4104 ImGuiContext
& g
= *GImGui
;
4105 ImGuiWindow
* window
= GetCurrentWindow();
4106 ImDrawList
* draw_list
= window
->DrawList
;
4108 ImGuiStyle
& style
= g
.Style
;
4114 if (!(flags
& ImGuiColorEditFlags_NoSidePreview
))
4115 flags
|= ImGuiColorEditFlags_NoSmallPreview
;
4117 // Context menu: display and store options.
4118 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
4119 ColorPickerOptionsPopup(col
, flags
);
4121 // Read stored options
4122 if (!(flags
& ImGuiColorEditFlags__PickerMask
))
4123 flags
|= ((g
.ColorEditOptions
& ImGuiColorEditFlags__PickerMask
) ? g
.ColorEditOptions
: ImGuiColorEditFlags__OptionsDefault
) & ImGuiColorEditFlags__PickerMask
;
4124 IM_ASSERT(ImIsPowerOfTwo((int)(flags
& ImGuiColorEditFlags__PickerMask
))); // Check that only 1 is selected
4125 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
4126 flags
|= (g
.ColorEditOptions
& ImGuiColorEditFlags_AlphaBar
);
4129 int components
= (flags
& ImGuiColorEditFlags_NoAlpha
) ? 3 : 4;
4130 bool alpha_bar
= (flags
& ImGuiColorEditFlags_AlphaBar
) && !(flags
& ImGuiColorEditFlags_NoAlpha
);
4131 ImVec2 picker_pos
= window
->DC
.CursorPos
;
4132 float square_sz
= GetFrameHeight();
4133 float bars_width
= square_sz
; // Arbitrary smallish width of Hue/Alpha picking bars
4134 float sv_picker_size
= ImMax(bars_width
* 1, CalcItemWidth() - (alpha_bar
? 2 : 1) * (bars_width
+ style
.ItemInnerSpacing
.x
)); // Saturation/Value picking box
4135 float bar0_pos_x
= picker_pos
.x
+ sv_picker_size
+ style
.ItemInnerSpacing
.x
;
4136 float bar1_pos_x
= bar0_pos_x
+ bars_width
+ style
.ItemInnerSpacing
.x
;
4137 float bars_triangles_half_sz
= (float)(int)(bars_width
* 0.20f
);
4139 float backup_initial_col
[4];
4140 memcpy(backup_initial_col
, col
, components
* sizeof(float));
4142 float wheel_thickness
= sv_picker_size
* 0.08f
;
4143 float wheel_r_outer
= sv_picker_size
* 0.50f
;
4144 float wheel_r_inner
= wheel_r_outer
- wheel_thickness
;
4145 ImVec2
wheel_center(picker_pos
.x
+ (sv_picker_size
+ bars_width
)*0.5f
, picker_pos
.y
+ sv_picker_size
*0.5f
);
4147 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
4148 float triangle_r
= wheel_r_inner
- (int)(sv_picker_size
* 0.027f
);
4149 ImVec2 triangle_pa
= ImVec2(triangle_r
, 0.0f
); // Hue point.
4150 ImVec2 triangle_pb
= ImVec2(triangle_r
* -0.5f
, triangle_r
* -0.866025f
); // Black point.
4151 ImVec2 triangle_pc
= ImVec2(triangle_r
* -0.5f
, triangle_r
* +0.866025f
); // White point.
4154 ColorConvertRGBtoHSV(col
[0], col
[1], col
[2], H
, S
, V
);
4156 bool value_changed
= false, value_changed_h
= false, value_changed_sv
= false;
4158 PushItemFlag(ImGuiItemFlags_NoNav
, true);
4159 if (flags
& ImGuiColorEditFlags_PickerHueWheel
)
4161 // Hue wheel + SV triangle logic
4162 InvisibleButton("hsv", ImVec2(sv_picker_size
+ style
.ItemInnerSpacing
.x
+ bars_width
, sv_picker_size
));
4165 ImVec2 initial_off
= g
.IO
.MouseClickedPos
[0] - wheel_center
;
4166 ImVec2 current_off
= g
.IO
.MousePos
- wheel_center
;
4167 float initial_dist2
= ImLengthSqr(initial_off
);
4168 if (initial_dist2
>= (wheel_r_inner
-1)*(wheel_r_inner
-1) && initial_dist2
<= (wheel_r_outer
+1)*(wheel_r_outer
+1))
4170 // Interactive with Hue wheel
4171 H
= ImAtan2(current_off
.y
, current_off
.x
) / IM_PI
*0.5f
;
4174 value_changed
= value_changed_h
= true;
4176 float cos_hue_angle
= ImCos(-H
* 2.0f
* IM_PI
);
4177 float sin_hue_angle
= ImSin(-H
* 2.0f
* IM_PI
);
4178 if (ImTriangleContainsPoint(triangle_pa
, triangle_pb
, triangle_pc
, ImRotate(initial_off
, cos_hue_angle
, sin_hue_angle
)))
4180 // Interacting with SV triangle
4181 ImVec2 current_off_unrotated
= ImRotate(current_off
, cos_hue_angle
, sin_hue_angle
);
4182 if (!ImTriangleContainsPoint(triangle_pa
, triangle_pb
, triangle_pc
, current_off_unrotated
))
4183 current_off_unrotated
= ImTriangleClosestPoint(triangle_pa
, triangle_pb
, triangle_pc
, current_off_unrotated
);
4185 ImTriangleBarycentricCoords(triangle_pa
, triangle_pb
, triangle_pc
, current_off_unrotated
, uu
, vv
, ww
);
4186 V
= ImClamp(1.0f
- vv
, 0.0001f
, 1.0f
);
4187 S
= ImClamp(uu
/ V
, 0.0001f
, 1.0f
);
4188 value_changed
= value_changed_sv
= true;
4191 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
4192 OpenPopupOnItemClick("context");
4194 else if (flags
& ImGuiColorEditFlags_PickerHueBar
)
4196 // SV rectangle logic
4197 InvisibleButton("sv", ImVec2(sv_picker_size
, sv_picker_size
));
4200 S
= ImSaturate((io
.MousePos
.x
- picker_pos
.x
) / (sv_picker_size
-1));
4201 V
= 1.0f
- ImSaturate((io
.MousePos
.y
- picker_pos
.y
) / (sv_picker_size
-1));
4202 value_changed
= value_changed_sv
= true;
4204 if (!(flags
& ImGuiColorEditFlags_NoOptions
))
4205 OpenPopupOnItemClick("context");
4208 SetCursorScreenPos(ImVec2(bar0_pos_x
, picker_pos
.y
));
4209 InvisibleButton("hue", ImVec2(bars_width
, sv_picker_size
));
4212 H
= ImSaturate((io
.MousePos
.y
- picker_pos
.y
) / (sv_picker_size
-1));
4213 value_changed
= value_changed_h
= true;
4220 SetCursorScreenPos(ImVec2(bar1_pos_x
, picker_pos
.y
));
4221 InvisibleButton("alpha", ImVec2(bars_width
, sv_picker_size
));
4224 col
[3] = 1.0f
- ImSaturate((io
.MousePos
.y
- picker_pos
.y
) / (sv_picker_size
-1));
4225 value_changed
= true;
4228 PopItemFlag(); // ImGuiItemFlags_NoNav
4230 if (!(flags
& ImGuiColorEditFlags_NoSidePreview
))
4232 SameLine(0, style
.ItemInnerSpacing
.x
);
4236 if (!(flags
& ImGuiColorEditFlags_NoLabel
))
4238 const char* label_display_end
= FindRenderedTextEnd(label
);
4239 if (label
!= label_display_end
)
4241 if ((flags
& ImGuiColorEditFlags_NoSidePreview
))
4242 SameLine(0, style
.ItemInnerSpacing
.x
);
4243 TextUnformatted(label
, label_display_end
);
4247 if (!(flags
& ImGuiColorEditFlags_NoSidePreview
))
4249 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus
, true);
4250 ImVec4
col_v4(col
[0], col
[1], col
[2], (flags
& ImGuiColorEditFlags_NoAlpha
) ? 1.0f
: col
[3]);
4251 if ((flags
& ImGuiColorEditFlags_NoLabel
))
4253 ColorButton("##current", col_v4
, (flags
& (ImGuiColorEditFlags_HDR
|ImGuiColorEditFlags_AlphaPreview
|ImGuiColorEditFlags_AlphaPreviewHalf
|ImGuiColorEditFlags_NoTooltip
)), ImVec2(square_sz
* 3, square_sz
* 2));
4254 if (ref_col
!= NULL
)
4257 ImVec4
ref_col_v4(ref_col
[0], ref_col
[1], ref_col
[2], (flags
& ImGuiColorEditFlags_NoAlpha
) ? 1.0f
: ref_col
[3]);
4258 if (ColorButton("##original", ref_col_v4
, (flags
& (ImGuiColorEditFlags_HDR
|ImGuiColorEditFlags_AlphaPreview
|ImGuiColorEditFlags_AlphaPreviewHalf
|ImGuiColorEditFlags_NoTooltip
)), ImVec2(square_sz
* 3, square_sz
* 2)))
4260 memcpy(col
, ref_col
, components
* sizeof(float));
4261 value_changed
= true;
4268 // Convert back color to RGB
4269 if (value_changed_h
|| value_changed_sv
)
4270 ColorConvertHSVtoRGB(H
>= 1.0f
? H
- 10 * 1e-6f
: H
, S
> 0.0f
? S
: 10*1e-6f
, V
> 0.0f
? V
: 1e-6f
, col
[0], col
[1], col
[2]);
4272 // R,G,B and H,S,V slider color editor
4273 bool value_changed_fix_hue_wrap
= false;
4274 if ((flags
& ImGuiColorEditFlags_NoInputs
) == 0)
4276 PushItemWidth((alpha_bar
? bar1_pos_x
: bar0_pos_x
) + bars_width
- picker_pos
.x
);
4277 ImGuiColorEditFlags sub_flags_to_forward
= ImGuiColorEditFlags__DataTypeMask
| ImGuiColorEditFlags_HDR
| ImGuiColorEditFlags_NoAlpha
| ImGuiColorEditFlags_NoOptions
| ImGuiColorEditFlags_NoSmallPreview
| ImGuiColorEditFlags_AlphaPreview
| ImGuiColorEditFlags_AlphaPreviewHalf
;
4278 ImGuiColorEditFlags sub_flags
= (flags
& sub_flags_to_forward
) | ImGuiColorEditFlags_NoPicker
;
4279 if (flags
& ImGuiColorEditFlags_RGB
|| (flags
& ImGuiColorEditFlags__InputsMask
) == 0)
4280 if (ColorEdit4("##rgb", col
, sub_flags
| ImGuiColorEditFlags_RGB
))
4282 // FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
4283 // 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)
4284 value_changed_fix_hue_wrap
= (g
.ActiveId
!= 0 && !g
.ActiveIdAllowOverlap
);
4285 value_changed
= true;
4287 if (flags
& ImGuiColorEditFlags_HSV
|| (flags
& ImGuiColorEditFlags__InputsMask
) == 0)
4288 value_changed
|= ColorEdit4("##hsv", col
, sub_flags
| ImGuiColorEditFlags_HSV
);
4289 if (flags
& ImGuiColorEditFlags_HEX
|| (flags
& ImGuiColorEditFlags__InputsMask
) == 0)
4290 value_changed
|= ColorEdit4("##hex", col
, sub_flags
| ImGuiColorEditFlags_HEX
);
4294 // Try to cancel hue wrap (after ColorEdit4 call), if any
4295 if (value_changed_fix_hue_wrap
)
4297 float new_H
, new_S
, new_V
;
4298 ColorConvertRGBtoHSV(col
[0], col
[1], col
[2], new_H
, new_S
, new_V
);
4299 if (new_H
<= 0 && H
> 0)
4301 if (new_V
<= 0 && V
!= new_V
)
4302 ColorConvertHSVtoRGB(H
, S
, new_V
<= 0 ? V
* 0.5f
: new_V
, col
[0], col
[1], col
[2]);
4303 else if (new_S
<= 0)
4304 ColorConvertHSVtoRGB(H
, new_S
<= 0 ? S
* 0.5f
: new_S
, new_V
, col
[0], col
[1], col
[2]);
4308 ImVec4
hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H
, 1, 1, hue_color_f
.x
, hue_color_f
.y
, hue_color_f
.z
);
4309 ImU32 hue_color32
= ColorConvertFloat4ToU32(hue_color_f
);
4310 ImU32 col32_no_alpha
= ColorConvertFloat4ToU32(ImVec4(col
[0], col
[1], col
[2], 1.0f
));
4312 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) };
4313 ImVec2 sv_cursor_pos
;
4315 if (flags
& ImGuiColorEditFlags_PickerHueWheel
)
4318 const float aeps
= 1.5f
/ wheel_r_outer
; // Half a pixel arc length in radians (2pi cancels out).
4319 const int segment_per_arc
= ImMax(4, (int)wheel_r_outer
/ 12);
4320 for (int n
= 0; n
< 6; n
++)
4322 const float a0
= (n
) /6.0f
* 2.0f
* IM_PI
- aeps
;
4323 const float a1
= (n
+1.0f
)/6.0f
* 2.0f
* IM_PI
+ aeps
;
4324 const int vert_start_idx
= draw_list
->VtxBuffer
.Size
;
4325 draw_list
->PathArcTo(wheel_center
, (wheel_r_inner
+ wheel_r_outer
)*0.5f
, a0
, a1
, segment_per_arc
);
4326 draw_list
->PathStroke(IM_COL32_WHITE
, false, wheel_thickness
);
4327 const int vert_end_idx
= draw_list
->VtxBuffer
.Size
;
4329 // Paint colors over existing vertices
4330 ImVec2
gradient_p0(wheel_center
.x
+ ImCos(a0
) * wheel_r_inner
, wheel_center
.y
+ ImSin(a0
) * wheel_r_inner
);
4331 ImVec2
gradient_p1(wheel_center
.x
+ ImCos(a1
) * wheel_r_inner
, wheel_center
.y
+ ImSin(a1
) * wheel_r_inner
);
4332 ShadeVertsLinearColorGradientKeepAlpha(draw_list
, vert_start_idx
, vert_end_idx
, gradient_p0
, gradient_p1
, hue_colors
[n
], hue_colors
[n
+1]);
4335 // Render Cursor + preview on Hue Wheel
4336 float cos_hue_angle
= ImCos(H
* 2.0f
* IM_PI
);
4337 float sin_hue_angle
= ImSin(H
* 2.0f
* IM_PI
);
4338 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
);
4339 float hue_cursor_rad
= value_changed_h
? wheel_thickness
* 0.65f
: wheel_thickness
* 0.55f
;
4340 int hue_cursor_segments
= ImClamp((int)(hue_cursor_rad
/ 1.4f
), 9, 32);
4341 draw_list
->AddCircleFilled(hue_cursor_pos
, hue_cursor_rad
, hue_color32
, hue_cursor_segments
);
4342 draw_list
->AddCircle(hue_cursor_pos
, hue_cursor_rad
+1, IM_COL32(128,128,128,255), hue_cursor_segments
);
4343 draw_list
->AddCircle(hue_cursor_pos
, hue_cursor_rad
, IM_COL32_WHITE
, hue_cursor_segments
);
4345 // Render SV triangle (rotated according to hue)
4346 ImVec2 tra
= wheel_center
+ ImRotate(triangle_pa
, cos_hue_angle
, sin_hue_angle
);
4347 ImVec2 trb
= wheel_center
+ ImRotate(triangle_pb
, cos_hue_angle
, sin_hue_angle
);
4348 ImVec2 trc
= wheel_center
+ ImRotate(triangle_pc
, cos_hue_angle
, sin_hue_angle
);
4349 ImVec2 uv_white
= GetFontTexUvWhitePixel();
4350 draw_list
->PrimReserve(6, 6);
4351 draw_list
->PrimVtx(tra
, uv_white
, hue_color32
);
4352 draw_list
->PrimVtx(trb
, uv_white
, hue_color32
);
4353 draw_list
->PrimVtx(trc
, uv_white
, IM_COL32_WHITE
);
4354 draw_list
->PrimVtx(tra
, uv_white
, IM_COL32_BLACK_TRANS
);
4355 draw_list
->PrimVtx(trb
, uv_white
, IM_COL32_BLACK
);
4356 draw_list
->PrimVtx(trc
, uv_white
, IM_COL32_BLACK_TRANS
);
4357 draw_list
->AddTriangle(tra
, trb
, trc
, IM_COL32(128,128,128,255), 1.5f
);
4358 sv_cursor_pos
= ImLerp(ImLerp(trc
, tra
, ImSaturate(S
)), trb
, ImSaturate(1 - V
));
4360 else if (flags
& ImGuiColorEditFlags_PickerHueBar
)
4363 draw_list
->AddRectFilledMultiColor(picker_pos
, picker_pos
+ ImVec2(sv_picker_size
,sv_picker_size
), IM_COL32_WHITE
, hue_color32
, hue_color32
, IM_COL32_WHITE
);
4364 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
);
4365 RenderFrameBorder(picker_pos
, picker_pos
+ ImVec2(sv_picker_size
,sv_picker_size
), 0.0f
);
4366 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
4367 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);
4370 for (int i
= 0; i
< 6; ++i
)
4371 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]);
4372 float bar0_line_y
= (float)(int)(picker_pos
.y
+ H
* sv_picker_size
+ 0.5f
);
4373 RenderFrameBorder(ImVec2(bar0_pos_x
, picker_pos
.y
), ImVec2(bar0_pos_x
+ bars_width
, picker_pos
.y
+ sv_picker_size
), 0.0f
);
4374 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
);
4377 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
4378 float sv_cursor_rad
= value_changed_sv
? 10.0f
: 6.0f
;
4379 draw_list
->AddCircleFilled(sv_cursor_pos
, sv_cursor_rad
, col32_no_alpha
, 12);
4380 draw_list
->AddCircle(sv_cursor_pos
, sv_cursor_rad
+1, IM_COL32(128,128,128,255), 12);
4381 draw_list
->AddCircle(sv_cursor_pos
, sv_cursor_rad
, IM_COL32_WHITE
, 12);
4386 float alpha
= ImSaturate(col
[3]);
4387 ImRect
bar1_bb(bar1_pos_x
, picker_pos
.y
, bar1_pos_x
+ bars_width
, picker_pos
.y
+ sv_picker_size
);
4388 RenderColorRectWithAlphaCheckerboard(bar1_bb
.Min
, bar1_bb
.Max
, IM_COL32(0,0,0,0), bar1_bb
.GetWidth() / 2.0f
, ImVec2(0.0f
, 0.0f
));
4389 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
);
4390 float bar1_line_y
= (float)(int)(picker_pos
.y
+ (1.0f
- alpha
) * sv_picker_size
+ 0.5f
);
4391 RenderFrameBorder(bar1_bb
.Min
, bar1_bb
.Max
, 0.0f
);
4392 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
);
4397 if (value_changed
&& memcmp(backup_initial_col
, col
, components
* sizeof(float)) == 0)
4398 value_changed
= false;
4400 MarkItemEdited(window
->DC
.LastItemId
);
4404 return value_changed
;
4407 // A little colored square. Return true when clicked.
4408 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
4409 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
4410 bool ImGui::ColorButton(const char* desc_id
, const ImVec4
& col
, ImGuiColorEditFlags flags
, ImVec2 size
)
4412 ImGuiWindow
* window
= GetCurrentWindow();
4413 if (window
->SkipItems
)
4416 ImGuiContext
& g
= *GImGui
;
4417 const ImGuiID id
= window
->GetID(desc_id
);
4418 float default_size
= GetFrameHeight();
4420 size
.x
= default_size
;
4422 size
.y
= default_size
;
4423 const ImRect
bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ size
);
4424 ItemSize(bb
, (size
.y
>= default_size
) ? g
.Style
.FramePadding
.y
: 0.0f
);
4425 if (!ItemAdd(bb
, id
))
4429 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
);
4431 if (flags
& ImGuiColorEditFlags_NoAlpha
)
4432 flags
&= ~(ImGuiColorEditFlags_AlphaPreview
| ImGuiColorEditFlags_AlphaPreviewHalf
);
4434 ImVec4
col_without_alpha(col
.x
, col
.y
, col
.z
, 1.0f
);
4435 float grid_step
= ImMin(size
.x
, size
.y
) / 2.99f
;
4436 float rounding
= ImMin(g
.Style
.FrameRounding
, grid_step
* 0.5f
);
4437 ImRect bb_inner
= bb
;
4438 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.
4439 bb_inner
.Expand(off
);
4440 if ((flags
& ImGuiColorEditFlags_AlphaPreviewHalf
) && col
.w
< 1.0f
)
4442 float mid_x
= (float)(int)((bb_inner
.Min
.x
+ bb_inner
.Max
.x
) * 0.5f
+ 0.5f
);
4443 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
);
4444 window
->DrawList
->AddRectFilled(bb_inner
.Min
, ImVec2(mid_x
, bb_inner
.Max
.y
), GetColorU32(col_without_alpha
), rounding
, ImDrawCornerFlags_TopLeft
|ImDrawCornerFlags_BotLeft
);
4448 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
4449 ImVec4 col_source
= (flags
& ImGuiColorEditFlags_AlphaPreview
) ? col
: col_without_alpha
;
4450 if (col_source
.w
< 1.0f
)
4451 RenderColorRectWithAlphaCheckerboard(bb_inner
.Min
, bb_inner
.Max
, GetColorU32(col_source
), grid_step
, ImVec2(off
, off
), rounding
);
4453 window
->DrawList
->AddRectFilled(bb_inner
.Min
, bb_inner
.Max
, GetColorU32(col_source
), rounding
, ImDrawCornerFlags_All
);
4455 RenderNavHighlight(bb
, id
);
4456 if (g
.Style
.FrameBorderSize
> 0.0f
)
4457 RenderFrameBorder(bb
.Min
, bb
.Max
, rounding
);
4459 window
->DrawList
->AddRect(bb
.Min
, bb
.Max
, GetColorU32(ImGuiCol_FrameBg
), rounding
); // Color button are often in need of some sort of border
4461 // Drag and Drop Source
4462 // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
4463 if (g
.ActiveId
== id
&& !(flags
& ImGuiColorEditFlags_NoDragDrop
) && BeginDragDropSource())
4465 if (flags
& ImGuiColorEditFlags_NoAlpha
)
4466 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F
, &col
, sizeof(float) * 3, ImGuiCond_Once
);
4468 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F
, &col
, sizeof(float) * 4, ImGuiCond_Once
);
4469 ColorButton(desc_id
, col
, flags
);
4471 TextUnformatted("Color");
4472 EndDragDropSource();
4476 if (!(flags
& ImGuiColorEditFlags_NoTooltip
) && hovered
)
4477 ColorTooltip(desc_id
, &col
.x
, flags
& (ImGuiColorEditFlags_NoAlpha
| ImGuiColorEditFlags_AlphaPreview
| ImGuiColorEditFlags_AlphaPreviewHalf
));
4485 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags
)
4487 ImGuiContext
& g
= *GImGui
;
4488 if ((flags
& ImGuiColorEditFlags__InputsMask
) == 0)
4489 flags
|= ImGuiColorEditFlags__OptionsDefault
& ImGuiColorEditFlags__InputsMask
;
4490 if ((flags
& ImGuiColorEditFlags__DataTypeMask
) == 0)
4491 flags
|= ImGuiColorEditFlags__OptionsDefault
& ImGuiColorEditFlags__DataTypeMask
;
4492 if ((flags
& ImGuiColorEditFlags__PickerMask
) == 0)
4493 flags
|= ImGuiColorEditFlags__OptionsDefault
& ImGuiColorEditFlags__PickerMask
;
4494 IM_ASSERT(ImIsPowerOfTwo((int)(flags
& ImGuiColorEditFlags__InputsMask
))); // Check only 1 option is selected
4495 IM_ASSERT(ImIsPowerOfTwo((int)(flags
& ImGuiColorEditFlags__DataTypeMask
))); // Check only 1 option is selected
4496 IM_ASSERT(ImIsPowerOfTwo((int)(flags
& ImGuiColorEditFlags__PickerMask
))); // Check only 1 option is selected
4497 g
.ColorEditOptions
= flags
;
4500 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4501 void ImGui::ColorTooltip(const char* text
, const float* col
, ImGuiColorEditFlags flags
)
4503 ImGuiContext
& g
= *GImGui
;
4505 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]);
4506 BeginTooltipEx(0, true);
4508 const char* text_end
= text
? FindRenderedTextEnd(text
, NULL
) : text
;
4509 if (text_end
> text
)
4511 TextUnformatted(text
, text_end
);
4515 ImVec2
sz(g
.FontSize
* 3 + g
.Style
.FramePadding
.y
* 2, g
.FontSize
* 3 + g
.Style
.FramePadding
.y
* 2);
4516 ColorButton("##preview", ImVec4(col
[0], col
[1], col
[2], col
[3]), (flags
& (ImGuiColorEditFlags_NoAlpha
| ImGuiColorEditFlags_AlphaPreview
| ImGuiColorEditFlags_AlphaPreviewHalf
)) | ImGuiColorEditFlags_NoTooltip
, sz
);
4518 if (flags
& ImGuiColorEditFlags_NoAlpha
)
4519 Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr
, cg
, cb
, cr
, cg
, cb
, col
[0], col
[1], col
[2]);
4521 Text("#%02X%02X%02X%02X\nR:%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]);
4525 void ImGui::ColorEditOptionsPopup(const float* col
, ImGuiColorEditFlags flags
)
4527 bool allow_opt_inputs
= !(flags
& ImGuiColorEditFlags__InputsMask
);
4528 bool allow_opt_datatype
= !(flags
& ImGuiColorEditFlags__DataTypeMask
);
4529 if ((!allow_opt_inputs
&& !allow_opt_datatype
) || !BeginPopup("context"))
4531 ImGuiContext
& g
= *GImGui
;
4532 ImGuiColorEditFlags opts
= g
.ColorEditOptions
;
4533 if (allow_opt_inputs
)
4535 if (RadioButton("RGB", (opts
& ImGuiColorEditFlags_RGB
) != 0)) opts
= (opts
& ~ImGuiColorEditFlags__InputsMask
) | ImGuiColorEditFlags_RGB
;
4536 if (RadioButton("HSV", (opts
& ImGuiColorEditFlags_HSV
) != 0)) opts
= (opts
& ~ImGuiColorEditFlags__InputsMask
) | ImGuiColorEditFlags_HSV
;
4537 if (RadioButton("HEX", (opts
& ImGuiColorEditFlags_HEX
) != 0)) opts
= (opts
& ~ImGuiColorEditFlags__InputsMask
) | ImGuiColorEditFlags_HEX
;
4539 if (allow_opt_datatype
)
4541 if (allow_opt_inputs
) Separator();
4542 if (RadioButton("0..255", (opts
& ImGuiColorEditFlags_Uint8
) != 0)) opts
= (opts
& ~ImGuiColorEditFlags__DataTypeMask
) | ImGuiColorEditFlags_Uint8
;
4543 if (RadioButton("0.00..1.00", (opts
& ImGuiColorEditFlags_Float
) != 0)) opts
= (opts
& ~ImGuiColorEditFlags__DataTypeMask
) | ImGuiColorEditFlags_Float
;
4546 if (allow_opt_inputs
|| allow_opt_datatype
)
4548 if (Button("Copy as..", ImVec2(-1,0)))
4550 if (BeginPopup("Copy"))
4552 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]);
4554 ImFormatString(buf
, IM_ARRAYSIZE(buf
), "(%.3ff, %.3ff, %.3ff, %.3ff)", col
[0], col
[1], col
[2], (flags
& ImGuiColorEditFlags_NoAlpha
) ? 1.0f
: col
[3]);
4555 if (Selectable(buf
))
4556 SetClipboardText(buf
);
4557 ImFormatString(buf
, IM_ARRAYSIZE(buf
), "(%d,%d,%d,%d)", cr
, cg
, cb
, ca
);
4558 if (Selectable(buf
))
4559 SetClipboardText(buf
);
4560 if (flags
& ImGuiColorEditFlags_NoAlpha
)
4561 ImFormatString(buf
, IM_ARRAYSIZE(buf
), "0x%02X%02X%02X", cr
, cg
, cb
);
4563 ImFormatString(buf
, IM_ARRAYSIZE(buf
), "0x%02X%02X%02X%02X", cr
, cg
, cb
, ca
);
4564 if (Selectable(buf
))
4565 SetClipboardText(buf
);
4569 g
.ColorEditOptions
= opts
;
4573 void ImGui::ColorPickerOptionsPopup(const float* ref_col
, ImGuiColorEditFlags flags
)
4575 bool allow_opt_picker
= !(flags
& ImGuiColorEditFlags__PickerMask
);
4576 bool allow_opt_alpha_bar
= !(flags
& ImGuiColorEditFlags_NoAlpha
) && !(flags
& ImGuiColorEditFlags_AlphaBar
);
4577 if ((!allow_opt_picker
&& !allow_opt_alpha_bar
) || !BeginPopup("context"))
4579 ImGuiContext
& g
= *GImGui
;
4580 if (allow_opt_picker
)
4582 ImVec2
picker_size(g
.FontSize
* 8, ImMax(g
.FontSize
* 8 - (GetFrameHeight() + g
.Style
.ItemInnerSpacing
.x
), 1.0f
)); // FIXME: Picker size copied from main picker function
4583 PushItemWidth(picker_size
.x
);
4584 for (int picker_type
= 0; picker_type
< 2; picker_type
++)
4586 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
4587 if (picker_type
> 0) Separator();
4588 PushID(picker_type
);
4589 ImGuiColorEditFlags picker_flags
= ImGuiColorEditFlags_NoInputs
|ImGuiColorEditFlags_NoOptions
|ImGuiColorEditFlags_NoLabel
|ImGuiColorEditFlags_NoSidePreview
|(flags
& ImGuiColorEditFlags_NoAlpha
);
4590 if (picker_type
== 0) picker_flags
|= ImGuiColorEditFlags_PickerHueBar
;
4591 if (picker_type
== 1) picker_flags
|= ImGuiColorEditFlags_PickerHueWheel
;
4592 ImVec2 backup_pos
= GetCursorScreenPos();
4593 if (Selectable("##selectable", false, 0, picker_size
)) // By default, Selectable() is closing popup
4594 g
.ColorEditOptions
= (g
.ColorEditOptions
& ~ImGuiColorEditFlags__PickerMask
) | (picker_flags
& ImGuiColorEditFlags__PickerMask
);
4595 SetCursorScreenPos(backup_pos
);
4596 ImVec4 dummy_ref_col
;
4597 memcpy(&dummy_ref_col
, ref_col
, sizeof(float) * ((picker_flags
& ImGuiColorEditFlags_NoAlpha
) ? 3 : 4));
4598 ColorPicker4("##dummypicker", &dummy_ref_col
.x
, picker_flags
);
4603 if (allow_opt_alpha_bar
)
4605 if (allow_opt_picker
) Separator();
4606 CheckboxFlags("Alpha Bar", (unsigned int*)&g
.ColorEditOptions
, ImGuiColorEditFlags_AlphaBar
);
4611 //-------------------------------------------------------------------------
4612 // [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
4613 //-------------------------------------------------------------------------
4618 // - TreeNodeBehavior() [Internal]
4621 // - TreeAdvanceToLabelPos()
4622 // - GetTreeNodeToLabelSpacing()
4623 // - SetNextTreeNodeOpen()
4624 // - CollapsingHeader()
4625 //-------------------------------------------------------------------------
4627 bool ImGui::TreeNode(const char* str_id
, const char* fmt
, ...)
4630 va_start(args
, fmt
);
4631 bool is_open
= TreeNodeExV(str_id
, 0, fmt
, args
);
4636 bool ImGui::TreeNode(const void* ptr_id
, const char* fmt
, ...)
4639 va_start(args
, fmt
);
4640 bool is_open
= TreeNodeExV(ptr_id
, 0, fmt
, args
);
4645 bool ImGui::TreeNode(const char* label
)
4647 ImGuiWindow
* window
= GetCurrentWindow();
4648 if (window
->SkipItems
)
4650 return TreeNodeBehavior(window
->GetID(label
), 0, label
, NULL
);
4653 bool ImGui::TreeNodeV(const char* str_id
, const char* fmt
, va_list args
)
4655 return TreeNodeExV(str_id
, 0, fmt
, args
);
4658 bool ImGui::TreeNodeV(const void* ptr_id
, const char* fmt
, va_list args
)
4660 return TreeNodeExV(ptr_id
, 0, fmt
, args
);
4663 bool ImGui::TreeNodeEx(const char* label
, ImGuiTreeNodeFlags flags
)
4665 ImGuiWindow
* window
= GetCurrentWindow();
4666 if (window
->SkipItems
)
4669 return TreeNodeBehavior(window
->GetID(label
), flags
, label
, NULL
);
4672 bool ImGui::TreeNodeEx(const char* str_id
, ImGuiTreeNodeFlags flags
, const char* fmt
, ...)
4675 va_start(args
, fmt
);
4676 bool is_open
= TreeNodeExV(str_id
, flags
, fmt
, args
);
4681 bool ImGui::TreeNodeEx(const void* ptr_id
, ImGuiTreeNodeFlags flags
, const char* fmt
, ...)
4684 va_start(args
, fmt
);
4685 bool is_open
= TreeNodeExV(ptr_id
, flags
, fmt
, args
);
4690 bool ImGui::TreeNodeExV(const char* str_id
, ImGuiTreeNodeFlags flags
, const char* fmt
, va_list args
)
4692 ImGuiWindow
* window
= GetCurrentWindow();
4693 if (window
->SkipItems
)
4696 ImGuiContext
& g
= *GImGui
;
4697 const char* label_end
= g
.TempBuffer
+ ImFormatStringV(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), fmt
, args
);
4698 return TreeNodeBehavior(window
->GetID(str_id
), flags
, g
.TempBuffer
, label_end
);
4701 bool ImGui::TreeNodeExV(const void* ptr_id
, ImGuiTreeNodeFlags flags
, const char* fmt
, va_list args
)
4703 ImGuiWindow
* window
= GetCurrentWindow();
4704 if (window
->SkipItems
)
4707 ImGuiContext
& g
= *GImGui
;
4708 const char* label_end
= g
.TempBuffer
+ ImFormatStringV(g
.TempBuffer
, IM_ARRAYSIZE(g
.TempBuffer
), fmt
, args
);
4709 return TreeNodeBehavior(window
->GetID(ptr_id
), flags
, g
.TempBuffer
, label_end
);
4712 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id
, ImGuiTreeNodeFlags flags
)
4714 if (flags
& ImGuiTreeNodeFlags_Leaf
)
4717 // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
4718 ImGuiContext
& g
= *GImGui
;
4719 ImGuiWindow
* window
= g
.CurrentWindow
;
4720 ImGuiStorage
* storage
= window
->DC
.StateStorage
;
4723 if (g
.NextTreeNodeOpenCond
!= 0)
4725 if (g
.NextTreeNodeOpenCond
& ImGuiCond_Always
)
4727 is_open
= g
.NextTreeNodeOpenVal
;
4728 storage
->SetInt(id
, is_open
);
4732 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
4733 const int stored_value
= storage
->GetInt(id
, -1);
4734 if (stored_value
== -1)
4736 is_open
= g
.NextTreeNodeOpenVal
;
4737 storage
->SetInt(id
, is_open
);
4741 is_open
= stored_value
!= 0;
4744 g
.NextTreeNodeOpenCond
= 0;
4748 is_open
= storage
->GetInt(id
, (flags
& ImGuiTreeNodeFlags_DefaultOpen
) ? 1 : 0) != 0;
4751 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
4752 // NB- If we are above max depth we still allow manually opened nodes to be logged.
4753 if (g
.LogEnabled
&& !(flags
& ImGuiTreeNodeFlags_NoAutoOpenOnLog
) && window
->DC
.TreeDepth
< g
.LogAutoExpandMaxDepth
)
4759 bool ImGui::TreeNodeBehavior(ImGuiID id
, ImGuiTreeNodeFlags flags
, const char* label
, const char* label_end
)
4761 ImGuiWindow
* window
= GetCurrentWindow();
4762 if (window
->SkipItems
)
4765 ImGuiContext
& g
= *GImGui
;
4766 const ImGuiStyle
& style
= g
.Style
;
4767 const bool display_frame
= (flags
& ImGuiTreeNodeFlags_Framed
) != 0;
4768 const ImVec2 padding
= (display_frame
|| (flags
& ImGuiTreeNodeFlags_FramePadding
)) ? style
.FramePadding
: ImVec2(style
.FramePadding
.x
, 0.0f
);
4771 label_end
= FindRenderedTextEnd(label
);
4772 const ImVec2 label_size
= CalcTextSize(label
, label_end
, false);
4774 // We vertically grow up to current line height up the typical widget height.
4775 const float text_base_offset_y
= ImMax(padding
.y
, window
->DC
.CurrentLineTextBaseOffset
); // Latch before ItemSize changes it
4776 const float frame_height
= ImMax(ImMin(window
->DC
.CurrentLineSize
.y
, g
.FontSize
+ style
.FramePadding
.y
*2), label_size
.y
+ padding
.y
*2);
4777 ImRect frame_bb
= ImRect(window
->DC
.CursorPos
, ImVec2(window
->Pos
.x
+ GetContentRegionMax().x
, window
->DC
.CursorPos
.y
+ frame_height
));
4780 // Framed header expand a little outside the default padding
4781 frame_bb
.Min
.x
-= (float)(int)(window
->WindowPadding
.x
*0.5f
) - 1;
4782 frame_bb
.Max
.x
+= (float)(int)(window
->WindowPadding
.x
*0.5f
) - 1;
4785 const float text_offset_x
= (g
.FontSize
+ (display_frame
? padding
.x
*3 : padding
.x
*2)); // Collapser arrow width + Spacing
4786 const float text_width
= g
.FontSize
+ (label_size
.x
> 0.0f
? label_size
.x
+ padding
.x
*2 : 0.0f
); // Include collapser
4787 ItemSize(ImVec2(text_width
, frame_height
), text_base_offset_y
);
4789 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
4790 // (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)
4791 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
);
4792 bool is_open
= TreeNodeBehaviorIsOpen(id
, flags
);
4793 bool is_leaf
= (flags
& ImGuiTreeNodeFlags_Leaf
) != 0;
4795 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
4796 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
4797 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
4798 if (is_open
&& !g
.NavIdIsAlive
&& (flags
& ImGuiTreeNodeFlags_NavLeftJumpsBackHere
) && !(flags
& ImGuiTreeNodeFlags_NoTreePushOnOpen
))
4799 window
->DC
.TreeDepthMayJumpToParentOnPop
|= (1 << window
->DC
.TreeDepth
);
4801 bool item_add
= ItemAdd(interact_bb
, id
);
4802 window
->DC
.LastItemStatusFlags
|= ImGuiItemStatusFlags_HasDisplayRect
;
4803 window
->DC
.LastItemDisplayRect
= frame_bb
;
4807 if (is_open
&& !(flags
& ImGuiTreeNodeFlags_NoTreePushOnOpen
))
4809 IMGUI_TEST_ENGINE_ITEM_INFO(window
->DC
.LastItemId
, label
, window
->DC
.ItemFlags
| (is_leaf
? 0 : ImGuiItemStatusFlags_Openable
) | (is_open
? ImGuiItemStatusFlags_Opened
: 0));
4813 // Flags that affects opening behavior:
4814 // - 0 (default) .................... single-click anywhere to open
4815 // - OpenOnDoubleClick .............. double-click anywhere to open
4816 // - OpenOnArrow .................... single-click on arrow to open
4817 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
4818 ImGuiButtonFlags button_flags
= ImGuiButtonFlags_NoKeyModifiers
;
4819 if (flags
& ImGuiTreeNodeFlags_AllowItemOverlap
)
4820 button_flags
|= ImGuiButtonFlags_AllowItemOverlap
;
4821 if (flags
& ImGuiTreeNodeFlags_OpenOnDoubleClick
)
4822 button_flags
|= ImGuiButtonFlags_PressedOnDoubleClick
| ((flags
& ImGuiTreeNodeFlags_OpenOnArrow
) ? ImGuiButtonFlags_PressedOnClickRelease
: 0);
4824 button_flags
|= ImGuiButtonFlags_PressedOnDragDropHold
;
4826 bool selected
= (flags
& ImGuiTreeNodeFlags_Selected
) != 0;
4828 bool pressed
= ButtonBehavior(interact_bb
, id
, &hovered
, &held
, button_flags
);
4829 bool toggled
= false;
4834 toggled
= !(flags
& (ImGuiTreeNodeFlags_OpenOnArrow
| ImGuiTreeNodeFlags_OpenOnDoubleClick
)) || (g
.NavActivateId
== id
);
4835 if (flags
& ImGuiTreeNodeFlags_OpenOnArrow
)
4836 toggled
|= IsMouseHoveringRect(interact_bb
.Min
, ImVec2(interact_bb
.Min
.x
+ text_offset_x
, interact_bb
.Max
.y
)) && (!g
.NavDisableMouseHover
);
4837 if (flags
& ImGuiTreeNodeFlags_OpenOnDoubleClick
)
4838 toggled
|= g
.IO
.MouseDoubleClicked
[0];
4839 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.
4843 if (g
.NavId
== id
&& g
.NavMoveRequest
&& g
.NavMoveDir
== ImGuiDir_Left
&& is_open
)
4846 NavMoveRequestCancel();
4848 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?
4851 NavMoveRequestCancel();
4857 window
->DC
.StateStorage
->SetInt(id
, is_open
);
4860 if (flags
& ImGuiTreeNodeFlags_AllowItemOverlap
)
4861 SetItemAllowOverlap();
4864 const ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_HeaderActive
: hovered
? ImGuiCol_HeaderHovered
: ImGuiCol_Header
);
4865 const ImVec2 text_pos
= frame_bb
.Min
+ ImVec2(text_offset_x
, text_base_offset_y
);
4866 ImGuiNavHighlightFlags nav_highlight_flags
= ImGuiNavHighlightFlags_TypeThin
;
4870 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, col
, true, style
.FrameRounding
);
4871 RenderNavHighlight(frame_bb
, id
, nav_highlight_flags
);
4872 RenderArrow(frame_bb
.Min
+ ImVec2(padding
.x
, text_base_offset_y
), is_open
? ImGuiDir_Down
: ImGuiDir_Right
, 1.0f
);
4875 // 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.
4876 const char log_prefix
[] = "\n##";
4877 const char log_suffix
[] = "##";
4878 LogRenderedText(&text_pos
, log_prefix
, log_prefix
+3);
4879 RenderTextClipped(text_pos
, frame_bb
.Max
, label
, label_end
, &label_size
);
4880 LogRenderedText(&text_pos
, log_suffix
+1, log_suffix
+3);
4884 RenderTextClipped(text_pos
, frame_bb
.Max
, label
, label_end
, &label_size
);
4889 // Unframed typed for tree nodes
4890 if (hovered
|| selected
)
4892 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, col
, false);
4893 RenderNavHighlight(frame_bb
, id
, nav_highlight_flags
);
4896 if (flags
& ImGuiTreeNodeFlags_Bullet
)
4897 RenderBullet(frame_bb
.Min
+ ImVec2(text_offset_x
* 0.5f
, g
.FontSize
*0.50f
+ text_base_offset_y
));
4899 RenderArrow(frame_bb
.Min
+ ImVec2(padding
.x
, g
.FontSize
*0.15f
+ text_base_offset_y
), is_open
? ImGuiDir_Down
: ImGuiDir_Right
, 0.70f
);
4901 LogRenderedText(&text_pos
, ">");
4902 RenderText(text_pos
, label
, label_end
, false);
4905 if (is_open
&& !(flags
& ImGuiTreeNodeFlags_NoTreePushOnOpen
))
4907 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
| (is_leaf
? 0 : ImGuiItemStatusFlags_Openable
) | (is_open
? ImGuiItemStatusFlags_Opened
: 0));
4911 void ImGui::TreePush(const char* str_id
)
4913 ImGuiWindow
* window
= GetCurrentWindow();
4915 window
->DC
.TreeDepth
++;
4916 PushID(str_id
? str_id
: "#TreePush");
4919 void ImGui::TreePush(const void* ptr_id
)
4921 ImGuiWindow
* window
= GetCurrentWindow();
4923 window
->DC
.TreeDepth
++;
4924 PushID(ptr_id
? ptr_id
: (const void*)"#TreePush");
4927 void ImGui::TreePushRawID(ImGuiID id
)
4929 ImGuiWindow
* window
= GetCurrentWindow();
4931 window
->DC
.TreeDepth
++;
4932 window
->IDStack
.push_back(id
);
4935 void ImGui::TreePop()
4937 ImGuiContext
& g
= *GImGui
;
4938 ImGuiWindow
* window
= g
.CurrentWindow
;
4941 window
->DC
.TreeDepth
--;
4942 if (g
.NavMoveDir
== ImGuiDir_Left
&& g
.NavWindow
== window
&& NavMoveRequestButNoResultYet())
4943 if (g
.NavIdIsAlive
&& (window
->DC
.TreeDepthMayJumpToParentOnPop
& (1 << window
->DC
.TreeDepth
)))
4945 SetNavID(window
->IDStack
.back(), g
.NavLayer
);
4946 NavMoveRequestCancel();
4948 window
->DC
.TreeDepthMayJumpToParentOnPop
&= (1 << window
->DC
.TreeDepth
) - 1;
4950 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.
4954 void ImGui::TreeAdvanceToLabelPos()
4956 ImGuiContext
& g
= *GImGui
;
4957 g
.CurrentWindow
->DC
.CursorPos
.x
+= GetTreeNodeToLabelSpacing();
4960 // Horizontal distance preceding label when using TreeNode() or Bullet()
4961 float ImGui::GetTreeNodeToLabelSpacing()
4963 ImGuiContext
& g
= *GImGui
;
4964 return g
.FontSize
+ (g
.Style
.FramePadding
.x
* 2.0f
);
4967 void ImGui::SetNextTreeNodeOpen(bool is_open
, ImGuiCond cond
)
4969 ImGuiContext
& g
= *GImGui
;
4970 if (g
.CurrentWindow
->SkipItems
)
4972 g
.NextTreeNodeOpenVal
= is_open
;
4973 g
.NextTreeNodeOpenCond
= cond
? cond
: ImGuiCond_Always
;
4976 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
4977 // 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().
4978 bool ImGui::CollapsingHeader(const char* label
, ImGuiTreeNodeFlags flags
)
4980 ImGuiWindow
* window
= GetCurrentWindow();
4981 if (window
->SkipItems
)
4984 return TreeNodeBehavior(window
->GetID(label
), flags
| ImGuiTreeNodeFlags_CollapsingHeader
, label
);
4987 bool ImGui::CollapsingHeader(const char* label
, bool* p_open
, ImGuiTreeNodeFlags flags
)
4989 ImGuiWindow
* window
= GetCurrentWindow();
4990 if (window
->SkipItems
)
4993 if (p_open
&& !*p_open
)
4996 ImGuiID id
= window
->GetID(label
);
4997 bool is_open
= TreeNodeBehavior(id
, flags
| ImGuiTreeNodeFlags_CollapsingHeader
| (p_open
? ImGuiTreeNodeFlags_AllowItemOverlap
: 0), label
);
5000 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
5001 ImGuiContext
& g
= *GImGui
;
5002 ImGuiItemHoveredDataBackup last_item_backup
;
5003 float button_radius
= g
.FontSize
* 0.5f
;
5004 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
);
5005 if (CloseButton(window
->GetID((void*)((intptr_t)id
+1)), button_center
, button_radius
))
5007 last_item_backup
.Restore();
5013 //-------------------------------------------------------------------------
5014 // [SECTION] Widgets: Selectable
5015 //-------------------------------------------------------------------------
5017 //-------------------------------------------------------------------------
5019 // Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
5020 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
5021 bool ImGui::Selectable(const char* label
, bool selected
, ImGuiSelectableFlags flags
, const ImVec2
& size_arg
)
5023 ImGuiWindow
* window
= GetCurrentWindow();
5024 if (window
->SkipItems
)
5027 ImGuiContext
& g
= *GImGui
;
5028 const ImGuiStyle
& style
= g
.Style
;
5030 if ((flags
& ImGuiSelectableFlags_SpanAllColumns
) && window
->DC
.ColumnsSet
) // FIXME-OPT: Avoid if vertically clipped.
5033 ImGuiID id
= window
->GetID(label
);
5034 ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
5035 ImVec2
size(size_arg
.x
!= 0.0f
? size_arg
.x
: label_size
.x
, size_arg
.y
!= 0.0f
? size_arg
.y
: label_size
.y
);
5036 ImVec2 pos
= window
->DC
.CursorPos
;
5037 pos
.y
+= window
->DC
.CurrentLineTextBaseOffset
;
5038 ImRect
bb_inner(pos
, pos
+ size
);
5041 // Fill horizontal space.
5042 ImVec2 window_padding
= window
->WindowPadding
;
5043 float max_x
= (flags
& ImGuiSelectableFlags_SpanAllColumns
) ? GetWindowContentRegionMax().x
: GetContentRegionMax().x
;
5044 float w_draw
= ImMax(label_size
.x
, window
->Pos
.x
+ max_x
- window_padding
.x
- pos
.x
);
5045 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
);
5046 ImRect
bb(pos
, pos
+ size_draw
);
5047 if (size_arg
.x
== 0.0f
|| (flags
& ImGuiSelectableFlags_DrawFillAvailWidth
))
5048 bb
.Max
.x
+= window_padding
.x
;
5050 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
5051 float spacing_L
= (float)(int)(style
.ItemSpacing
.x
* 0.5f
);
5052 float spacing_U
= (float)(int)(style
.ItemSpacing
.y
* 0.5f
);
5053 float spacing_R
= style
.ItemSpacing
.x
- spacing_L
;
5054 float spacing_D
= style
.ItemSpacing
.y
- spacing_U
;
5055 bb
.Min
.x
-= spacing_L
;
5056 bb
.Min
.y
-= spacing_U
;
5057 bb
.Max
.x
+= spacing_R
;
5058 bb
.Max
.y
+= spacing_D
;
5059 if (!ItemAdd(bb
, id
))
5061 if ((flags
& ImGuiSelectableFlags_SpanAllColumns
) && window
->DC
.ColumnsSet
)
5062 PushColumnClipRect();
5066 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
5067 ImGuiButtonFlags button_flags
= 0;
5068 if (flags
& ImGuiSelectableFlags_NoHoldingActiveID
) button_flags
|= ImGuiButtonFlags_NoHoldingActiveID
;
5069 if (flags
& ImGuiSelectableFlags_PressedOnClick
) button_flags
|= ImGuiButtonFlags_PressedOnClick
;
5070 if (flags
& ImGuiSelectableFlags_PressedOnRelease
) button_flags
|= ImGuiButtonFlags_PressedOnRelease
;
5071 if (flags
& ImGuiSelectableFlags_Disabled
) button_flags
|= ImGuiButtonFlags_Disabled
;
5072 if (flags
& ImGuiSelectableFlags_AllowDoubleClick
) button_flags
|= ImGuiButtonFlags_PressedOnClickRelease
| ImGuiButtonFlags_PressedOnDoubleClick
;
5073 if (flags
& ImGuiSelectableFlags_Disabled
)
5077 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
, button_flags
);
5078 // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
5079 if (pressed
|| hovered
)
5080 if (!g
.NavDisableMouseHover
&& g
.NavWindow
== window
&& g
.NavLayer
== window
->DC
.NavLayerCurrent
)
5082 g
.NavDisableHighlight
= true;
5083 SetNavID(id
, window
->DC
.NavLayerCurrent
);
5089 if (hovered
|| selected
)
5091 const ImU32 col
= GetColorU32((held
&& hovered
) ? ImGuiCol_HeaderActive
: hovered
? ImGuiCol_HeaderHovered
: ImGuiCol_Header
);
5092 RenderFrame(bb
.Min
, bb
.Max
, col
, false, 0.0f
);
5093 RenderNavHighlight(bb
, id
, ImGuiNavHighlightFlags_TypeThin
| ImGuiNavHighlightFlags_NoRounding
);
5096 if ((flags
& ImGuiSelectableFlags_SpanAllColumns
) && window
->DC
.ColumnsSet
)
5098 PushColumnClipRect();
5099 bb
.Max
.x
-= (GetContentRegionMax().x
- max_x
);
5102 if (flags
& ImGuiSelectableFlags_Disabled
) PushStyleColor(ImGuiCol_Text
, g
.Style
.Colors
[ImGuiCol_TextDisabled
]);
5103 RenderTextClipped(bb_inner
.Min
, bb_inner
.Max
, label
, NULL
, &label_size
, style
.SelectableTextAlign
, &bb
);
5104 if (flags
& ImGuiSelectableFlags_Disabled
) PopStyleColor();
5106 // Automatically close popups
5107 if (pressed
&& (window
->Flags
& ImGuiWindowFlags_Popup
) && !(flags
& ImGuiSelectableFlags_DontClosePopups
) && !(window
->DC
.ItemFlags
& ImGuiItemFlags_SelectableDontClosePopup
))
5108 CloseCurrentPopup();
5112 bool ImGui::Selectable(const char* label
, bool* p_selected
, ImGuiSelectableFlags flags
, const ImVec2
& size_arg
)
5114 if (Selectable(label
, *p_selected
, flags
, size_arg
))
5116 *p_selected
= !*p_selected
;
5122 //-------------------------------------------------------------------------
5123 // [SECTION] Widgets: ListBox
5124 //-------------------------------------------------------------------------
5126 // - ListBoxHeader()
5127 // - ListBoxFooter()
5128 //-------------------------------------------------------------------------
5130 // FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5131 // Helper to calculate the size of a listbox and display a label on the right.
5132 // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
5133 bool ImGui::ListBoxHeader(const char* label
, const ImVec2
& size_arg
)
5135 ImGuiWindow
* window
= GetCurrentWindow();
5136 if (window
->SkipItems
)
5139 const ImGuiStyle
& style
= GetStyle();
5140 const ImGuiID id
= GetID(label
);
5141 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
5143 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
5144 ImVec2 size
= CalcItemSize(size_arg
, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f
+ style
.ItemSpacing
.y
);
5145 ImVec2 frame_size
= ImVec2(size
.x
, ImMax(size
.y
, label_size
.y
));
5146 ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ frame_size
);
5147 ImRect
bb(frame_bb
.Min
, frame_bb
.Max
+ ImVec2(label_size
.x
> 0.0f
? style
.ItemInnerSpacing
.x
+ label_size
.x
: 0.0f
, 0.0f
));
5148 window
->DC
.LastItemRect
= bb
; // Forward storage for ListBoxFooter.. dodgy.
5150 if (!IsRectVisible(bb
.Min
, bb
.Max
))
5152 ItemSize(bb
.GetSize(), style
.FramePadding
.y
);
5153 ItemAdd(bb
, 0, &frame_bb
);
5158 if (label_size
.x
> 0)
5159 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, frame_bb
.Min
.y
+ style
.FramePadding
.y
), label
);
5161 BeginChildFrame(id
, frame_bb
.GetSize());
5165 // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5166 bool ImGui::ListBoxHeader(const char* label
, int items_count
, int height_in_items
)
5168 // Size default to hold ~7.25 items.
5169 // We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
5170 // We don't add this extra bit 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.
5171 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
5172 if (height_in_items
< 0)
5173 height_in_items
= ImMin(items_count
, 7);
5174 const ImGuiStyle
& style
= GetStyle();
5175 float height_in_items_f
= (height_in_items
< items_count
) ? (height_in_items
+ 0.25f
) : (height_in_items
+ 0.00f
);
5177 // 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().
5180 size
.y
= GetTextLineHeightWithSpacing() * height_in_items_f
+ style
.FramePadding
.y
* 2.0f
;
5181 return ListBoxHeader(label
, size
);
5184 // FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5185 void ImGui::ListBoxFooter()
5187 ImGuiWindow
* parent_window
= GetCurrentWindow()->ParentWindow
;
5188 const ImRect bb
= parent_window
->DC
.LastItemRect
;
5189 const ImGuiStyle
& style
= GetStyle();
5193 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
5194 // We call SameLine() to restore DC.CurrentLine* data
5196 parent_window
->DC
.CursorPos
= bb
.Min
;
5197 ItemSize(bb
, style
.FramePadding
.y
);
5201 bool ImGui::ListBox(const char* label
, int* current_item
, const char* const items
[], int items_count
, int height_items
)
5203 const bool value_changed
= ListBox(label
, current_item
, Items_ArrayGetter
, (void*)items
, items_count
, height_items
);
5204 return value_changed
;
5207 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
)
5209 if (!ListBoxHeader(label
, items_count
, height_in_items
))
5212 // 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.
5213 ImGuiContext
& g
= *GImGui
;
5214 bool value_changed
= false;
5215 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.
5216 while (clipper
.Step())
5217 for (int i
= clipper
.DisplayStart
; i
< clipper
.DisplayEnd
; i
++)
5219 const bool item_selected
= (i
== *current_item
);
5220 const char* item_text
;
5221 if (!items_getter(data
, i
, &item_text
))
5222 item_text
= "*Unknown item*";
5225 if (Selectable(item_text
, item_selected
))
5228 value_changed
= true;
5231 SetItemDefaultFocus();
5236 MarkItemEdited(g
.CurrentWindow
->DC
.LastItemId
);
5238 return value_changed
;
5241 //-------------------------------------------------------------------------
5242 // [SECTION] Widgets: PlotLines, PlotHistogram
5243 //-------------------------------------------------------------------------
5244 // - PlotEx() [Internal]
5246 // - PlotHistogram()
5247 //-------------------------------------------------------------------------
5249 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 frame_size
)
5251 ImGuiWindow
* window
= GetCurrentWindow();
5252 if (window
->SkipItems
)
5255 ImGuiContext
& g
= *GImGui
;
5256 const ImGuiStyle
& style
= g
.Style
;
5257 const ImGuiID id
= window
->GetID(label
);
5259 const ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
5260 if (frame_size
.x
== 0.0f
)
5261 frame_size
.x
= CalcItemWidth();
5262 if (frame_size
.y
== 0.0f
)
5263 frame_size
.y
= label_size
.y
+ (style
.FramePadding
.y
* 2);
5265 const ImRect
frame_bb(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ frame_size
);
5266 const ImRect
inner_bb(frame_bb
.Min
+ style
.FramePadding
, frame_bb
.Max
- style
.FramePadding
);
5267 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));
5268 ItemSize(total_bb
, style
.FramePadding
.y
);
5269 if (!ItemAdd(total_bb
, 0, &frame_bb
))
5271 const bool hovered
= ItemHoverable(frame_bb
, id
);
5273 // Determine scale from values if not specified
5274 if (scale_min
== FLT_MAX
|| scale_max
== FLT_MAX
)
5276 float v_min
= FLT_MAX
;
5277 float v_max
= -FLT_MAX
;
5278 for (int i
= 0; i
< values_count
; i
++)
5280 const float v
= values_getter(data
, i
);
5281 v_min
= ImMin(v_min
, v
);
5282 v_max
= ImMax(v_max
, v
);
5284 if (scale_min
== FLT_MAX
)
5286 if (scale_max
== FLT_MAX
)
5290 RenderFrame(frame_bb
.Min
, frame_bb
.Max
, GetColorU32(ImGuiCol_FrameBg
), true, style
.FrameRounding
);
5292 if (values_count
> 0)
5294 int res_w
= ImMin((int)frame_size
.x
, values_count
) + ((plot_type
== ImGuiPlotType_Lines
) ? -1 : 0);
5295 int item_count
= values_count
+ ((plot_type
== ImGuiPlotType_Lines
) ? -1 : 0);
5299 if (hovered
&& inner_bb
.Contains(g
.IO
.MousePos
))
5301 const float t
= ImClamp((g
.IO
.MousePos
.x
- inner_bb
.Min
.x
) / (inner_bb
.Max
.x
- inner_bb
.Min
.x
), 0.0f
, 0.9999f
);
5302 const int v_idx
= (int)(t
* item_count
);
5303 IM_ASSERT(v_idx
>= 0 && v_idx
< values_count
);
5305 const float v0
= values_getter(data
, (v_idx
+ values_offset
) % values_count
);
5306 const float v1
= values_getter(data
, (v_idx
+ 1 + values_offset
) % values_count
);
5307 if (plot_type
== ImGuiPlotType_Lines
)
5308 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx
, v0
, v_idx
+1, v1
);
5309 else if (plot_type
== ImGuiPlotType_Histogram
)
5310 SetTooltip("%d: %8.4g", v_idx
, v0
);
5314 const float t_step
= 1.0f
/ (float)res_w
;
5315 const float inv_scale
= (scale_min
== scale_max
) ? 0.0f
: (1.0f
/ (scale_max
- scale_min
));
5317 float v0
= values_getter(data
, (0 + values_offset
) % values_count
);
5319 ImVec2 tp0
= ImVec2( t0
, 1.0f
- ImSaturate((v0
- scale_min
) * inv_scale
) ); // Point in the normalized space of our target rectangle
5320 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
5322 const ImU32 col_base
= GetColorU32((plot_type
== ImGuiPlotType_Lines
) ? ImGuiCol_PlotLines
: ImGuiCol_PlotHistogram
);
5323 const ImU32 col_hovered
= GetColorU32((plot_type
== ImGuiPlotType_Lines
) ? ImGuiCol_PlotLinesHovered
: ImGuiCol_PlotHistogramHovered
);
5325 for (int n
= 0; n
< res_w
; n
++)
5327 const float t1
= t0
+ t_step
;
5328 const int v1_idx
= (int)(t0
* item_count
+ 0.5f
);
5329 IM_ASSERT(v1_idx
>= 0 && v1_idx
< values_count
);
5330 const float v1
= values_getter(data
, (v1_idx
+ values_offset
+ 1) % values_count
);
5331 const ImVec2 tp1
= ImVec2( t1
, 1.0f
- ImSaturate((v1
- scale_min
) * inv_scale
) );
5333 // 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.
5334 ImVec2 pos0
= ImLerp(inner_bb
.Min
, inner_bb
.Max
, tp0
);
5335 ImVec2 pos1
= ImLerp(inner_bb
.Min
, inner_bb
.Max
, (plot_type
== ImGuiPlotType_Lines
) ? tp1
: ImVec2(tp1
.x
, histogram_zero_line_t
));
5336 if (plot_type
== ImGuiPlotType_Lines
)
5338 window
->DrawList
->AddLine(pos0
, pos1
, v_hovered
== v1_idx
? col_hovered
: col_base
);
5340 else if (plot_type
== ImGuiPlotType_Histogram
)
5342 if (pos1
.x
>= pos0
.x
+ 2.0f
)
5344 window
->DrawList
->AddRectFilled(pos0
, pos1
, v_hovered
== v1_idx
? col_hovered
: col_base
);
5354 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
));
5356 if (label_size
.x
> 0.0f
)
5357 RenderText(ImVec2(frame_bb
.Max
.x
+ style
.ItemInnerSpacing
.x
, inner_bb
.Min
.y
), label
);
5360 struct ImGuiPlotArrayGetterData
5362 const float* Values
;
5365 ImGuiPlotArrayGetterData(const float* values
, int stride
) { Values
= values
; Stride
= stride
; }
5368 static float Plot_ArrayGetter(void* data
, int idx
)
5370 ImGuiPlotArrayGetterData
* plot_data
= (ImGuiPlotArrayGetterData
*)data
;
5371 const float v
= *(const float*)(const void*)((const unsigned char*)plot_data
->Values
+ (size_t)idx
* plot_data
->Stride
);
5375 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
)
5377 ImGuiPlotArrayGetterData
data(values
, stride
);
5378 PlotEx(ImGuiPlotType_Lines
, label
, &Plot_ArrayGetter
, (void*)&data
, values_count
, values_offset
, overlay_text
, scale_min
, scale_max
, graph_size
);
5381 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
)
5383 PlotEx(ImGuiPlotType_Lines
, label
, values_getter
, data
, values_count
, values_offset
, overlay_text
, scale_min
, scale_max
, graph_size
);
5386 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
)
5388 ImGuiPlotArrayGetterData
data(values
, stride
);
5389 PlotEx(ImGuiPlotType_Histogram
, label
, &Plot_ArrayGetter
, (void*)&data
, values_count
, values_offset
, overlay_text
, scale_min
, scale_max
, graph_size
);
5392 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
)
5394 PlotEx(ImGuiPlotType_Histogram
, label
, values_getter
, data
, values_count
, values_offset
, overlay_text
, scale_min
, scale_max
, graph_size
);
5397 //-------------------------------------------------------------------------
5398 // [SECTION] Widgets: Value helpers
5399 // Those is not very useful, legacy API.
5400 //-------------------------------------------------------------------------
5402 //-------------------------------------------------------------------------
5404 void ImGui::Value(const char* prefix
, bool b
)
5406 Text("%s: %s", prefix
, (b
? "true" : "false"));
5409 void ImGui::Value(const char* prefix
, int v
)
5411 Text("%s: %d", prefix
, v
);
5414 void ImGui::Value(const char* prefix
, unsigned int v
)
5416 Text("%s: %d", prefix
, v
);
5419 void ImGui::Value(const char* prefix
, float v
, const char* float_format
)
5424 ImFormatString(fmt
, IM_ARRAYSIZE(fmt
), "%%s: %s", float_format
);
5425 Text(fmt
, prefix
, v
);
5429 Text("%s: %.3f", prefix
, v
);
5433 //-------------------------------------------------------------------------
5434 // [SECTION] MenuItem, BeginMenu, EndMenu, etc.
5435 //-------------------------------------------------------------------------
5436 // - ImGuiMenuColumns [Internal]
5437 // - BeginMainMenuBar()
5438 // - EndMainMenuBar()
5444 //-------------------------------------------------------------------------
5446 // Helpers for internal use
5447 ImGuiMenuColumns::ImGuiMenuColumns()
5450 Spacing
= Width
= NextWidth
= 0.0f
;
5451 memset(Pos
, 0, sizeof(Pos
));
5452 memset(NextWidths
, 0, sizeof(NextWidths
));
5455 void ImGuiMenuColumns::Update(int count
, float spacing
, bool clear
)
5457 IM_ASSERT(Count
<= IM_ARRAYSIZE(Pos
));
5459 Width
= NextWidth
= 0.0f
;
5461 if (clear
) memset(NextWidths
, 0, sizeof(NextWidths
));
5462 for (int i
= 0; i
< Count
; i
++)
5464 if (i
> 0 && NextWidths
[i
] > 0.0f
)
5466 Pos
[i
] = (float)(int)Width
;
5467 Width
+= NextWidths
[i
];
5468 NextWidths
[i
] = 0.0f
;
5472 float ImGuiMenuColumns::DeclColumns(float w0
, float w1
, float w2
) // not using va_arg because they promote float to double
5475 NextWidths
[0] = ImMax(NextWidths
[0], w0
);
5476 NextWidths
[1] = ImMax(NextWidths
[1], w1
);
5477 NextWidths
[2] = ImMax(NextWidths
[2], w2
);
5478 for (int i
= 0; i
< 3; i
++)
5479 NextWidth
+= NextWidths
[i
] + ((i
> 0 && NextWidths
[i
] > 0.0f
) ? Spacing
: 0.0f
);
5480 return ImMax(Width
, NextWidth
);
5483 float ImGuiMenuColumns::CalcExtraSpace(float avail_w
)
5485 return ImMax(0.0f
, avail_w
- Width
);
5488 // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
5489 bool ImGui::BeginMainMenuBar()
5491 ImGuiContext
& g
= *GImGui
;
5492 g
.NextWindowData
.MenuBarOffsetMinVal
= ImVec2(g
.Style
.DisplaySafeAreaPadding
.x
, ImMax(g
.Style
.DisplaySafeAreaPadding
.y
- g
.Style
.FramePadding
.y
, 0.0f
));
5493 SetNextWindowPos(ImVec2(0.0f
, 0.0f
));
5494 SetNextWindowSize(ImVec2(g
.IO
.DisplaySize
.x
, g
.NextWindowData
.MenuBarOffsetMinVal
.y
+ g
.FontBaseSize
+ g
.Style
.FramePadding
.y
));
5495 PushStyleVar(ImGuiStyleVar_WindowRounding
, 0.0f
);
5496 PushStyleVar(ImGuiStyleVar_WindowMinSize
, ImVec2(0,0));
5497 ImGuiWindowFlags window_flags
= ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_MenuBar
;
5498 bool is_open
= Begin("##MainMenuBar", NULL
, window_flags
) && BeginMenuBar();
5500 g
.NextWindowData
.MenuBarOffsetMinVal
= ImVec2(0.0f
, 0.0f
);
5506 return true; //-V1020
5509 void ImGui::EndMainMenuBar()
5513 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
5514 ImGuiContext
& g
= *GImGui
;
5515 if (g
.CurrentWindow
== g
.NavWindow
&& g
.NavLayer
== 0)
5516 FocusPreviousWindowIgnoringOne(g
.NavWindow
);
5521 bool ImGui::BeginMenuBar()
5523 ImGuiWindow
* window
= GetCurrentWindow();
5524 if (window
->SkipItems
)
5526 if (!(window
->Flags
& ImGuiWindowFlags_MenuBar
))
5529 IM_ASSERT(!window
->DC
.MenuBarAppending
);
5530 BeginGroup(); // Backup position on layer 0
5531 PushID("##menubar");
5533 // 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.
5534 // 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.
5535 ImRect bar_rect
= window
->MenuBarRect();
5536 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
));
5537 clip_rect
.ClipWith(window
->OuterRectClipped
);
5538 PushClipRect(clip_rect
.Min
, clip_rect
.Max
, false);
5540 window
->DC
.CursorPos
= ImVec2(bar_rect
.Min
.x
+ window
->DC
.MenuBarOffset
.x
, bar_rect
.Min
.y
+ window
->DC
.MenuBarOffset
.y
);
5541 window
->DC
.LayoutType
= ImGuiLayoutType_Horizontal
;
5542 window
->DC
.NavLayerCurrent
= ImGuiNavLayer_Menu
;
5543 window
->DC
.NavLayerCurrentMask
= (1 << ImGuiNavLayer_Menu
);
5544 window
->DC
.MenuBarAppending
= true;
5545 AlignTextToFramePadding();
5549 void ImGui::EndMenuBar()
5551 ImGuiWindow
* window
= GetCurrentWindow();
5552 if (window
->SkipItems
)
5554 ImGuiContext
& g
= *GImGui
;
5556 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
5557 if (NavMoveRequestButNoResultYet() && (g
.NavMoveDir
== ImGuiDir_Left
|| g
.NavMoveDir
== ImGuiDir_Right
) && (g
.NavWindow
->Flags
& ImGuiWindowFlags_ChildMenu
))
5559 ImGuiWindow
* nav_earliest_child
= g
.NavWindow
;
5560 while (nav_earliest_child
->ParentWindow
&& (nav_earliest_child
->ParentWindow
->Flags
& ImGuiWindowFlags_ChildMenu
))
5561 nav_earliest_child
= nav_earliest_child
->ParentWindow
;
5562 if (nav_earliest_child
->ParentWindow
== window
&& nav_earliest_child
->DC
.ParentLayoutType
== ImGuiLayoutType_Horizontal
&& g
.NavMoveRequestForward
== ImGuiNavForward_None
)
5564 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
5565 // 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)
5566 IM_ASSERT(window
->DC
.NavLayerActiveMaskNext
& 0x02); // Sanity check
5567 FocusWindow(window
);
5568 SetNavIDWithRectRel(window
->NavLastIds
[1], 1, window
->NavRectRel
[1]);
5569 g
.NavLayer
= ImGuiNavLayer_Menu
;
5570 g
.NavDisableHighlight
= true; // Hide highlight for the current frame so we don't see the intermediary selection.
5571 g
.NavMoveRequestForward
= ImGuiNavForward_ForwardQueued
;
5572 NavMoveRequestCancel();
5576 IM_ASSERT(window
->Flags
& ImGuiWindowFlags_MenuBar
);
5577 IM_ASSERT(window
->DC
.MenuBarAppending
);
5580 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.
5581 window
->DC
.GroupStack
.back().AdvanceCursor
= false;
5582 EndGroup(); // Restore position on layer 0
5583 window
->DC
.LayoutType
= ImGuiLayoutType_Vertical
;
5584 window
->DC
.NavLayerCurrent
= ImGuiNavLayer_Main
;
5585 window
->DC
.NavLayerCurrentMask
= (1 << ImGuiNavLayer_Main
);
5586 window
->DC
.MenuBarAppending
= false;
5589 bool ImGui::BeginMenu(const char* label
, bool enabled
)
5591 ImGuiWindow
* window
= GetCurrentWindow();
5592 if (window
->SkipItems
)
5595 ImGuiContext
& g
= *GImGui
;
5596 const ImGuiStyle
& style
= g
.Style
;
5597 const ImGuiID id
= window
->GetID(label
);
5599 ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
5602 bool menu_is_open
= IsPopupOpen(id
);
5603 bool menuset_is_open
= !(window
->Flags
& ImGuiWindowFlags_Popup
) && (g
.OpenPopupStack
.Size
> g
.BeginPopupStack
.Size
&& g
.OpenPopupStack
[g
.BeginPopupStack
.Size
].OpenParentId
== window
->IDStack
.back());
5604 ImGuiWindow
* backed_nav_window
= g
.NavWindow
;
5605 if (menuset_is_open
)
5606 g
.NavWindow
= window
; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
5608 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
5609 // However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().
5610 // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
5611 ImVec2 popup_pos
, pos
= window
->DC
.CursorPos
;
5612 if (window
->DC
.LayoutType
== ImGuiLayoutType_Horizontal
)
5614 // Menu inside an horizontal menu bar
5615 // Selectable extend their highlight by half ItemSpacing in each direction.
5616 // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
5617 popup_pos
= ImVec2(pos
.x
- 1.0f
- (float)(int)(style
.ItemSpacing
.x
* 0.5f
), pos
.y
- style
.FramePadding
.y
+ window
->MenuBarHeight());
5618 window
->DC
.CursorPos
.x
+= (float)(int)(style
.ItemSpacing
.x
* 0.5f
);
5619 PushStyleVar(ImGuiStyleVar_ItemSpacing
, style
.ItemSpacing
* 2.0f
);
5620 float w
= label_size
.x
;
5621 pressed
= Selectable(label
, menu_is_open
, ImGuiSelectableFlags_NoHoldingActiveID
| ImGuiSelectableFlags_PressedOnClick
| ImGuiSelectableFlags_DontClosePopups
| (!enabled
? ImGuiSelectableFlags_Disabled
: 0), ImVec2(w
, 0.0f
));
5623 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().
5627 // Menu inside a menu
5628 popup_pos
= ImVec2(pos
.x
, pos
.y
- style
.WindowPadding
.y
);
5629 float w
= window
->MenuColumns
.DeclColumns(label_size
.x
, 0.0f
, (float)(int)(g
.FontSize
* 1.20f
)); // Feedback to next frame
5630 float extra_w
= ImMax(0.0f
, GetContentRegionAvail().x
- w
);
5631 pressed
= Selectable(label
, menu_is_open
, ImGuiSelectableFlags_NoHoldingActiveID
| ImGuiSelectableFlags_PressedOnClick
| ImGuiSelectableFlags_DontClosePopups
| ImGuiSelectableFlags_DrawFillAvailWidth
| (!enabled
? ImGuiSelectableFlags_Disabled
: 0), ImVec2(w
, 0.0f
));
5632 if (!enabled
) PushStyleColor(ImGuiCol_Text
, g
.Style
.Colors
[ImGuiCol_TextDisabled
]);
5633 RenderArrow(pos
+ ImVec2(window
->MenuColumns
.Pos
[2] + extra_w
+ g
.FontSize
* 0.30f
, 0.0f
), ImGuiDir_Right
);
5634 if (!enabled
) PopStyleColor();
5637 const bool hovered
= enabled
&& ItemHoverable(window
->DC
.LastItemRect
, id
);
5638 if (menuset_is_open
)
5639 g
.NavWindow
= backed_nav_window
;
5641 bool want_open
= false, want_close
= false;
5642 if (window
->DC
.LayoutType
== ImGuiLayoutType_Vertical
) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5644 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
5645 bool moving_within_opened_triangle
= false;
5646 if (g
.HoveredWindow
== window
&& g
.OpenPopupStack
.Size
> g
.BeginPopupStack
.Size
&& g
.OpenPopupStack
[g
.BeginPopupStack
.Size
].ParentWindow
== window
&& !(window
->Flags
& ImGuiWindowFlags_MenuBar
))
5648 if (ImGuiWindow
* next_window
= g
.OpenPopupStack
[g
.BeginPopupStack
.Size
].Window
)
5650 // FIXME-DPI: Values should be derived from a master "scale" factor.
5651 ImRect next_window_rect
= next_window
->Rect();
5652 ImVec2 ta
= g
.IO
.MousePos
- g
.IO
.MouseDelta
;
5653 ImVec2 tb
= (window
->Pos
.x
< next_window
->Pos
.x
) ? next_window_rect
.GetTL() : next_window_rect
.GetTR();
5654 ImVec2 tc
= (window
->Pos
.x
< next_window
->Pos
.x
) ? next_window_rect
.GetBL() : next_window_rect
.GetBR();
5655 float extra
= ImClamp(ImFabs(ta
.x
- tb
.x
) * 0.30f
, 5.0f
, 30.0f
); // add a bit of extra slack.
5656 ta
.x
+= (window
->Pos
.x
< next_window
->Pos
.x
) ? -0.5f
: +0.5f
; // to avoid numerical issues
5657 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?
5658 tc
.y
= ta
.y
+ ImMin((tc
.y
+ extra
) - ta
.y
, +100.0f
);
5659 moving_within_opened_triangle
= ImTriangleContainsPoint(ta
, tb
, tc
, g
.IO
.MousePos
);
5660 //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
5664 want_close
= (menu_is_open
&& !hovered
&& g
.HoveredWindow
== window
&& g
.HoveredIdPreviousFrame
!= 0 && g
.HoveredIdPreviousFrame
!= id
&& !moving_within_opened_triangle
);
5665 want_open
= (!menu_is_open
&& hovered
&& !moving_within_opened_triangle
) || (!menu_is_open
&& hovered
&& pressed
);
5667 if (g
.NavActivateId
== id
)
5669 want_close
= menu_is_open
;
5670 want_open
= !menu_is_open
;
5672 if (g
.NavId
== id
&& g
.NavMoveRequest
&& g
.NavMoveDir
== ImGuiDir_Right
) // Nav-Right to open
5675 NavMoveRequestCancel();
5681 if (menu_is_open
&& pressed
&& menuset_is_open
) // Click an open menu again to close it
5684 want_open
= menu_is_open
= false;
5686 else if (pressed
|| (hovered
&& menuset_is_open
&& !menu_is_open
)) // First click to open, then hover to open others
5690 else if (g
.NavId
== id
&& g
.NavMoveRequest
&& g
.NavMoveDir
== ImGuiDir_Down
) // Nav-Down to open
5693 NavMoveRequestCancel();
5697 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.. }'
5699 if (want_close
&& IsPopupOpen(id
))
5700 ClosePopupToLevel(g
.BeginPopupStack
.Size
, true);
5702 IMGUI_TEST_ENGINE_ITEM_INFO(id
, label
, window
->DC
.ItemFlags
| ImGuiItemStatusFlags_Openable
| (menu_is_open
? ImGuiItemStatusFlags_Opened
: 0));
5704 if (!menu_is_open
&& want_open
&& g
.OpenPopupStack
.Size
> g
.BeginPopupStack
.Size
)
5706 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
5711 menu_is_open
|= want_open
;
5717 // 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)
5718 SetNextWindowPos(popup_pos
, ImGuiCond_Always
);
5719 ImGuiWindowFlags flags
= ImGuiWindowFlags_ChildMenu
| ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_NoNavFocus
;
5720 if (window
->Flags
& (ImGuiWindowFlags_Popup
|ImGuiWindowFlags_ChildMenu
))
5721 flags
|= ImGuiWindowFlags_ChildWindow
;
5722 menu_is_open
= BeginPopupEx(id
, flags
); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5725 return menu_is_open
;
5728 void ImGui::EndMenu()
5730 // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
5731 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
5732 // 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.
5733 ImGuiContext
& g
= *GImGui
;
5734 ImGuiWindow
* window
= g
.CurrentWindow
;
5735 if (g
.NavWindow
&& g
.NavWindow
->ParentWindow
== window
&& g
.NavMoveDir
== ImGuiDir_Left
&& NavMoveRequestButNoResultYet() && window
->DC
.LayoutType
== ImGuiLayoutType_Vertical
)
5737 ClosePopupToLevel(g
.BeginPopupStack
.Size
, true);
5738 NavMoveRequestCancel();
5744 bool ImGui::MenuItem(const char* label
, const char* shortcut
, bool selected
, bool enabled
)
5746 ImGuiWindow
* window
= GetCurrentWindow();
5747 if (window
->SkipItems
)
5750 ImGuiContext
& g
= *GImGui
;
5751 ImGuiStyle
& style
= g
.Style
;
5752 ImVec2 pos
= window
->DC
.CursorPos
;
5753 ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
5755 ImGuiSelectableFlags flags
= ImGuiSelectableFlags_PressedOnRelease
| (enabled
? 0 : ImGuiSelectableFlags_Disabled
);
5757 if (window
->DC
.LayoutType
== ImGuiLayoutType_Horizontal
)
5759 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
5760 // Note that in this situation we render neither the shortcut neither the selected tick mark
5761 float w
= label_size
.x
;
5762 window
->DC
.CursorPos
.x
+= (float)(int)(style
.ItemSpacing
.x
* 0.5f
);
5763 PushStyleVar(ImGuiStyleVar_ItemSpacing
, style
.ItemSpacing
* 2.0f
);
5764 pressed
= Selectable(label
, false, flags
, ImVec2(w
, 0.0f
));
5766 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().
5770 ImVec2 shortcut_size
= shortcut
? CalcTextSize(shortcut
, NULL
) : ImVec2(0.0f
, 0.0f
);
5771 float w
= window
->MenuColumns
.DeclColumns(label_size
.x
, shortcut_size
.x
, (float)(int)(g
.FontSize
* 1.20f
)); // Feedback for next frame
5772 float extra_w
= ImMax(0.0f
, GetContentRegionAvail().x
- w
);
5773 pressed
= Selectable(label
, false, flags
| ImGuiSelectableFlags_DrawFillAvailWidth
, ImVec2(w
, 0.0f
));
5774 if (shortcut_size
.x
> 0.0f
)
5776 PushStyleColor(ImGuiCol_Text
, g
.Style
.Colors
[ImGuiCol_TextDisabled
]);
5777 RenderText(pos
+ ImVec2(window
->MenuColumns
.Pos
[1] + extra_w
, 0.0f
), shortcut
, NULL
, false);
5781 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
);
5784 IMGUI_TEST_ENGINE_ITEM_INFO(window
->DC
.LastItemId
, label
, window
->DC
.ItemFlags
| ImGuiItemStatusFlags_Checkable
| (selected
? ImGuiItemStatusFlags_Checked
: 0));
5788 bool ImGui::MenuItem(const char* label
, const char* shortcut
, bool* p_selected
, bool enabled
)
5790 if (MenuItem(label
, shortcut
, p_selected
? *p_selected
: false, enabled
))
5793 *p_selected
= !*p_selected
;
5799 //-------------------------------------------------------------------------
5800 // [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
5801 //-------------------------------------------------------------------------
5802 // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
5803 // and some of the construct which are not used in Master may be left here to facilitate merging.
5804 //-------------------------------------------------------------------------
5806 // - BeginTabBarEx() [Internal]
5808 // - TabBarLayout() [Internal]
5809 // - TabBarCalcTabID() [Internal]
5810 // - TabBarCalcMaxTabWidth() [Internal]
5811 // - TabBarFindTabById() [Internal]
5812 // - TabBarRemoveTab() [Internal]
5813 // - TabBarCloseTab() [Internal]
5814 // - TabBarScrollClamp()v
5815 // - TabBarScrollToTab() [Internal]
5816 // - TabBarQueueChangeTabOrder() [Internal]
5817 // - TabBarScrollingButtons() [Internal]
5818 // - TabBarTabListPopupButton() [Internal]
5819 //-------------------------------------------------------------------------
5823 static void TabBarLayout(ImGuiTabBar
* tab_bar
);
5824 static ImU32
TabBarCalcTabID(ImGuiTabBar
* tab_bar
, const char* label
);
5825 static float TabBarCalcMaxTabWidth();
5826 static float TabBarScrollClamp(ImGuiTabBar
* tab_bar
, float scrolling
);
5827 static void TabBarScrollToTab(ImGuiTabBar
* tab_bar
, ImGuiTabItem
* tab
);
5828 static ImGuiTabItem
* TabBarScrollingButtons(ImGuiTabBar
* tab_bar
);
5829 static ImGuiTabItem
* TabBarTabListPopupButton(ImGuiTabBar
* tab_bar
);
5832 ImGuiTabBar::ImGuiTabBar()
5835 SelectedTabId
= NextSelectedTabId
= VisibleTabId
= 0;
5836 CurrFrameVisible
= PrevFrameVisible
= -1;
5837 ContentsHeight
= 0.0f
;
5838 OffsetMax
= OffsetNextTab
= 0.0f
;
5839 ScrollingAnim
= ScrollingTarget
= 0.0f
;
5840 Flags
= ImGuiTabBarFlags_None
;
5841 ReorderRequestTabId
= 0;
5842 ReorderRequestDir
= 0;
5843 WantLayout
= VisibleTabWasSubmitted
= false;
5844 LastTabItemIdx
= -1;
5847 static int IMGUI_CDECL
TabItemComparerByVisibleOffset(const void* lhs
, const void* rhs
)
5849 const ImGuiTabItem
* a
= (const ImGuiTabItem
*)lhs
;
5850 const ImGuiTabItem
* b
= (const ImGuiTabItem
*)rhs
;
5851 return (int)(a
->Offset
- b
->Offset
);
5854 static int IMGUI_CDECL
TabBarSortItemComparer(const void* lhs
, const void* rhs
)
5856 const ImGuiTabBarSortItem
* a
= (const ImGuiTabBarSortItem
*)lhs
;
5857 const ImGuiTabBarSortItem
* b
= (const ImGuiTabBarSortItem
*)rhs
;
5858 if (int d
= (int)(b
->Width
- a
->Width
))
5860 return (b
->Index
- a
->Index
);
5863 bool ImGui::BeginTabBar(const char* str_id
, ImGuiTabBarFlags flags
)
5865 ImGuiContext
& g
= *GImGui
;
5866 ImGuiWindow
* window
= g
.CurrentWindow
;
5867 if (window
->SkipItems
)
5870 ImGuiID id
= window
->GetID(str_id
);
5871 ImGuiTabBar
* tab_bar
= g
.TabBars
.GetOrAddByKey(id
);
5872 ImRect tab_bar_bb
= ImRect(window
->DC
.CursorPos
.x
, window
->DC
.CursorPos
.y
, window
->InnerClipRect
.Max
.x
, window
->DC
.CursorPos
.y
+ g
.FontSize
+ g
.Style
.FramePadding
.y
* 2);
5874 return BeginTabBarEx(tab_bar
, tab_bar_bb
, flags
| ImGuiTabBarFlags_IsFocused
);
5877 bool ImGui::BeginTabBarEx(ImGuiTabBar
* tab_bar
, const ImRect
& tab_bar_bb
, ImGuiTabBarFlags flags
)
5879 ImGuiContext
& g
= *GImGui
;
5880 ImGuiWindow
* window
= g
.CurrentWindow
;
5881 if (window
->SkipItems
)
5884 if ((flags
& ImGuiTabBarFlags_DockNode
) == 0)
5885 window
->IDStack
.push_back(tab_bar
->ID
);
5887 g
.CurrentTabBar
.push_back(tab_bar
);
5888 if (tab_bar
->CurrFrameVisible
== g
.FrameCount
)
5890 //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
5895 // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
5896 // Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user.
5897 if ((flags
& ImGuiTabBarFlags_Reorderable
) && !(tab_bar
->Flags
& ImGuiTabBarFlags_Reorderable
) && tab_bar
->Tabs
.Size
> 1 && tab_bar
->PrevFrameVisible
!= -1)
5898 ImQsort(tab_bar
->Tabs
.Data
, tab_bar
->Tabs
.Size
, sizeof(ImGuiTabItem
), TabItemComparerByVisibleOffset
);
5901 if ((flags
& ImGuiTabBarFlags_FittingPolicyMask_
) == 0)
5902 flags
|= ImGuiTabBarFlags_FittingPolicyDefault_
;
5904 tab_bar
->Flags
= flags
;
5905 tab_bar
->BarRect
= tab_bar_bb
;
5906 tab_bar
->WantLayout
= true; // Layout will be done on the first call to ItemTab()
5907 tab_bar
->PrevFrameVisible
= tab_bar
->CurrFrameVisible
;
5908 tab_bar
->CurrFrameVisible
= g
.FrameCount
;
5909 tab_bar
->FramePadding
= g
.Style
.FramePadding
;
5912 ItemSize(ImVec2(tab_bar
->OffsetMax
, tab_bar
->BarRect
.GetHeight()));
5913 window
->DC
.CursorPos
.x
= tab_bar
->BarRect
.Min
.x
;
5916 const ImU32 col
= GetColorU32((flags
& ImGuiTabBarFlags_IsFocused
) ? ImGuiCol_TabActive
: ImGuiCol_Tab
);
5917 const float y
= tab_bar
->BarRect
.Max
.y
- 1.0f
;
5919 const float separator_min_x
= tab_bar
->BarRect
.Min
.x
- window
->WindowPadding
.x
;
5920 const float separator_max_x
= tab_bar
->BarRect
.Max
.x
+ window
->WindowPadding
.x
;
5921 window
->DrawList
->AddLine(ImVec2(separator_min_x
, y
), ImVec2(separator_max_x
, y
), col
, 1.0f
);
5926 void ImGui::EndTabBar()
5928 ImGuiContext
& g
= *GImGui
;
5929 ImGuiWindow
* window
= g
.CurrentWindow
;
5930 if (window
->SkipItems
)
5933 IM_ASSERT(!g
.CurrentTabBar
.empty()); // Mismatched BeginTabBar/EndTabBar
5934 ImGuiTabBar
* tab_bar
= g
.CurrentTabBar
.back();
5935 if (tab_bar
->WantLayout
)
5936 TabBarLayout(tab_bar
);
5938 // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
5939 const bool tab_bar_appearing
= (tab_bar
->PrevFrameVisible
+ 1 < g
.FrameCount
);
5940 if (tab_bar
->VisibleTabWasSubmitted
|| tab_bar
->VisibleTabId
== 0 || tab_bar_appearing
)
5941 tab_bar
->ContentsHeight
= ImMax(window
->DC
.CursorPos
.y
- tab_bar
->BarRect
.Max
.y
, 0.0f
);
5943 window
->DC
.CursorPos
.y
= tab_bar
->BarRect
.Max
.y
+ tab_bar
->ContentsHeight
;
5945 if ((tab_bar
->Flags
& ImGuiTabBarFlags_DockNode
) == 0)
5947 g
.CurrentTabBar
.pop_back();
5950 // This is called only once a frame before by the first call to ItemTab()
5951 // The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
5952 static void ImGui::TabBarLayout(ImGuiTabBar
* tab_bar
)
5954 ImGuiContext
& g
= *GImGui
;
5955 tab_bar
->WantLayout
= false;
5959 for (int tab_src_n
= 0; tab_src_n
< tab_bar
->Tabs
.Size
; tab_src_n
++)
5961 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_src_n
];
5962 if (tab
->LastFrameVisible
< tab_bar
->PrevFrameVisible
)
5964 if (tab
->ID
== tab_bar
->SelectedTabId
)
5965 tab_bar
->SelectedTabId
= 0;
5968 if (tab_dst_n
!= tab_src_n
)
5969 tab_bar
->Tabs
[tab_dst_n
] = tab_bar
->Tabs
[tab_src_n
];
5972 if (tab_bar
->Tabs
.Size
!= tab_dst_n
)
5973 tab_bar
->Tabs
.resize(tab_dst_n
);
5975 // Setup next selected tab
5976 ImGuiID scroll_track_selected_tab_id
= 0;
5977 if (tab_bar
->NextSelectedTabId
)
5979 tab_bar
->SelectedTabId
= tab_bar
->NextSelectedTabId
;
5980 tab_bar
->NextSelectedTabId
= 0;
5981 scroll_track_selected_tab_id
= tab_bar
->SelectedTabId
;
5984 // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
5985 if (tab_bar
->ReorderRequestTabId
!= 0)
5987 if (ImGuiTabItem
* tab1
= TabBarFindTabByID(tab_bar
, tab_bar
->ReorderRequestTabId
))
5989 //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
5990 int tab2_order
= tab_bar
->GetTabOrder(tab1
) + tab_bar
->ReorderRequestDir
;
5991 if (tab2_order
>= 0 && tab2_order
< tab_bar
->Tabs
.Size
)
5993 ImGuiTabItem
* tab2
= &tab_bar
->Tabs
[tab2_order
];
5994 ImGuiTabItem item_tmp
= *tab1
;
5997 if (tab2
->ID
== tab_bar
->SelectedTabId
)
5998 scroll_track_selected_tab_id
= tab2
->ID
;
6001 if (tab_bar
->Flags
& ImGuiTabBarFlags_SaveSettings
)
6002 MarkIniSettingsDirty();
6004 tab_bar
->ReorderRequestTabId
= 0;
6007 // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
6008 const bool tab_list_popup_button
= (tab_bar
->Flags
& ImGuiTabBarFlags_TabListPopupButton
) != 0;
6009 if (tab_list_popup_button
)
6010 if (ImGuiTabItem
* tab_to_select
= TabBarTabListPopupButton(tab_bar
)) // NB: Will alter BarRect.Max.x!
6011 scroll_track_selected_tab_id
= tab_bar
->SelectedTabId
= tab_to_select
->ID
;
6013 ImVector
<ImGuiTabBarSortItem
>& width_sort_buffer
= g
.TabSortByWidthBuffer
;
6014 width_sort_buffer
.resize(tab_bar
->Tabs
.Size
);
6016 // Compute ideal widths
6017 float width_total_contents
= 0.0f
;
6018 ImGuiTabItem
* most_recently_selected_tab
= NULL
;
6019 bool found_selected_tab_id
= false;
6020 for (int tab_n
= 0; tab_n
< tab_bar
->Tabs
.Size
; tab_n
++)
6022 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_n
];
6023 IM_ASSERT(tab
->LastFrameVisible
>= tab_bar
->PrevFrameVisible
);
6025 if (most_recently_selected_tab
== NULL
|| most_recently_selected_tab
->LastFrameSelected
< tab
->LastFrameSelected
)
6026 most_recently_selected_tab
= tab
;
6027 if (tab
->ID
== tab_bar
->SelectedTabId
)
6028 found_selected_tab_id
= true;
6030 // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
6031 // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
6032 // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
6033 const char* tab_name
= tab_bar
->GetTabName(tab
);
6034 tab
->WidthContents
= TabItemCalcSize(tab_name
, (tab
->Flags
& ImGuiTabItemFlags_NoCloseButton
) ? false : true).x
;
6036 width_total_contents
+= (tab_n
> 0 ? g
.Style
.ItemInnerSpacing
.x
: 0.0f
) + tab
->WidthContents
;
6038 // Store data so we can build an array sorted by width if we need to shrink tabs down
6039 width_sort_buffer
[tab_n
].Index
= tab_n
;
6040 width_sort_buffer
[tab_n
].Width
= tab
->WidthContents
;
6044 const float width_avail
= tab_bar
->BarRect
.GetWidth();
6045 float width_excess
= (width_avail
< width_total_contents
) ? (width_total_contents
- width_avail
) : 0.0f
;
6046 if (width_excess
> 0.0f
&& (tab_bar
->Flags
& ImGuiTabBarFlags_FittingPolicyResizeDown
))
6048 // If we don't have enough room, resize down the largest tabs first
6049 if (tab_bar
->Tabs
.Size
> 1)
6050 ImQsort(width_sort_buffer
.Data
, (size_t)width_sort_buffer
.Size
, sizeof(ImGuiTabBarSortItem
), TabBarSortItemComparer
);
6051 int tab_count_same_width
= 1;
6052 while (width_excess
> 0.0f
&& tab_count_same_width
< tab_bar
->Tabs
.Size
)
6054 while (tab_count_same_width
< tab_bar
->Tabs
.Size
&& width_sort_buffer
[0].Width
== width_sort_buffer
[tab_count_same_width
].Width
)
6055 tab_count_same_width
++;
6056 float width_to_remove_per_tab_max
= (tab_count_same_width
< tab_bar
->Tabs
.Size
) ? (width_sort_buffer
[0].Width
- width_sort_buffer
[tab_count_same_width
].Width
) : (width_sort_buffer
[0].Width
- 1.0f
);
6057 float width_to_remove_per_tab
= ImMin(width_excess
/ tab_count_same_width
, width_to_remove_per_tab_max
);
6058 for (int tab_n
= 0; tab_n
< tab_count_same_width
; tab_n
++)
6059 width_sort_buffer
[tab_n
].Width
-= width_to_remove_per_tab
;
6060 width_excess
-= width_to_remove_per_tab
* tab_count_same_width
;
6062 for (int tab_n
= 0; tab_n
< tab_bar
->Tabs
.Size
; tab_n
++)
6063 tab_bar
->Tabs
[width_sort_buffer
[tab_n
].Index
].Width
= (float)(int)width_sort_buffer
[tab_n
].Width
;
6067 const float tab_max_width
= TabBarCalcMaxTabWidth();
6068 for (int tab_n
= 0; tab_n
< tab_bar
->Tabs
.Size
; tab_n
++)
6070 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_n
];
6071 tab
->Width
= ImMin(tab
->WidthContents
, tab_max_width
);
6075 // Layout all active tabs
6076 float offset_x
= 0.0f
;
6077 for (int tab_n
= 0; tab_n
< tab_bar
->Tabs
.Size
; tab_n
++)
6079 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_n
];
6080 tab
->Offset
= offset_x
;
6081 if (scroll_track_selected_tab_id
== 0 && g
.NavJustMovedToId
== tab
->ID
)
6082 scroll_track_selected_tab_id
= tab
->ID
;
6083 offset_x
+= tab
->Width
+ g
.Style
.ItemInnerSpacing
.x
;
6085 tab_bar
->OffsetMax
= ImMax(offset_x
- g
.Style
.ItemInnerSpacing
.x
, 0.0f
);
6086 tab_bar
->OffsetNextTab
= 0.0f
;
6088 // Horizontal scrolling buttons
6089 const bool scrolling_buttons
= (tab_bar
->OffsetMax
> tab_bar
->BarRect
.GetWidth() && tab_bar
->Tabs
.Size
> 1) && !(tab_bar
->Flags
& ImGuiTabBarFlags_NoTabListScrollingButtons
) && (tab_bar
->Flags
& ImGuiTabBarFlags_FittingPolicyScroll
);
6090 if (scrolling_buttons
)
6091 if (ImGuiTabItem
* tab_to_select
= TabBarScrollingButtons(tab_bar
)) // NB: Will alter BarRect.Max.x!
6092 scroll_track_selected_tab_id
= tab_bar
->SelectedTabId
= tab_to_select
->ID
;
6094 // If we have lost the selected tab, select the next most recently active one
6095 if (found_selected_tab_id
== false)
6096 tab_bar
->SelectedTabId
= 0;
6097 if (tab_bar
->SelectedTabId
== 0 && tab_bar
->NextSelectedTabId
== 0 && most_recently_selected_tab
!= NULL
)
6098 scroll_track_selected_tab_id
= tab_bar
->SelectedTabId
= most_recently_selected_tab
->ID
;
6100 // Lock in visible tab
6101 tab_bar
->VisibleTabId
= tab_bar
->SelectedTabId
;
6102 tab_bar
->VisibleTabWasSubmitted
= false;
6105 if (scroll_track_selected_tab_id
)
6106 if (ImGuiTabItem
* scroll_track_selected_tab
= TabBarFindTabByID(tab_bar
, scroll_track_selected_tab_id
))
6107 TabBarScrollToTab(tab_bar
, scroll_track_selected_tab
);
6108 tab_bar
->ScrollingAnim
= TabBarScrollClamp(tab_bar
, tab_bar
->ScrollingAnim
);
6109 tab_bar
->ScrollingTarget
= TabBarScrollClamp(tab_bar
, tab_bar
->ScrollingTarget
);
6110 const float scrolling_speed
= (tab_bar
->PrevFrameVisible
+ 1 < g
.FrameCount
) ? FLT_MAX
: (g
.IO
.DeltaTime
* g
.FontSize
* 70.0f
);
6111 if (tab_bar
->ScrollingAnim
!= tab_bar
->ScrollingTarget
)
6112 tab_bar
->ScrollingAnim
= ImLinearSweep(tab_bar
->ScrollingAnim
, tab_bar
->ScrollingTarget
, scrolling_speed
);
6114 // Clear name buffers
6115 if ((tab_bar
->Flags
& ImGuiTabBarFlags_DockNode
) == 0)
6116 tab_bar
->TabsNames
.Buf
.resize(0);
6119 // Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
6120 static ImU32
ImGui::TabBarCalcTabID(ImGuiTabBar
* tab_bar
, const char* label
)
6122 if (tab_bar
->Flags
& ImGuiTabBarFlags_DockNode
)
6124 ImGuiID id
= ImHashStr(label
, 0);
6130 ImGuiWindow
* window
= GImGui
->CurrentWindow
;
6131 return window
->GetID(label
);
6135 static float ImGui::TabBarCalcMaxTabWidth()
6137 ImGuiContext
& g
= *GImGui
;
6138 return g
.FontSize
* 20.0f
;
6141 ImGuiTabItem
* ImGui::TabBarFindTabByID(ImGuiTabBar
* tab_bar
, ImGuiID tab_id
)
6144 for (int n
= 0; n
< tab_bar
->Tabs
.Size
; n
++)
6145 if (tab_bar
->Tabs
[n
].ID
== tab_id
)
6146 return &tab_bar
->Tabs
[n
];
6150 // The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
6151 void ImGui::TabBarRemoveTab(ImGuiTabBar
* tab_bar
, ImGuiID tab_id
)
6153 if (ImGuiTabItem
* tab
= TabBarFindTabByID(tab_bar
, tab_id
))
6154 tab_bar
->Tabs
.erase(tab
);
6155 if (tab_bar
->VisibleTabId
== tab_id
) { tab_bar
->VisibleTabId
= 0; }
6156 if (tab_bar
->SelectedTabId
== tab_id
) { tab_bar
->SelectedTabId
= 0; }
6157 if (tab_bar
->NextSelectedTabId
== tab_id
) { tab_bar
->NextSelectedTabId
= 0; }
6160 // Called on manual closure attempt
6161 void ImGui::TabBarCloseTab(ImGuiTabBar
* tab_bar
, ImGuiTabItem
* tab
)
6163 if ((tab_bar
->VisibleTabId
== tab
->ID
) && !(tab
->Flags
& ImGuiTabItemFlags_UnsavedDocument
))
6165 // This will remove a frame of lag for selecting another tab on closure.
6166 // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
6167 tab
->LastFrameVisible
= -1;
6168 tab_bar
->SelectedTabId
= tab_bar
->NextSelectedTabId
= 0;
6170 else if ((tab_bar
->VisibleTabId
!= tab
->ID
) && (tab
->Flags
& ImGuiTabItemFlags_UnsavedDocument
))
6172 // Actually select before expecting closure
6173 tab_bar
->NextSelectedTabId
= tab
->ID
;
6177 static float ImGui::TabBarScrollClamp(ImGuiTabBar
* tab_bar
, float scrolling
)
6179 scrolling
= ImMin(scrolling
, tab_bar
->OffsetMax
- tab_bar
->BarRect
.GetWidth());
6180 return ImMax(scrolling
, 0.0f
);
6183 static void ImGui::TabBarScrollToTab(ImGuiTabBar
* tab_bar
, ImGuiTabItem
* tab
)
6185 ImGuiContext
& g
= *GImGui
;
6186 float margin
= g
.FontSize
* 1.0f
; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
6187 int order
= tab_bar
->GetTabOrder(tab
);
6188 float tab_x1
= tab
->Offset
+ (order
> 0 ? -margin
: 0.0f
);
6189 float tab_x2
= tab
->Offset
+ tab
->Width
+ (order
+ 1 < tab_bar
->Tabs
.Size
? margin
: 1.0f
);
6190 if (tab_bar
->ScrollingTarget
> tab_x1
)
6191 tab_bar
->ScrollingTarget
= tab_x1
;
6192 if (tab_bar
->ScrollingTarget
+ tab_bar
->BarRect
.GetWidth() < tab_x2
)
6193 tab_bar
->ScrollingTarget
= tab_x2
- tab_bar
->BarRect
.GetWidth();
6196 void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar
* tab_bar
, const ImGuiTabItem
* tab
, int dir
)
6198 IM_ASSERT(dir
== -1 || dir
== +1);
6199 IM_ASSERT(tab_bar
->ReorderRequestTabId
== 0);
6200 tab_bar
->ReorderRequestTabId
= tab
->ID
;
6201 tab_bar
->ReorderRequestDir
= dir
;
6204 static ImGuiTabItem
* ImGui::TabBarScrollingButtons(ImGuiTabBar
* tab_bar
)
6206 ImGuiContext
& g
= *GImGui
;
6207 ImGuiWindow
* window
= g
.CurrentWindow
;
6209 const ImVec2
arrow_button_size(g
.FontSize
- 2.0f
, g
.FontSize
+ g
.Style
.FramePadding
.y
* 2.0f
);
6210 const float scrolling_buttons_width
= arrow_button_size
.x
* 2.0f
;
6212 const ImVec2 backup_cursor_pos
= window
->DC
.CursorPos
;
6213 //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
6215 const ImRect avail_bar_rect
= tab_bar
->BarRect
;
6216 bool want_clip_rect
= !avail_bar_rect
.Contains(ImRect(window
->DC
.CursorPos
, window
->DC
.CursorPos
+ ImVec2(scrolling_buttons_width
, 0.0f
)));
6218 PushClipRect(tab_bar
->BarRect
.Min
, tab_bar
->BarRect
.Max
+ ImVec2(g
.Style
.ItemInnerSpacing
.x
, 0.0f
), true);
6220 ImGuiTabItem
* tab_to_select
= NULL
;
6223 ImVec4 arrow_col
= g
.Style
.Colors
[ImGuiCol_Text
];
6224 arrow_col
.w
*= 0.5f
;
6226 PushStyleColor(ImGuiCol_Text
, arrow_col
);
6227 PushStyleColor(ImGuiCol_Button
, ImVec4(0, 0, 0, 0));
6228 const float backup_repeat_delay
= g
.IO
.KeyRepeatDelay
;
6229 const float backup_repeat_rate
= g
.IO
.KeyRepeatRate
;
6230 g
.IO
.KeyRepeatDelay
= 0.250f
;
6231 g
.IO
.KeyRepeatRate
= 0.200f
;
6232 window
->DC
.CursorPos
= ImVec2(tab_bar
->BarRect
.Max
.x
- scrolling_buttons_width
, tab_bar
->BarRect
.Min
.y
);
6233 if (ArrowButtonEx("##<", ImGuiDir_Left
, arrow_button_size
, ImGuiButtonFlags_PressedOnClick
| ImGuiButtonFlags_Repeat
))
6235 window
->DC
.CursorPos
= ImVec2(tab_bar
->BarRect
.Max
.x
- scrolling_buttons_width
+ arrow_button_size
.x
, tab_bar
->BarRect
.Min
.y
);
6236 if (ArrowButtonEx("##>", ImGuiDir_Right
, arrow_button_size
, ImGuiButtonFlags_PressedOnClick
| ImGuiButtonFlags_Repeat
))
6239 g
.IO
.KeyRepeatRate
= backup_repeat_rate
;
6240 g
.IO
.KeyRepeatDelay
= backup_repeat_delay
;
6245 if (select_dir
!= 0)
6246 if (ImGuiTabItem
* tab_item
= TabBarFindTabByID(tab_bar
, tab_bar
->SelectedTabId
))
6248 int selected_order
= tab_bar
->GetTabOrder(tab_item
);
6249 int target_order
= selected_order
+ select_dir
;
6250 tab_to_select
= &tab_bar
->Tabs
[(target_order
>= 0 && target_order
< tab_bar
->Tabs
.Size
) ? target_order
: selected_order
]; // If we are at the end of the list, still scroll to make our tab visible
6252 window
->DC
.CursorPos
= backup_cursor_pos
;
6253 tab_bar
->BarRect
.Max
.x
-= scrolling_buttons_width
+ 1.0f
;
6255 return tab_to_select
;
6258 static ImGuiTabItem
* ImGui::TabBarTabListPopupButton(ImGuiTabBar
* tab_bar
)
6260 ImGuiContext
& g
= *GImGui
;
6261 ImGuiWindow
* window
= g
.CurrentWindow
;
6263 // We use g.Style.FramePadding.y to match the square ArrowButton size
6264 const float tab_list_popup_button_width
= g
.FontSize
+ g
.Style
.FramePadding
.y
;
6265 const ImVec2 backup_cursor_pos
= window
->DC
.CursorPos
;
6266 window
->DC
.CursorPos
= ImVec2(tab_bar
->BarRect
.Min
.x
- g
.Style
.FramePadding
.y
, tab_bar
->BarRect
.Min
.y
);
6267 tab_bar
->BarRect
.Min
.x
+= tab_list_popup_button_width
;
6269 ImVec4 arrow_col
= g
.Style
.Colors
[ImGuiCol_Text
];
6270 arrow_col
.w
*= 0.5f
;
6271 PushStyleColor(ImGuiCol_Text
, arrow_col
);
6272 PushStyleColor(ImGuiCol_Button
, ImVec4(0, 0, 0, 0));
6273 bool open
= BeginCombo("##v", NULL
, ImGuiComboFlags_NoPreview
);
6276 ImGuiTabItem
* tab_to_select
= NULL
;
6279 for (int tab_n
= 0; tab_n
< tab_bar
->Tabs
.Size
; tab_n
++)
6281 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_n
];
6282 const char* tab_name
= tab_bar
->GetTabName(tab
);
6283 if (Selectable(tab_name
, tab_bar
->SelectedTabId
== tab
->ID
))
6284 tab_to_select
= tab
;
6289 window
->DC
.CursorPos
= backup_cursor_pos
;
6290 return tab_to_select
;
6293 //-------------------------------------------------------------------------
6294 // [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
6295 //-------------------------------------------------------------------------
6296 // [BETA API] API may evolve! This code has been extracted out of the Docking branch,
6297 // and some of the construct which are not used in Master may be left here to facilitate merging.
6298 //-------------------------------------------------------------------------
6301 // - TabItemEx() [Internal]
6302 // - SetTabItemClosed()
6303 // - TabItemCalcSize() [Internal]
6304 // - TabItemBackground() [Internal]
6305 // - TabItemLabelAndCloseButton() [Internal]
6306 //-------------------------------------------------------------------------
6308 bool ImGui::BeginTabItem(const char* label
, bool* p_open
, ImGuiTabItemFlags flags
)
6310 ImGuiContext
& g
= *GImGui
;
6311 if (g
.CurrentWindow
->SkipItems
)
6314 IM_ASSERT(g
.CurrentTabBar
.Size
> 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6315 ImGuiTabBar
* tab_bar
= g
.CurrentTabBar
.back();
6316 bool ret
= TabItemEx(tab_bar
, label
, p_open
, flags
);
6317 if (ret
&& !(flags
& ImGuiTabItemFlags_NoPushId
))
6319 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_bar
->LastTabItemIdx
];
6320 g
.CurrentWindow
->IDStack
.push_back(tab
->ID
); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
6325 void ImGui::EndTabItem()
6327 ImGuiContext
& g
= *GImGui
;
6328 if (g
.CurrentWindow
->SkipItems
)
6331 IM_ASSERT(g
.CurrentTabBar
.Size
> 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6332 ImGuiTabBar
* tab_bar
= g
.CurrentTabBar
.back();
6333 IM_ASSERT(tab_bar
->LastTabItemIdx
>= 0 && "Needs to be called between BeginTabItem() and EndTabItem()");
6334 ImGuiTabItem
* tab
= &tab_bar
->Tabs
[tab_bar
->LastTabItemIdx
];
6335 if (!(tab
->Flags
& ImGuiTabItemFlags_NoPushId
))
6336 g
.CurrentWindow
->IDStack
.pop_back();
6339 bool ImGui::TabItemEx(ImGuiTabBar
* tab_bar
, const char* label
, bool* p_open
, ImGuiTabItemFlags flags
)
6341 // Layout whole tab bar if not already done
6342 if (tab_bar
->WantLayout
)
6343 TabBarLayout(tab_bar
);
6345 ImGuiContext
& g
= *GImGui
;
6346 ImGuiWindow
* window
= g
.CurrentWindow
;
6347 if (window
->SkipItems
)
6350 const ImGuiStyle
& style
= g
.Style
;
6351 const ImGuiID id
= TabBarCalcTabID(tab_bar
, label
);
6353 // If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
6354 if (p_open
&& !*p_open
)
6356 PushItemFlag(ImGuiItemFlags_NoNav
| ImGuiItemFlags_NoNavDefaultFocus
, true);
6357 ItemAdd(ImRect(), id
);
6362 // Calculate tab contents size
6363 ImVec2 size
= TabItemCalcSize(label
, p_open
!= NULL
);
6366 ImGuiTabItem
* tab
= TabBarFindTabByID(tab_bar
, id
);
6367 bool tab_is_new
= false;
6370 tab_bar
->Tabs
.push_back(ImGuiTabItem());
6371 tab
= &tab_bar
->Tabs
.back();
6373 tab
->Width
= size
.x
;
6376 tab_bar
->LastTabItemIdx
= (short)tab_bar
->Tabs
.index_from_ptr(tab
);
6377 tab
->WidthContents
= size
.x
;
6380 flags
|= ImGuiTabItemFlags_NoCloseButton
;
6382 const bool tab_bar_appearing
= (tab_bar
->PrevFrameVisible
+ 1 < g
.FrameCount
);
6383 const bool tab_bar_focused
= (tab_bar
->Flags
& ImGuiTabBarFlags_IsFocused
) != 0;
6384 const bool tab_appearing
= (tab
->LastFrameVisible
+ 1 < g
.FrameCount
);
6385 tab
->LastFrameVisible
= g
.FrameCount
;
6388 // Append name with zero-terminator
6389 tab
->NameOffset
= tab_bar
->TabsNames
.size();
6390 tab_bar
->TabsNames
.append(label
, label
+ strlen(label
) + 1);
6392 // If we are not reorderable, always reset offset based on submission order.
6393 // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
6394 if (!tab_appearing
&& !(tab_bar
->Flags
& ImGuiTabBarFlags_Reorderable
))
6396 tab
->Offset
= tab_bar
->OffsetNextTab
;
6397 tab_bar
->OffsetNextTab
+= tab
->Width
+ g
.Style
.ItemInnerSpacing
.x
;
6400 // Update selected tab
6401 if (tab_appearing
&& (tab_bar
->Flags
& ImGuiTabBarFlags_AutoSelectNewTabs
) && tab_bar
->NextSelectedTabId
== 0)
6402 if (!tab_bar_appearing
|| tab_bar
->SelectedTabId
== 0)
6403 tab_bar
->NextSelectedTabId
= id
; // New tabs gets activated
6406 bool tab_contents_visible
= (tab_bar
->VisibleTabId
== id
);
6407 if (tab_contents_visible
)
6408 tab_bar
->VisibleTabWasSubmitted
= true;
6410 // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
6411 if (!tab_contents_visible
&& tab_bar
->SelectedTabId
== 0 && tab_bar_appearing
)
6412 if (tab_bar
->Tabs
.Size
== 1 && !(tab_bar
->Flags
& ImGuiTabBarFlags_AutoSelectNewTabs
))
6413 tab_contents_visible
= true;
6415 if (tab_appearing
&& !(tab_bar_appearing
&& !tab_is_new
))
6417 PushItemFlag(ImGuiItemFlags_NoNav
| ImGuiItemFlags_NoNavDefaultFocus
, true);
6418 ItemAdd(ImRect(), id
);
6420 return tab_contents_visible
;
6423 if (tab_bar
->SelectedTabId
== id
)
6424 tab
->LastFrameSelected
= g
.FrameCount
;
6426 // Backup current layout position
6427 const ImVec2 backup_main_cursor_pos
= window
->DC
.CursorPos
;
6430 size
.x
= tab
->Width
;
6431 window
->DC
.CursorPos
= tab_bar
->BarRect
.Min
+ ImVec2((float)(int)tab
->Offset
- tab_bar
->ScrollingAnim
, 0.0f
);
6432 ImVec2 pos
= window
->DC
.CursorPos
;
6433 ImRect
bb(pos
, pos
+ size
);
6435 // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
6436 bool want_clip_rect
= (bb
.Min
.x
< tab_bar
->BarRect
.Min
.x
) || (bb
.Max
.x
>= tab_bar
->BarRect
.Max
.x
);
6438 PushClipRect(ImVec2(ImMax(bb
.Min
.x
, tab_bar
->BarRect
.Min
.x
), bb
.Min
.y
- 1), ImVec2(tab_bar
->BarRect
.Max
.x
, bb
.Max
.y
), true);
6440 ItemSize(bb
, style
.FramePadding
.y
);
6441 if (!ItemAdd(bb
, id
))
6445 window
->DC
.CursorPos
= backup_main_cursor_pos
;
6446 return tab_contents_visible
;
6449 // Click to Select a tab
6450 ImGuiButtonFlags button_flags
= (ImGuiButtonFlags_PressedOnClick
| ImGuiButtonFlags_AllowItemOverlap
);
6451 if (g
.DragDropActive
)
6452 button_flags
|= ImGuiButtonFlags_PressedOnDragDropHold
;
6454 bool pressed
= ButtonBehavior(bb
, id
, &hovered
, &held
, button_flags
);
6455 hovered
|= (g
.HoveredId
== id
);
6456 if (pressed
|| ((flags
& ImGuiTabItemFlags_SetSelected
) && !tab_contents_visible
)) // SetSelected can only be passed on explicit tab bar
6457 tab_bar
->NextSelectedTabId
= id
;
6459 // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
6461 SetItemAllowOverlap();
6463 // Drag and drop: re-order tabs
6464 if (held
&& !tab_appearing
&& IsMouseDragging(0))
6466 if (!g
.DragDropActive
&& (tab_bar
->Flags
& ImGuiTabBarFlags_Reorderable
))
6468 // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
6469 if (g
.IO
.MouseDelta
.x
< 0.0f
&& g
.IO
.MousePos
.x
< bb
.Min
.x
)
6471 if (tab_bar
->Flags
& ImGuiTabBarFlags_Reorderable
)
6472 TabBarQueueChangeTabOrder(tab_bar
, tab
, -1);
6474 else if (g
.IO
.MouseDelta
.x
> 0.0f
&& g
.IO
.MousePos
.x
> bb
.Max
.x
)
6476 if (tab_bar
->Flags
& ImGuiTabBarFlags_Reorderable
)
6477 TabBarQueueChangeTabOrder(tab_bar
, tab
, +1);
6483 if (hovered
&& g
.HoveredIdNotActiveTimer
> 0.50f
&& bb
.GetWidth() < tab
->WidthContents
)
6485 // Enlarge tab display when hovering
6486 bb
.Max
.x
= bb
.Min
.x
+ (float)(int)ImLerp(bb
.GetWidth(), tab
->WidthContents
, ImSaturate((g
.HoveredIdNotActiveTimer
- 0.40f
) * 6.0f
));
6487 display_draw_list
= GetOverlayDrawList(window
);
6488 TabItemBackground(display_draw_list
, bb
, flags
, GetColorU32(ImGuiCol_TitleBgActive
));
6493 ImDrawList
* display_draw_list
= window
->DrawList
;
6494 const ImU32 tab_col
= GetColorU32((held
|| hovered
) ? ImGuiCol_TabHovered
: tab_contents_visible
? (tab_bar_focused
? ImGuiCol_TabActive
: ImGuiCol_TabUnfocusedActive
) : (tab_bar_focused
? ImGuiCol_Tab
: ImGuiCol_TabUnfocused
));
6495 TabItemBackground(display_draw_list
, bb
, flags
, tab_col
);
6496 RenderNavHighlight(bb
, id
);
6498 // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
6499 const bool hovered_unblocked
= IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup
);
6500 if (hovered_unblocked
&& (IsMouseClicked(1) || IsMouseReleased(1)))
6501 tab_bar
->NextSelectedTabId
= id
;
6503 if (tab_bar
->Flags
& ImGuiTabBarFlags_NoCloseWithMiddleMouseButton
)
6504 flags
|= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton
;
6506 // Render tab label, process close button
6507 const ImGuiID close_button_id
= p_open
? window
->GetID((void*)((intptr_t)id
+ 1)) : 0;
6508 bool just_closed
= TabItemLabelAndCloseButton(display_draw_list
, bb
, flags
, tab_bar
->FramePadding
, label
, id
, close_button_id
);
6509 if (just_closed
&& p_open
!= NULL
)
6512 TabBarCloseTab(tab_bar
, tab
);
6515 // Restore main window position so user can draw there
6518 window
->DC
.CursorPos
= backup_main_cursor_pos
;
6520 // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
6521 if (g
.HoveredId
== id
&& !held
&& g
.HoveredIdNotActiveTimer
> 0.50f
)
6522 if (!(tab_bar
->Flags
& ImGuiTabBarFlags_NoTooltip
))
6523 SetTooltip("%.*s", (int)(FindRenderedTextEnd(label
) - label
), label
);
6525 return tab_contents_visible
;
6528 // [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
6529 // To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
6530 void ImGui::SetTabItemClosed(const char* label
)
6532 ImGuiContext
& g
= *GImGui
;
6533 bool is_within_manual_tab_bar
= (g
.CurrentTabBar
.Size
> 0) && !(g
.CurrentTabBar
.back()->Flags
& ImGuiTabBarFlags_DockNode
);
6534 if (is_within_manual_tab_bar
)
6536 ImGuiTabBar
* tab_bar
= g
.CurrentTabBar
.back();
6537 IM_ASSERT(tab_bar
->WantLayout
); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
6538 ImGuiID tab_id
= TabBarCalcTabID(tab_bar
, label
);
6539 TabBarRemoveTab(tab_bar
, tab_id
);
6543 ImVec2
ImGui::TabItemCalcSize(const char* label
, bool has_close_button
)
6545 ImGuiContext
& g
= *GImGui
;
6546 ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
6547 ImVec2 size
= ImVec2(label_size
.x
+ g
.Style
.FramePadding
.x
, label_size
.y
+ g
.Style
.FramePadding
.y
* 2.0f
);
6548 if (has_close_button
)
6549 size
.x
+= g
.Style
.FramePadding
.x
+ (g
.Style
.ItemInnerSpacing
.x
+ g
.FontSize
); // We use Y intentionally to fit the close button circle.
6551 size
.x
+= g
.Style
.FramePadding
.x
+ 1.0f
;
6552 return ImVec2(ImMin(size
.x
, TabBarCalcMaxTabWidth()), size
.y
);
6555 void ImGui::TabItemBackground(ImDrawList
* draw_list
, const ImRect
& bb
, ImGuiTabItemFlags flags
, ImU32 col
)
6557 // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
6558 ImGuiContext
& g
= *GImGui
;
6559 const float width
= bb
.GetWidth();
6561 IM_ASSERT(width
> 0.0f
);
6562 const float rounding
= ImMax(0.0f
, ImMin(g
.Style
.TabRounding
, width
* 0.5f
- 1.0f
));
6563 const float y1
= bb
.Min
.y
+ 1.0f
;
6564 const float y2
= bb
.Max
.y
- 1.0f
;
6565 draw_list
->PathLineTo(ImVec2(bb
.Min
.x
, y2
));
6566 draw_list
->PathArcToFast(ImVec2(bb
.Min
.x
+ rounding
, y1
+ rounding
), rounding
, 6, 9);
6567 draw_list
->PathArcToFast(ImVec2(bb
.Max
.x
- rounding
, y1
+ rounding
), rounding
, 9, 12);
6568 draw_list
->PathLineTo(ImVec2(bb
.Max
.x
, y2
));
6569 draw_list
->PathFillConvex(col
);
6570 if (g
.Style
.TabBorderSize
> 0.0f
)
6572 draw_list
->PathLineTo(ImVec2(bb
.Min
.x
+ 0.5f
, y2
));
6573 draw_list
->PathArcToFast(ImVec2(bb
.Min
.x
+ rounding
+ 0.5f
, y1
+ rounding
+ 0.5f
), rounding
, 6, 9);
6574 draw_list
->PathArcToFast(ImVec2(bb
.Max
.x
- rounding
- 0.5f
, y1
+ rounding
+ 0.5f
), rounding
, 9, 12);
6575 draw_list
->PathLineTo(ImVec2(bb
.Max
.x
- 0.5f
, y2
));
6576 draw_list
->PathStroke(GetColorU32(ImGuiCol_Border
), false, g
.Style
.TabBorderSize
);
6580 // Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
6581 // We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
6582 bool ImGui::TabItemLabelAndCloseButton(ImDrawList
* draw_list
, const ImRect
& bb
, ImGuiTabItemFlags flags
, ImVec2 frame_padding
, const char* label
, ImGuiID tab_id
, ImGuiID close_button_id
)
6584 ImGuiContext
& g
= *GImGui
;
6585 ImVec2 label_size
= CalcTextSize(label
, NULL
, true);
6586 if (bb
.GetWidth() <= 1.0f
)
6589 // Render text label (with clipping + alpha gradient) + unsaved marker
6590 const char* TAB_UNSAVED_MARKER
= "*";
6591 ImRect
text_pixel_clip_bb(bb
.Min
.x
+ frame_padding
.x
, bb
.Min
.y
+ frame_padding
.y
, bb
.Max
.x
- frame_padding
.x
, bb
.Max
.y
);
6592 if (flags
& ImGuiTabItemFlags_UnsavedDocument
)
6594 text_pixel_clip_bb
.Max
.x
-= CalcTextSize(TAB_UNSAVED_MARKER
, NULL
, false).x
;
6595 ImVec2
unsaved_marker_pos(ImMin(bb
.Min
.x
+ frame_padding
.x
+ label_size
.x
+ 2, text_pixel_clip_bb
.Max
.x
), bb
.Min
.y
+ frame_padding
.y
+ (float)(int)(-g
.FontSize
* 0.25f
));
6596 RenderTextClippedEx(draw_list
, unsaved_marker_pos
, bb
.Max
- frame_padding
, TAB_UNSAVED_MARKER
, NULL
, NULL
);
6598 ImRect text_ellipsis_clip_bb
= text_pixel_clip_bb
;
6601 // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
6602 // 'hovered' will be true when hovering the Tab but NOT when hovering the close button
6603 // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
6604 // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
6605 bool close_button_pressed
= false;
6606 bool close_button_visible
= false;
6607 if (close_button_id
!= 0)
6608 if (g
.HoveredId
== tab_id
|| g
.HoveredId
== close_button_id
|| g
.ActiveId
== close_button_id
)
6609 close_button_visible
= true;
6610 if (close_button_visible
)
6612 ImGuiItemHoveredDataBackup last_item_backup
;
6613 const float close_button_sz
= g
.FontSize
* 0.5f
;
6614 if (CloseButton(close_button_id
, ImVec2(bb
.Max
.x
- frame_padding
.x
- close_button_sz
, bb
.Min
.y
+ frame_padding
.y
+ close_button_sz
), close_button_sz
))
6615 close_button_pressed
= true;
6616 last_item_backup
.Restore();
6618 // Close with middle mouse button
6619 if (!(flags
& ImGuiTabItemFlags_NoCloseWithMiddleMouseButton
) && IsMouseClicked(2))
6620 close_button_pressed
= true;
6622 text_pixel_clip_bb
.Max
.x
-= close_button_sz
* 2.0f
;
6625 // Label with ellipsis
6626 // FIXME: This should be extracted into a helper but the use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment
6627 const char* label_display_end
= FindRenderedTextEnd(label
);
6628 if (label_size
.x
> text_ellipsis_clip_bb
.GetWidth())
6630 const int ellipsis_dot_count
= 3;
6631 const float ellipsis_width
= (1.0f
+ 1.0f
) * ellipsis_dot_count
- 1.0f
;
6632 const char* label_end
= NULL
;
6633 float label_size_clipped_x
= g
.Font
->CalcTextSizeA(g
.FontSize
, text_ellipsis_clip_bb
.GetWidth() - ellipsis_width
+ 1.0f
, 0.0f
, label
, label_display_end
, &label_end
).x
;
6634 if (label_end
== label
&& label_end
< label_display_end
) // Always display at least 1 character if there's no room for character + ellipsis
6636 label_end
= label
+ ImTextCountUtf8BytesFromChar(label
, label_display_end
);
6637 label_size_clipped_x
= g
.Font
->CalcTextSizeA(g
.FontSize
, FLT_MAX
, 0.0f
, label
, label_end
).x
;
6639 while (label_end
> label
&& ImCharIsBlankA(label_end
[-1])) // Trim trailing space
6642 label_size_clipped_x
-= g
.Font
->CalcTextSizeA(g
.FontSize
, FLT_MAX
, 0.0f
, label_end
, label_end
+ 1).x
; // Ascii blanks are always 1 byte
6644 RenderTextClippedEx(draw_list
, text_pixel_clip_bb
.Min
, text_pixel_clip_bb
.Max
, label
, label_end
, &label_size
, ImVec2(0.0f
, 0.0f
));
6646 const float ellipsis_x
= text_pixel_clip_bb
.Min
.x
+ label_size_clipped_x
+ 1.0f
;
6647 if (!close_button_visible
&& ellipsis_x
+ ellipsis_width
<= bb
.Max
.x
)
6648 RenderPixelEllipsis(draw_list
, ImVec2(ellipsis_x
, text_pixel_clip_bb
.Min
.y
), ellipsis_dot_count
, GetColorU32(ImGuiCol_Text
));
6652 RenderTextClippedEx(draw_list
, text_pixel_clip_bb
.Min
, text_pixel_clip_bb
.Max
, label
, label_display_end
, &label_size
, ImVec2(0.0f
, 0.0f
));
6655 return close_button_pressed
;