Added few more stubs so that control reaches to DestroyDevice().
[mesa.git] / src / imgui / imgui_widgets.cpp
1 // dear imgui, v1.68 WIP
2 // (widgets code)
3
4 /*
5
6 Index of this file:
7
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.
27
28 */
29
30 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
31 #define _CRT_SECURE_NO_WARNINGS
32 #endif
33
34 #include "imgui.h"
35 #ifndef IMGUI_DEFINE_MATH_OPERATORS
36 #define IMGUI_DEFINE_MATH_OPERATORS
37 #endif
38 #include "imgui_internal.h"
39
40 #include <ctype.h> // toupper, isprint
41 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
42 #include <stddef.h> // intptr_t
43 #else
44 #include <stdint.h> // intptr_t
45 #endif
46
47 // Visual Studio warnings
48 #ifdef _MSC_VER
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
51 #endif
52
53 // Clang/GCC warnings with -Weverything
54 #ifdef __clang__
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
61 #endif
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.
64 #endif
65 #elif defined(__GNUC__)
66 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
67 #if __GNUC__ >= 8
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
69 #endif
70 #endif
71
72 //-------------------------------------------------------------------------
73 // Data
74 //-------------------------------------------------------------------------
75
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)
81 #ifdef LLONG_MIN
82 static const ImS64 IM_S64_MIN = LLONG_MIN; // (-9223372036854775807ll - 1ll);
83 static const ImS64 IM_S64_MAX = LLONG_MAX; // (9223372036854775807ll);
84 #else
85 static const ImS64 IM_S64_MIN = -9223372036854775807LL - 1;
86 static const ImS64 IM_S64_MAX = 9223372036854775807LL;
87 #endif
88 static const ImU64 IM_U64_MIN = 0;
89 #ifdef ULLONG_MAX
90 static const ImU64 IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
91 #else
92 static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
93 #endif
94
95 //-------------------------------------------------------------------------
96 // [SECTION] Forward Declarations
97 //-------------------------------------------------------------------------
98
99 // Data Type helpers
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);
103
104 // For InputTextEx()
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);
108
109 //-------------------------------------------------------------------------
110 // [SECTION] Widgets: Text, etc.
111 //-------------------------------------------------------------------------
112 // - TextUnformatted()
113 // - Text()
114 // - TextV()
115 // - TextColored()
116 // - TextColoredV()
117 // - TextDisabled()
118 // - TextDisabledV()
119 // - TextWrapped()
120 // - TextWrappedV()
121 // - LabelText()
122 // - LabelTextV()
123 // - BulletText()
124 // - BulletTextV()
125 //-------------------------------------------------------------------------
126
127 void ImGui::TextUnformatted(const char* text, const char* text_end)
128 {
129 ImGuiWindow* window = GetCurrentWindow();
130 if (window->SkipItems)
131 return;
132
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
138
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)
143 {
144 // Long text!
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);
153
154 if (text_pos.y <= clip_rect.Max.y)
155 {
156 ImVec2 pos = text_pos;
157
158 // Lines to skip (can't skip when logging text)
159 if (!g.LogEnabled)
160 {
161 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
162 if (lines_skippable > 0)
163 {
164 int lines_skipped = 0;
165 while (line < text_end && lines_skipped < lines_skippable)
166 {
167 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
168 if (!line_end)
169 line_end = text_end;
170 line = line_end + 1;
171 lines_skipped++;
172 }
173 pos.y += lines_skipped * line_height;
174 }
175 }
176
177 // Lines to render
178 if (line < text_end)
179 {
180 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
181 while (line < text_end)
182 {
183 if (IsClippedEx(line_rect, 0, false))
184 break;
185
186 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
187 if (!line_end)
188 line_end = text_end;
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);
192 line = line_end + 1;
193 line_rect.Min.y += line_height;
194 line_rect.Max.y += line_height;
195 pos.y += line_height;
196 }
197
198 // Count remaining lines
199 int lines_skipped = 0;
200 while (line < text_end)
201 {
202 const char* line_end = (const char*)memchr(line, '\n', text_end - line);
203 if (!line_end)
204 line_end = text_end;
205 line = line_end + 1;
206 lines_skipped++;
207 }
208 pos.y += lines_skipped * line_height;
209 }
210
211 text_size.y += (pos - text_pos).y;
212 }
213
214 ImRect bb(text_pos, text_pos + text_size);
215 ItemSize(text_size);
216 ItemAdd(bb, 0);
217 }
218 else
219 {
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);
222
223 // Account of baseline offset
224 ImRect bb(text_pos, text_pos + text_size);
225 ItemSize(text_size);
226 if (!ItemAdd(bb, 0))
227 return;
228
229 // Render (we don't hide text after ## in this end-user function)
230 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
231 }
232 }
233
234 void ImGui::Text(const char* fmt, ...)
235 {
236 va_list args;
237 va_start(args, fmt);
238 TextV(fmt, args);
239 va_end(args);
240 }
241
242 void ImGui::TextV(const char* fmt, va_list args)
243 {
244 ImGuiWindow* window = GetCurrentWindow();
245 if (window->SkipItems)
246 return;
247
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);
251 }
252
253 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
254 {
255 va_list args;
256 va_start(args, fmt);
257 TextColoredV(col, fmt, args);
258 va_end(args);
259 }
260
261 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
262 {
263 PushStyleColor(ImGuiCol_Text, col);
264 TextV(fmt, args);
265 PopStyleColor();
266 }
267
268 void ImGui::TextDisabled(const char* fmt, ...)
269 {
270 va_list args;
271 va_start(args, fmt);
272 TextDisabledV(fmt, args);
273 va_end(args);
274 }
275
276 void ImGui::TextDisabledV(const char* fmt, va_list args)
277 {
278 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
279 TextV(fmt, args);
280 PopStyleColor();
281 }
282
283 void ImGui::TextWrapped(const char* fmt, ...)
284 {
285 va_list args;
286 va_start(args, fmt);
287 TextWrappedV(fmt, args);
288 va_end(args);
289 }
290
291 void ImGui::TextWrappedV(const char* fmt, va_list args)
292 {
293 bool need_backup = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position if one is already set
294 if (need_backup)
295 PushTextWrapPos(0.0f);
296 TextV(fmt, args);
297 if (need_backup)
298 PopTextWrapPos();
299 }
300
301 void ImGui::LabelText(const char* label, const char* fmt, ...)
302 {
303 va_list args;
304 va_start(args, fmt);
305 LabelTextV(label, fmt, args);
306 va_end(args);
307 }
308
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)
311 {
312 ImGuiWindow* window = GetCurrentWindow();
313 if (window->SkipItems)
314 return;
315
316 ImGuiContext& g = *GImGui;
317 const ImGuiStyle& style = g.Style;
318 const float w = CalcItemWidth();
319
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))
325 return;
326
327 // Render
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);
333 }
334
335 void ImGui::BulletText(const char* fmt, ...)
336 {
337 va_list args;
338 va_start(args, fmt);
339 BulletTextV(fmt, args);
340 va_end(args);
341 }
342
343 // Text with a little bullet aligned to the typical tree node.
344 void ImGui::BulletTextV(const char* fmt, va_list args)
345 {
346 ImGuiWindow* window = GetCurrentWindow();
347 if (window->SkipItems)
348 return;
349
350 ImGuiContext& g = *GImGui;
351 const ImGuiStyle& style = g.Style;
352
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
359 ItemSize(bb);
360 if (!ItemAdd(bb, 0))
361 return;
362
363 // Render
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);
366 }
367
368 //-------------------------------------------------------------------------
369 // [SECTION] Widgets: Main
370 //-------------------------------------------------------------------------
371 // - ButtonBehavior() [Internal]
372 // - Button()
373 // - SmallButton()
374 // - InvisibleButton()
375 // - ArrowButton()
376 // - CloseButton() [Internal]
377 // - CollapseButton() [Internal]
378 // - Scrollbar() [Internal]
379 // - Image()
380 // - ImageButton()
381 // - Checkbox()
382 // - CheckboxFlags()
383 // - RadioButton()
384 // - ProgressBar()
385 // - Bullet()
386 //-------------------------------------------------------------------------
387
388 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
389 {
390 ImGuiContext& g = *GImGui;
391 ImGuiWindow* window = GetCurrentWindow();
392
393 if (flags & ImGuiButtonFlags_Disabled)
394 {
395 if (out_hovered) *out_hovered = false;
396 if (out_held) *out_held = false;
397 if (g.ActiveId == id) ClearActiveID();
398 return false;
399 }
400
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;
404
405 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
406 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
407 g.HoveredWindow = window;
408
409 #ifdef IMGUI_ENABLE_TEST_ENGINE
410 if (id != 0 && window->DC.LastItemId != id)
411 ImGuiTestEngineHook_ItemAdd(&g, bb, id);
412 #endif
413
414 bool pressed = false;
415 bool hovered = ItemHoverable(bb, id);
416
417 // Drag source doesn't report as hovered
418 if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
419 hovered = false;
420
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))
424 {
425 hovered = true;
426 SetHoveredID(id);
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
428 {
429 pressed = true;
430 FocusWindow(window);
431 }
432 }
433
434 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
435 g.HoveredWindow = backup_hovered_window;
436
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))
439 hovered = false;
440
441 // Mouse
442 if (hovered)
443 {
444 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
445 {
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])
453 {
454 SetActiveID(id, window);
455 if (!(flags & ImGuiButtonFlags_NoNavFocus))
456 SetFocusID(id, window);
457 FocusWindow(window);
458 }
459 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
460 {
461 pressed = true;
462 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
463 ClearActiveID();
464 else
465 SetActiveID(id, window); // Hold on ID
466 FocusWindow(window);
467 }
468 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
469 {
470 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
471 pressed = true;
472 ClearActiveID();
473 }
474
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))
478 pressed = true;
479 }
480
481 if (pressed)
482 g.NavDisableHighlight = true;
483 }
484
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))
488 hovered = true;
489
490 if (g.NavActivateDownId == id)
491 {
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)
495 pressed = true;
496 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
497 {
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);
504 }
505 }
506
507 bool held = false;
508 if (g.ActiveId == id)
509 {
510 if (pressed)
511 g.ActiveIdHasBeenPressed = true;
512 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
513 {
514 if (g.ActiveIdIsJustActivated)
515 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
516 if (g.IO.MouseDown[0])
517 {
518 held = true;
519 }
520 else
521 {
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)
525 pressed = true;
526 ClearActiveID();
527 }
528 if (!(flags & ImGuiButtonFlags_NoNavFocus))
529 g.NavDisableHighlight = true;
530 }
531 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
532 {
533 if (g.NavActivateDownId != id)
534 ClearActiveID();
535 }
536 }
537
538 if (out_hovered) *out_hovered = hovered;
539 if (out_held) *out_held = held;
540
541 return pressed;
542 }
543
544 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
545 {
546 ImGuiWindow* window = GetCurrentWindow();
547 if (window->SkipItems)
548 return false;
549
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);
554
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);
559
560 const ImRect bb(pos, pos + size);
561 ItemSize(size, style.FramePadding.y);
562 if (!ItemAdd(bb, id))
563 return false;
564
565 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
566 flags |= ImGuiButtonFlags_Repeat;
567 bool hovered, held;
568 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
569 if (pressed)
570 MarkItemEdited(id);
571
572 // Render
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);
577
578 // Automatically close popups
579 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
580 // CloseCurrentPopup();
581
582 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
583 return pressed;
584 }
585
586 bool ImGui::Button(const char* label, const ImVec2& size_arg)
587 {
588 return ButtonEx(label, size_arg, 0);
589 }
590
591 // Small buttons fits within text without additional vertical spacing.
592 bool ImGui::SmallButton(const char* label)
593 {
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;
599 return pressed;
600 }
601
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)
605 {
606 ImGuiWindow* window = GetCurrentWindow();
607 if (window->SkipItems)
608 return false;
609
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);
612
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);
616 ItemSize(size);
617 if (!ItemAdd(bb, id))
618 return false;
619
620 bool hovered, held;
621 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
622
623 return pressed;
624 }
625
626 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
627 {
628 ImGuiWindow* window = GetCurrentWindow();
629 if (window->SkipItems)
630 return false;
631
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))
638 return false;
639
640 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
641 flags |= ImGuiButtonFlags_Repeat;
642
643 bool hovered, held;
644 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
645
646 // Render
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);
651
652 return pressed;
653 }
654
655 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
656 {
657 float sz = GetFrameHeight();
658 return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
659 }
660
661 // Button to close a window
662 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
663 {
664 ImGuiContext& g = *GImGui;
665 ImGuiWindow* window = g.CurrentWindow;
666
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);
671
672 bool hovered, held;
673 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
674 if (is_clipped)
675 return pressed;
676
677 // Render
678 ImVec2 center = bb.GetCenter();
679 if (hovered)
680 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
681
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);
687
688 return pressed;
689 }
690
691 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
692 {
693 ImGuiContext& g = *GImGui;
694 ImGuiWindow* window = g.CurrentWindow;
695
696 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize) + g.Style.FramePadding * 2.0f);
697 ItemAdd(bb, id);
698 bool hovered, held;
699 bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
700
701 ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
702 if (hovered || held)
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);
705
706 // Switch to moving the window after mouse is moved beyond the initial drag threshold
707 if (IsItemActive() && IsMouseDragging())
708 StartMouseMovingWindow(window);
709
710 return pressed;
711 }
712
713 ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction)
714 {
715 ImGuiContext& g = *GImGui;
716 ImGuiWindow* window = g.CurrentWindow;
717 return window->GetID((direction == ImGuiLayoutType_Horizontal) ? "#SCROLLX" : "#SCROLLY");
718 }
719
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)
726 {
727 ImGuiContext& g = *GImGui;
728 ImGuiWindow* window = g.CurrentWindow;
729
730 const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
731 const ImGuiStyle& style = g.Style;
732 const ImGuiID id = GetScrollbarID(direction);
733
734 // Render background
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);
742 if (!horizontal)
743 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
744
745 const float bb_height = bb.GetHeight();
746 if (bb.GetWidth() <= 0.0f || bb_height <= 0.0f)
747 return;
748
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)
750 float alpha = 1.0f;
751 if ((direction == ImGuiLayoutType_Vertical) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
752 {
753 alpha = ImSaturate((bb_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
754 if (alpha <= 0.0f)
755 return;
756 }
757 const bool allow_interaction = (alpha >= 1.0f);
758
759 int window_rounding_corners;
760 if (horizontal)
761 window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
762 else
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)));
766
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;
772
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;
779
780 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
781 bool held = false;
782 bool hovered = false;
783 const bool previously_held = (g.ActiveId == id);
784 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
785
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)
790 {
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;
794
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);
797 SetHoveredID(id);
798
799 bool seek_absolute = false;
800 if (!previously_held)
801 {
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)
804 {
805 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
806 }
807 else
808 {
809 seek_absolute = true;
810 *click_delta_to_grab_center_v = 0.0f;
811 }
812 }
813
814 // Apply scroll
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));
818 if (horizontal)
819 window->Scroll.x = scroll_v;
820 else
821 window->Scroll.y = scroll_v;
822
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;
826
827 // Update distance to grab now that we have seeked and saturated
828 if (seek_absolute)
829 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
830 }
831
832 // Render grab
833 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
834 ImRect grab_rect;
835 if (horizontal)
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);
837 else
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);
840 }
841
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)
843 {
844 ImGuiWindow* window = GetCurrentWindow();
845 if (window->SkipItems)
846 return;
847
848 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
849 if (border_col.w > 0.0f)
850 bb.Max += ImVec2(2, 2);
851 ItemSize(bb);
852 if (!ItemAdd(bb, 0))
853 return;
854
855 if (border_col.w > 0.0f)
856 {
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));
859 }
860 else
861 {
862 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
863 }
864 }
865
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)
871 {
872 ImGuiWindow* window = GetCurrentWindow();
873 if (window->SkipItems)
874 return false;
875
876 ImGuiContext& g = *GImGui;
877 const ImGuiStyle& style = g.Style;
878
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");
883 PopID();
884
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);
888 ItemSize(bb);
889 if (!ItemAdd(bb, id))
890 return false;
891
892 bool hovered, held;
893 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
894
895 // Render
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));
899 if (bg_col.w > 0.0f)
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));
902
903 return pressed;
904 }
905
906 bool ImGui::Checkbox(const char* label, bool* v)
907 {
908 ImGuiWindow* window = GetCurrentWindow();
909 if (window->SkipItems)
910 return false;
911
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);
916
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))
922 return false;
923
924 bool hovered, held;
925 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
926 if (pressed)
927 {
928 *v = !(*v);
929 MarkItemEdited(id);
930 }
931
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);
935 if (*v)
936 {
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);
939 }
940
941 if (g.LogEnabled)
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);
945
946 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
947 return pressed;
948 }
949
950 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
951 {
952 bool v = ((*flags & flags_value) == flags_value);
953 bool pressed = Checkbox(label, &v);
954 if (pressed)
955 {
956 if (v)
957 *flags |= flags_value;
958 else
959 *flags &= ~flags_value;
960 }
961
962 return pressed;
963 }
964
965 bool ImGui::RadioButton(const char* label, bool active)
966 {
967 ImGuiWindow* window = GetCurrentWindow();
968 if (window->SkipItems)
969 return false;
970
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);
975
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))
982 return false;
983
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;
988
989 bool hovered, held;
990 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
991 if (pressed)
992 MarkItemEdited(id);
993
994 RenderNavHighlight(total_bb, id);
995 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
996 if (active)
997 {
998 const float pad = ImMax(1.0f, (float)(int)(square_sz / 6.0f));
999 window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
1000 }
1001
1002 if (style.FrameBorderSize > 0.0f)
1003 {
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);
1006 }
1007
1008 if (g.LogEnabled)
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);
1012
1013 return pressed;
1014 }
1015
1016 bool ImGui::RadioButton(const char* label, int* v, int v_button)
1017 {
1018 const bool pressed = RadioButton(label, *v == v_button);
1019 if (pressed)
1020 *v = v_button;
1021 return pressed;
1022 }
1023
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)
1026 {
1027 ImGuiWindow* window = GetCurrentWindow();
1028 if (window->SkipItems)
1029 return;
1030
1031 ImGuiContext& g = *GImGui;
1032 const ImGuiStyle& style = g.Style;
1033
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))
1038 return;
1039
1040 // Render
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);
1046
1047 // Default displaying the fraction as percentage string, but user can override it
1048 char overlay_buf[32];
1049 if (!overlay)
1050 {
1051 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
1052 overlay = overlay_buf;
1053 }
1054
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);
1058 }
1059
1060 void ImGui::Bullet()
1061 {
1062 ImGuiWindow* window = GetCurrentWindow();
1063 if (window->SkipItems)
1064 return;
1065
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));
1070 ItemSize(bb);
1071 if (!ItemAdd(bb, 0))
1072 {
1073 SameLine(0, style.FramePadding.x*2);
1074 return;
1075 }
1076
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);
1080 }
1081
1082 //-------------------------------------------------------------------------
1083 // [SECTION] Widgets: Low-level Layout helpers
1084 //-------------------------------------------------------------------------
1085 // - Spacing()
1086 // - Dummy()
1087 // - NewLine()
1088 // - AlignTextToFramePadding()
1089 // - Separator()
1090 // - VerticalSeparator() [Internal]
1091 // - SplitterBehavior() [Internal]
1092 //-------------------------------------------------------------------------
1093
1094 void ImGui::Spacing()
1095 {
1096 ImGuiWindow* window = GetCurrentWindow();
1097 if (window->SkipItems)
1098 return;
1099 ItemSize(ImVec2(0,0));
1100 }
1101
1102 void ImGui::Dummy(const ImVec2& size)
1103 {
1104 ImGuiWindow* window = GetCurrentWindow();
1105 if (window->SkipItems)
1106 return;
1107
1108 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1109 ItemSize(bb);
1110 ItemAdd(bb, 0);
1111 }
1112
1113 void ImGui::NewLine()
1114 {
1115 ImGuiWindow* window = GetCurrentWindow();
1116 if (window->SkipItems)
1117 return;
1118
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));
1124 else
1125 ItemSize(ImVec2(0.0f, g.FontSize));
1126 window->DC.LayoutType = backup_layout_type;
1127 }
1128
1129 void ImGui::AlignTextToFramePadding()
1130 {
1131 ImGuiWindow* window = GetCurrentWindow();
1132 if (window->SkipItems)
1133 return;
1134
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);
1138 }
1139
1140 // Horizontal/vertical separating line
1141 void ImGui::Separator()
1142 {
1143 ImGuiWindow* window = GetCurrentWindow();
1144 if (window->SkipItems)
1145 return;
1146 ImGuiContext& g = *GImGui;
1147
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)
1152 {
1153 VerticalSeparator();
1154 return;
1155 }
1156
1157 // Horizontal Separator
1158 if (window->DC.ColumnsSet)
1159 PopClipRect();
1160
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;
1165
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))
1169 {
1170 if (window->DC.ColumnsSet)
1171 PushColumnClipRect();
1172 return;
1173 }
1174
1175 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
1176
1177 if (g.LogEnabled)
1178 LogRenderedText(&bb.Min, "--------------------------------");
1179
1180 if (window->DC.ColumnsSet)
1181 {
1182 PushColumnClipRect();
1183 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
1184 }
1185 }
1186
1187 void ImGui::VerticalSeparator()
1188 {
1189 ImGuiWindow* window = GetCurrentWindow();
1190 if (window->SkipItems)
1191 return;
1192 ImGuiContext& g = *GImGui;
1193
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))
1199 return;
1200
1201 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
1202 if (g.LogEnabled)
1203 LogText(" |");
1204 }
1205
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)
1208 {
1209 ImGuiContext& g = *GImGui;
1210 ImGuiWindow* window = g.CurrentWindow;
1211
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;
1216 if (!item_add)
1217 return false;
1218
1219 bool hovered, held;
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();
1225
1226 if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1227 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1228
1229 ImRect bb_render = bb;
1230 if (held)
1231 {
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;
1234
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;
1242
1243 // Apply resize
1244 if (mouse_delta != 0.0f)
1245 {
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));
1253 MarkItemEdited(id);
1254 }
1255 }
1256
1257 // Render
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);
1260
1261 return held;
1262 }
1263
1264 //-------------------------------------------------------------------------
1265 // [SECTION] Widgets: ComboBox
1266 //-------------------------------------------------------------------------
1267 // - BeginCombo()
1268 // - EndCombo()
1269 // - Combo()
1270 //-------------------------------------------------------------------------
1271
1272 static float CalcMaxPopupHeightFromItemCount(int items_count)
1273 {
1274 ImGuiContext& g = *GImGui;
1275 if (items_count <= 0)
1276 return FLT_MAX;
1277 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1278 }
1279
1280 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1281 {
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;
1286
1287 ImGuiWindow* window = GetCurrentWindow();
1288 if (window->SkipItems)
1289 return false;
1290
1291 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1292
1293 const ImGuiStyle& style = g.Style;
1294 const ImGuiID id = window->GetID(label);
1295
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))
1303 return false;
1304
1305 bool hovered, held;
1306 bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
1307 bool popup_open = IsPopupOpen(id);
1308
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))
1315 {
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);
1318 }
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);
1324
1325 if ((pressed || g.NavActivateId == id) && !popup_open)
1326 {
1327 if (window->DC.NavLayerCurrent == 0)
1328 window->NavLastIds[0] = id;
1329 OpenPopupEx(id);
1330 popup_open = true;
1331 }
1332
1333 if (!popup_open)
1334 return false;
1335
1336 if (backup_next_window_size_constraint)
1337 {
1338 g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
1339 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1340 }
1341 else
1342 {
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)));
1351 }
1352
1353 char name[16];
1354 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
1355
1356 // Peak into expected window size so we can position it
1357 if (ImGuiWindow* popup_window = FindWindowByName(name))
1358 if (popup_window->WasActive)
1359 {
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);
1366 }
1367
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);
1372 PopStyleVar();
1373 if (!ret)
1374 {
1375 EndPopup();
1376 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1377 return false;
1378 }
1379 return true;
1380 }
1381
1382 void ImGui::EndCombo()
1383 {
1384 EndPopup();
1385 }
1386
1387 // Getter for the old Combo() API: const char*[]
1388 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
1389 {
1390 const char* const* items = (const char* const*)data;
1391 if (out_text)
1392 *out_text = items[idx];
1393 return true;
1394 }
1395
1396 // Getter for the old Combo() API: "item1\0item2\0item3\0"
1397 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
1398 {
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;
1403 while (*p)
1404 {
1405 if (idx == items_count)
1406 break;
1407 p += strlen(p) + 1;
1408 items_count++;
1409 }
1410 if (!*p)
1411 return false;
1412 if (out_text)
1413 *out_text = p;
1414 return true;
1415 }
1416
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)
1419 {
1420 ImGuiContext& g = *GImGui;
1421
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);
1426
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)));
1430
1431 if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1432 return false;
1433
1434 // Display items
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++)
1438 {
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))
1445 {
1446 value_changed = true;
1447 *current_item = i;
1448 }
1449 if (item_selected)
1450 SetItemDefaultFocus();
1451 PopID();
1452 }
1453
1454 EndCombo();
1455 return value_changed;
1456 }
1457
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)
1460 {
1461 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1462 return value_changed;
1463 }
1464
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)
1467 {
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
1470 while (*p)
1471 {
1472 p += strlen(p) + 1;
1473 items_count++;
1474 }
1475 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1476 return value_changed;
1477 }
1478
1479 //-------------------------------------------------------------------------
1480 // [SECTION] Data Type and Data Formatting Helpers [Internal]
1481 //-------------------------------------------------------------------------
1482 // - PatchFormatStringFloatToInt()
1483 // - DataTypeFormatString()
1484 // - DataTypeApplyOp()
1485 // - DataTypeApplyOpFromText()
1486 // - GetMinimumStepAtDecimalPrecision
1487 // - RoundScalarWithFormat<>()
1488 //-------------------------------------------------------------------------
1489
1490 struct ImGuiDataTypeInfo
1491 {
1492 size_t Size;
1493 const char* PrintFmt; // Unused
1494 const char* ScanFmt;
1495 };
1496
1497 static const ImGuiDataTypeInfo GDataTypeInfo[] =
1498 {
1499 { sizeof(int), "%d", "%d" },
1500 { sizeof(unsigned int), "%u", "%u" },
1501 #ifdef _MSC_VER
1502 { sizeof(ImS64), "%I64d","%I64d" },
1503 { sizeof(ImU64), "%I64u","%I64u" },
1504 #else
1505 { sizeof(ImS64), "%lld", "%lld" },
1506 { sizeof(ImU64), "%llu", "%llu" },
1507 #endif
1508 { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg
1509 { sizeof(double), "%f", "%lf" },
1510 };
1511 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1512
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)
1517 {
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.
1519 return "%d";
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')
1523 {
1524 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1525 if (fmt_start == fmt && fmt_end[0] == 0)
1526 return "%d";
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;
1530 #else
1531 IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
1532 #endif
1533 }
1534 return fmt;
1535 }
1536
1537 static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
1538 {
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);
1547 IM_ASSERT(0);
1548 return 0;
1549 }
1550
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)
1553 {
1554 IM_ASSERT(op == '+' || op == '-');
1555 switch (data_type)
1556 {
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;
1560 return;
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;
1564 return;
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;
1568 return;
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;
1572 return;
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;
1576 return;
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;
1580 return;
1581 case ImGuiDataType_COUNT: break;
1582 }
1583 IM_ASSERT(0);
1584 }
1585
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)
1589 {
1590 while (ImCharIsBlankA(*buf))
1591 buf++;
1592
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
1595 char op = buf[0];
1596 if (op == '+' || op == '*' || op == '/')
1597 {
1598 buf++;
1599 while (ImCharIsBlankA(*buf))
1600 buf++;
1601 }
1602 else
1603 {
1604 op = 0;
1605 }
1606 if (!buf[0])
1607 return false;
1608
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);
1611 int data_backup[2];
1612 IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
1613 memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
1614
1615 if (format == NULL)
1616 format = GDataTypeInfo[data_type].ScanFmt;
1617
1618 int arg1i = 0;
1619 if (data_type == ImGuiDataType_S32)
1620 {
1621 int* v = (int*)data_ptr;
1622 int arg0i = *v;
1623 float arg1f = 0.0f;
1624 if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
1625 return false;
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
1631 }
1632 else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1633 {
1634 // Assign constant
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);
1637 }
1638 else if (data_type == ImGuiDataType_Float)
1639 {
1640 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
1641 format = "%f";
1642 float* v = (float*)data_ptr;
1643 float arg0f = *v, arg1f = 0.0f;
1644 if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
1645 return false;
1646 if (sscanf(buf, format, &arg1f) < 1)
1647 return false;
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
1652 }
1653 else if (data_type == ImGuiDataType_Double)
1654 {
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)
1659 return false;
1660 if (sscanf(buf, format, &arg1f) < 1)
1661 return false;
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
1666 }
1667 return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
1668 }
1669
1670 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
1671 {
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)
1674 return FLT_MIN;
1675 return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
1676 }
1677
1678 template<typename TYPE>
1679 static const char* ImAtoi(const char* src, TYPE* output)
1680 {
1681 int negative = 0;
1682 if (*src == '-') { negative = 1; src++; }
1683 if (*src == '+') { src++; }
1684 TYPE v = 0;
1685 while (*src >= '0' && *src <= '9')
1686 v = (v * 10) + (*src++ - '0');
1687 *output = negative ? -v : v;
1688 return src;
1689 }
1690
1691 template<typename TYPE, typename SIGNEDTYPE>
1692 TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
1693 {
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
1696 return v;
1697 char v_str[64];
1698 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
1699 const char* p = v_str;
1700 while (*p == ' ')
1701 p++;
1702 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
1703 v = (TYPE)ImAtof(p);
1704 else
1705 ImAtoi(p, (SIGNEDTYPE*)&v);
1706 return v;
1707 }
1708
1709 //-------------------------------------------------------------------------
1710 // [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1711 //-------------------------------------------------------------------------
1712 // - DragBehaviorT<>() [Internal]
1713 // - DragBehavior() [Internal]
1714 // - DragScalar()
1715 // - DragScalarN()
1716 // - DragFloat()
1717 // - DragFloat2()
1718 // - DragFloat3()
1719 // - DragFloat4()
1720 // - DragFloatRange2()
1721 // - DragInt()
1722 // - DragInt2()
1723 // - DragInt3()
1724 // - DragInt4()
1725 // - DragIntRange2()
1726 //-------------------------------------------------------------------------
1727
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)
1731 {
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));
1737
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);
1741
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)
1745 {
1746 adjust_delta = g.IO.MouseDelta[axis];
1747 if (g.IO.KeyAlt)
1748 adjust_delta *= 1.0f / 100.0f;
1749 if (g.IO.KeyShift)
1750 adjust_delta *= 10.0f;
1751 }
1752 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
1753 {
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));
1757 }
1758 adjust_delta *= v_speed;
1759
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;
1763
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)
1770 {
1771 g.DragCurrentAccum = 0.0f;
1772 g.DragCurrentAccumDirty = false;
1773 }
1774 else if (adjust_delta != 0.0f)
1775 {
1776 g.DragCurrentAccum += adjust_delta;
1777 g.DragCurrentAccumDirty = true;
1778 }
1779
1780 if (!g.DragCurrentAccumDirty)
1781 return false;
1782
1783 TYPE v_cur = *v;
1784 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
1785
1786 if (is_power)
1787 {
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;
1793 }
1794 else
1795 {
1796 v_cur += (TYPE)g.DragCurrentAccum;
1797 }
1798
1799 // Round to user desired precision based on format string
1800 v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
1801
1802 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1803 g.DragCurrentAccumDirty = false;
1804 if (is_power)
1805 {
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);
1808 }
1809 else
1810 {
1811 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
1812 }
1813
1814 // Lose zero sign for float/double
1815 if (v_cur == (TYPE)-0)
1816 v_cur = (TYPE)0;
1817
1818 // Clamp values (+ handle overflow/wrap-around for integer types)
1819 if (*v != v_cur && has_min_max)
1820 {
1821 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal))
1822 v_cur = v_min;
1823 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal))
1824 v_cur = v_max;
1825 }
1826
1827 // Apply result
1828 if (*v == v_cur)
1829 return false;
1830 *v = v_cur;
1831 return true;
1832 }
1833
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)
1835 {
1836 ImGuiContext& g = *GImGui;
1837 if (g.ActiveId == id)
1838 {
1839 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
1840 ClearActiveID();
1841 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1842 ClearActiveID();
1843 }
1844 if (g.ActiveId != id)
1845 return false;
1846
1847 switch (data_type)
1848 {
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;
1856 }
1857 IM_ASSERT(0);
1858 return false;
1859 }
1860
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)
1862 {
1863 ImGuiWindow* window = GetCurrentWindow();
1864 if (window->SkipItems)
1865 return false;
1866
1867 if (power != 1.0f)
1868 IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
1869
1870 ImGuiContext& g = *GImGui;
1871 const ImGuiStyle& style = g.Style;
1872 const ImGuiID id = window->GetID(label);
1873 const float w = CalcItemWidth();
1874
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));
1878
1879 ItemSize(total_bb, style.FramePadding.y);
1880 if (!ItemAdd(total_bb, id, &frame_bb))
1881 return false;
1882
1883 const bool hovered = ItemHoverable(frame_bb, id);
1884
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);
1888 if (format == NULL)
1889 format = GDataTypeInfo[data_type].PrintFmt;
1890 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
1891 format = PatchFormatStringFloatToInt(format);
1892
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))
1897 {
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)
1903 {
1904 start_text_input = true;
1905 g.ScalarAsInputTextId = 0;
1906 }
1907 }
1908 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
1909 {
1910 window->DC.CursorPos = frame_bb.Min;
1911 FocusableItemUnregister(window);
1912 return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
1913 }
1914
1915 // Actual drag behavior
1916 const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power, ImGuiDragFlags_None);
1917 if (value_changed)
1918 MarkItemEdited(id);
1919
1920 // Draw frame
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);
1924
1925 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1926 char value_buf[64];
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));
1929
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);
1932
1933 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
1934 return value_changed;
1935 }
1936
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)
1938 {
1939 ImGuiWindow* window = GetCurrentWindow();
1940 if (window->SkipItems)
1941 return false;
1942
1943 ImGuiContext& g = *GImGui;
1944 bool value_changed = false;
1945 BeginGroup();
1946 PushID(label);
1947 PushMultiItemsWidths(components);
1948 size_t type_size = GDataTypeInfo[data_type].Size;
1949 for (int i = 0; i < components; i++)
1950 {
1951 PushID(i);
1952 value_changed |= DragScalar("", data_type, v, v_speed, v_min, v_max, format, power);
1953 SameLine(0, g.Style.ItemInnerSpacing.x);
1954 PopID();
1955 PopItemWidth();
1956 v = (void*)((char*)v + type_size);
1957 }
1958 PopID();
1959
1960 TextUnformatted(label, FindRenderedTextEnd(label));
1961 EndGroup();
1962 return value_changed;
1963 }
1964
1965 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
1966 {
1967 return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
1968 }
1969
1970 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
1971 {
1972 return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
1973 }
1974
1975 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
1976 {
1977 return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
1978 }
1979
1980 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
1981 {
1982 return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
1983 }
1984
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)
1986 {
1987 ImGuiWindow* window = GetCurrentWindow();
1988 if (window->SkipItems)
1989 return false;
1990
1991 ImGuiContext& g = *GImGui;
1992 PushID(label);
1993 BeginGroup();
1994 PushMultiItemsWidths(2);
1995
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);
1997 PopItemWidth();
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);
2000 PopItemWidth();
2001 SameLine(0, g.Style.ItemInnerSpacing.x);
2002
2003 TextUnformatted(label, FindRenderedTextEnd(label));
2004 EndGroup();
2005 PopID();
2006 return value_changed;
2007 }
2008
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)
2011 {
2012 return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
2013 }
2014
2015 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
2016 {
2017 return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
2018 }
2019
2020 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
2021 {
2022 return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
2023 }
2024
2025 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
2026 {
2027 return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
2028 }
2029
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)
2031 {
2032 ImGuiWindow* window = GetCurrentWindow();
2033 if (window->SkipItems)
2034 return false;
2035
2036 ImGuiContext& g = *GImGui;
2037 PushID(label);
2038 BeginGroup();
2039 PushMultiItemsWidths(2);
2040
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);
2042 PopItemWidth();
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);
2045 PopItemWidth();
2046 SameLine(0, g.Style.ItemInnerSpacing.x);
2047
2048 TextUnformatted(label, FindRenderedTextEnd(label));
2049 EndGroup();
2050 PopID();
2051
2052 return value_changed;
2053 }
2054
2055 //-------------------------------------------------------------------------
2056 // [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2057 //-------------------------------------------------------------------------
2058 // - SliderBehaviorT<>() [Internal]
2059 // - SliderBehavior() [Internal]
2060 // - SliderScalar()
2061 // - SliderScalarN()
2062 // - SliderFloat()
2063 // - SliderFloat2()
2064 // - SliderFloat3()
2065 // - SliderFloat4()
2066 // - SliderAngle()
2067 // - SliderInt()
2068 // - SliderInt2()
2069 // - SliderInt3()
2070 // - SliderInt4()
2071 // - VSliderScalar()
2072 // - VSliderFloat()
2073 // - VSliderInt()
2074 //-------------------------------------------------------------------------
2075
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)
2078 {
2079 if (v_min == v_max)
2080 return 0.0f;
2081
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);
2084 if (is_power)
2085 {
2086 if (v_clamped < 0.0f)
2087 {
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;
2090 }
2091 else
2092 {
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);
2095 }
2096 }
2097
2098 // Linear slider
2099 return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
2100 }
2101
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)
2105 {
2106 ImGuiContext& g = *GImGui;
2107 const ImGuiStyle& style = g.Style;
2108
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;
2112
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;
2123
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)
2127 {
2128 // Different sign
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));
2132 }
2133 else
2134 {
2135 // Same sign
2136 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
2137 }
2138
2139 // Process interacting with the slider
2140 bool value_changed = false;
2141 if (g.ActiveId == id)
2142 {
2143 bool set_new_value = false;
2144 float clicked_t = 0.0f;
2145 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
2146 {
2147 if (!g.IO.MouseDown[0])
2148 {
2149 ClearActiveID();
2150 }
2151 else
2152 {
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;
2158 }
2159 }
2160 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
2161 {
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)
2165 {
2166 ClearActiveID();
2167 }
2168 else if (delta != 0.0f)
2169 {
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)
2173 {
2174 delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
2175 if (IsNavInputDown(ImGuiNavInput_TweakSlow))
2176 delta /= 10.0f;
2177 }
2178 else
2179 {
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
2182 else
2183 delta /= 100.0f;
2184 }
2185 if (IsNavInputDown(ImGuiNavInput_TweakFast))
2186 delta *= 10.0f;
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;
2190 else
2191 clicked_t = ImSaturate(clicked_t + delta);
2192 }
2193 }
2194
2195 if (set_new_value)
2196 {
2197 TYPE v_new;
2198 if (is_power)
2199 {
2200 // Account for power curve scale on both sides of the zero
2201 if (clicked_t < linear_zero_pos)
2202 {
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);
2207 }
2208 else
2209 {
2210 // Positive: rescale to the positive range before powering
2211 float a;
2212 if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
2213 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
2214 else
2215 a = clicked_t;
2216 a = ImPow(a, power);
2217 v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
2218 }
2219 }
2220 else
2221 {
2222 // Linear slider
2223 if (is_decimal)
2224 {
2225 v_new = ImLerp(v_min, v_max, clicked_t);
2226 }
2227 else
2228 {
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;
2236 else
2237 v_new = v_min + v_new_off_floor;
2238 }
2239 }
2240
2241 // Round to user desired precision based on format string
2242 v_new = RoundScalarWithFormatT<TYPE,SIGNEDTYPE>(format, data_type, v_new);
2243
2244 // Apply result
2245 if (*v != v_new)
2246 {
2247 *v = v_new;
2248 value_changed = true;
2249 }
2250 }
2251 }
2252
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);
2260 else
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);
2262
2263 return value_changed;
2264 }
2265
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)
2270 {
2271 switch (data_type)
2272 {
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;
2292 }
2293 IM_ASSERT(0);
2294 return false;
2295 }
2296
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)
2298 {
2299 ImGuiWindow* window = GetCurrentWindow();
2300 if (window->SkipItems)
2301 return false;
2302
2303 ImGuiContext& g = *GImGui;
2304 const ImGuiStyle& style = g.Style;
2305 const ImGuiID id = window->GetID(label);
2306 const float w = CalcItemWidth();
2307
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));
2311
2312 ItemSize(total_bb, style.FramePadding.y);
2313 if (!ItemAdd(total_bb, id, &frame_bb))
2314 return false;
2315
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);
2319 if (format == NULL)
2320 format = GDataTypeInfo[data_type].PrintFmt;
2321 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
2322 format = PatchFormatStringFloatToInt(format);
2323
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))
2329 {
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)
2335 {
2336 start_text_input = true;
2337 g.ScalarAsInputTextId = 0;
2338 }
2339 }
2340 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
2341 {
2342 window->DC.CursorPos = frame_bb.Min;
2343 FocusableItemUnregister(window);
2344 return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
2345 }
2346
2347 // Draw frame
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);
2351
2352 // Slider behavior
2353 ImRect grab_bb;
2354 const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_None, &grab_bb);
2355 if (value_changed)
2356 MarkItemEdited(id);
2357
2358 // Render grab
2359 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
2360
2361 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2362 char value_buf[64];
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));
2365
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);
2368
2369 IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
2370 return value_changed;
2371 }
2372
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)
2375 {
2376 ImGuiWindow* window = GetCurrentWindow();
2377 if (window->SkipItems)
2378 return false;
2379
2380 ImGuiContext& g = *GImGui;
2381 bool value_changed = false;
2382 BeginGroup();
2383 PushID(label);
2384 PushMultiItemsWidths(components);
2385 size_t type_size = GDataTypeInfo[data_type].Size;
2386 for (int i = 0; i < components; i++)
2387 {
2388 PushID(i);
2389 value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, power);
2390 SameLine(0, g.Style.ItemInnerSpacing.x);
2391 PopID();
2392 PopItemWidth();
2393 v = (void*)((char*)v + type_size);
2394 }
2395 PopID();
2396
2397 TextUnformatted(label, FindRenderedTextEnd(label));
2398 EndGroup();
2399 return value_changed;
2400 }
2401
2402 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
2403 {
2404 return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
2405 }
2406
2407 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
2408 {
2409 return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
2410 }
2411
2412 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
2413 {
2414 return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
2415 }
2416
2417 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
2418 {
2419 return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
2420 }
2421
2422 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format)
2423 {
2424 if (format == NULL)
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;
2430 }
2431
2432 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
2433 {
2434 return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
2435 }
2436
2437 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
2438 {
2439 return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
2440 }
2441
2442 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
2443 {
2444 return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
2445 }
2446
2447 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
2448 {
2449 return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
2450 }
2451
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)
2453 {
2454 ImGuiWindow* window = GetCurrentWindow();
2455 if (window->SkipItems)
2456 return false;
2457
2458 ImGuiContext& g = *GImGui;
2459 const ImGuiStyle& style = g.Style;
2460 const ImGuiID id = window->GetID(label);
2461
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));
2465
2466 ItemSize(bb, style.FramePadding.y);
2467 if (!ItemAdd(frame_bb, id))
2468 return false;
2469
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);
2473 if (format == NULL)
2474 format = GDataTypeInfo[data_type].PrintFmt;
2475 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
2476 format = PatchFormatStringFloatToInt(format);
2477
2478 const bool hovered = ItemHoverable(frame_bb, id);
2479 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
2480 {
2481 SetActiveID(id, window);
2482 SetFocusID(id, window);
2483 FocusWindow(window);
2484 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2485 }
2486
2487 // Draw frame
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);
2491
2492 // Slider behavior
2493 ImRect grab_bb;
2494 const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical, &grab_bb);
2495 if (value_changed)
2496 MarkItemEdited(id);
2497
2498 // Render grab
2499 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
2500
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
2503 char value_buf[64];
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);
2508
2509 return value_changed;
2510 }
2511
2512 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
2513 {
2514 return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
2515 }
2516
2517 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
2518 {
2519 return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
2520 }
2521
2522 //-------------------------------------------------------------------------
2523 // [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
2524 //-------------------------------------------------------------------------
2525 // - ImParseFormatFindStart() [Internal]
2526 // - ImParseFormatFindEnd() [Internal]
2527 // - ImParseFormatTrimDecorations() [Internal]
2528 // - ImParseFormatPrecision() [Internal]
2529 // - InputScalarAsWidgetReplacement() [Internal]
2530 // - InputScalar()
2531 // - InputScalarN()
2532 // - InputFloat()
2533 // - InputFloat2()
2534 // - InputFloat3()
2535 // - InputFloat4()
2536 // - InputInt()
2537 // - InputInt2()
2538 // - InputInt3()
2539 // - InputInt4()
2540 // - InputDouble()
2541 //-------------------------------------------------------------------------
2542
2543 // We don't use strchr() because our strings are usually very short and often start with '%'
2544 const char* ImParseFormatFindStart(const char* fmt)
2545 {
2546 while (char c = fmt[0])
2547 {
2548 if (c == '%' && fmt[1] != '%')
2549 return fmt;
2550 else if (c == '%')
2551 fmt++;
2552 fmt++;
2553 }
2554 return fmt;
2555 }
2556
2557 const char* ImParseFormatFindEnd(const char* fmt)
2558 {
2559 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
2560 if (fmt[0] != '%')
2561 return fmt;
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++)
2565 {
2566 if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
2567 return fmt + 1;
2568 if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
2569 return fmt + 1;
2570 }
2571 return fmt;
2572 }
2573
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)
2580 {
2581 const char* fmt_start = ImParseFormatFindStart(fmt);
2582 if (fmt_start[0] != '%')
2583 return fmt;
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.
2586 return fmt_start;
2587 ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
2588 return buf;
2589 }
2590
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)
2594 {
2595 fmt = ImParseFormatFindStart(fmt);
2596 if (fmt[0] != '%')
2597 return default_precision;
2598 fmt++;
2599 while (*fmt >= '0' && *fmt <= '9')
2600 fmt++;
2601 int precision = INT_MAX;
2602 if (*fmt == '.')
2603 {
2604 fmt = ImAtoi<int>(fmt + 1, &precision);
2605 if (precision < 0 || precision > 99)
2606 precision = default_precision;
2607 }
2608 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
2609 precision = -1;
2610 if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
2611 precision = -1;
2612 return (precision == INT_MAX) ? default_precision : precision;
2613 }
2614
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)
2618 {
2619 ImGuiContext& g = *GImGui;
2620
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)
2624 ClearActiveID();
2625
2626 char fmt_buf[32];
2627 char data_buf[32];
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)
2634 {
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;
2638 }
2639 if (value_changed)
2640 return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL);
2641 return false;
2642 }
2643
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)
2645 {
2646 ImGuiWindow* window = GetCurrentWindow();
2647 if (window->SkipItems)
2648 return false;
2649
2650 ImGuiContext& g = *GImGui;
2651 const ImGuiStyle& style = g.Style;
2652
2653 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2654 if (format == NULL)
2655 format = GDataTypeInfo[data_type].PrintFmt;
2656
2657 char buf[64];
2658 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
2659
2660 bool value_changed = false;
2661 if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
2662 flags |= ImGuiInputTextFlags_CharsDecimal;
2663 flags |= ImGuiInputTextFlags_AutoSelectAll;
2664
2665 if (step != NULL)
2666 {
2667 const float button_size = GetFrameHeight();
2668
2669 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
2670 PushID(label);
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);
2674 PopItemWidth();
2675
2676 // Step buttons
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))
2682 {
2683 DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
2684 value_changed = true;
2685 }
2686 SameLine(0, style.ItemInnerSpacing.x);
2687 if (ButtonEx("+", ImVec2(button_size, button_size), button_flags))
2688 {
2689 DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
2690 value_changed = true;
2691 }
2692 SameLine(0, style.ItemInnerSpacing.x);
2693 TextUnformatted(label, FindRenderedTextEnd(label));
2694
2695 PopID();
2696 EndGroup();
2697 }
2698 else
2699 {
2700 if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
2701 value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
2702 }
2703
2704 return value_changed;
2705 }
2706
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)
2708 {
2709 ImGuiWindow* window = GetCurrentWindow();
2710 if (window->SkipItems)
2711 return false;
2712
2713 ImGuiContext& g = *GImGui;
2714 bool value_changed = false;
2715 BeginGroup();
2716 PushID(label);
2717 PushMultiItemsWidths(components);
2718 size_t type_size = GDataTypeInfo[data_type].Size;
2719 for (int i = 0; i < components; i++)
2720 {
2721 PushID(i);
2722 value_changed |= InputScalar("", data_type, v, step, step_fast, format, flags);
2723 SameLine(0, g.Style.ItemInnerSpacing.x);
2724 PopID();
2725 PopItemWidth();
2726 v = (void*)((char*)v + type_size);
2727 }
2728 PopID();
2729
2730 TextUnformatted(label, FindRenderedTextEnd(label));
2731 EndGroup();
2732 return value_changed;
2733 }
2734
2735 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
2736 {
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);
2739 }
2740
2741 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
2742 {
2743 return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
2744 }
2745
2746 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
2747 {
2748 return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
2749 }
2750
2751 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
2752 {
2753 return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
2754 }
2755
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)
2759 {
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);
2764 }
2765
2766 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags flags)
2767 {
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);
2772 }
2773
2774 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags flags)
2775 {
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);
2780 }
2781
2782 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags flags)
2783 {
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);
2788 }
2789 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2790
2791 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
2792 {
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);
2796 }
2797
2798 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
2799 {
2800 return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
2801 }
2802
2803 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
2804 {
2805 return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
2806 }
2807
2808 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
2809 {
2810 return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
2811 }
2812
2813 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
2814 {
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);
2817 }
2818
2819 //-------------------------------------------------------------------------
2820 // [SECTION] Widgets: InputText, InputTextMultiline
2821 //-------------------------------------------------------------------------
2822 // - InputText()
2823 // - InputTextMultiline()
2824 // - InputTextEx() [Internal]
2825 //-------------------------------------------------------------------------
2826
2827 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2828 {
2829 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
2830 return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
2831 }
2832
2833 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
2834 {
2835 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
2836 }
2837
2838 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
2839 {
2840 int line_count = 0;
2841 const char* s = text_begin;
2842 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
2843 if (c == '\n')
2844 line_count++;
2845 s--;
2846 if (s[0] != '\n' && s[0] != '\r')
2847 line_count++;
2848 *out_text_end = s;
2849 return line_count;
2850 }
2851
2852 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
2853 {
2854 ImGuiContext& g = *GImGui;
2855 ImFont* font = g.Font;
2856 const float line_height = g.FontSize;
2857 const float scale = line_height / font->FontSize;
2858
2859 ImVec2 text_size = ImVec2(0,0);
2860 float line_width = 0.0f;
2861
2862 const ImWchar* s = text_begin;
2863 while (s < text_end)
2864 {
2865 unsigned int c = (unsigned int)(*s++);
2866 if (c == '\n')
2867 {
2868 text_size.x = ImMax(text_size.x, line_width);
2869 text_size.y += line_height;
2870 line_width = 0.0f;
2871 if (stop_on_new_line)
2872 break;
2873 continue;
2874 }
2875 if (c == '\r')
2876 continue;
2877
2878 const float char_width = font->GetCharAdvance((ImWchar)c) * scale;
2879 line_width += char_width;
2880 }
2881
2882 if (text_size.x < line_width)
2883 text_size.x = line_width;
2884
2885 if (out_offset)
2886 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
2887
2888 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
2889 text_size.y += line_height;
2890
2891 if (remaining)
2892 *remaining = s;
2893
2894 return text_size;
2895 }
2896
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)
2898 namespace ImGuiStb
2899 {
2900
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)
2907 {
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);
2911 r->x0 = 0.0f;
2912 r->x1 = size.x;
2913 r->baseline_y_delta = size.y;
2914 r->ymin = 0.0f;
2915 r->ymax = size.y;
2916 r->num_chars = (int)(text_remaining - (text + line_start_idx));
2917 }
2918
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; }
2925 #else
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; }
2927 #endif
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
2930
2931 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
2932 {
2933 ImWchar* dst = obj->TextW.Data + pos;
2934
2935 // We maintain our buffer length in both UTF-8 and wchar formats
2936 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
2937 obj->CurLenW -= n;
2938
2939 // Offset remaining text (FIXME-OPT: Use memmove)
2940 const ImWchar* src = obj->TextW.Data + pos + n;
2941 while (ImWchar c = *src++)
2942 *dst++ = c;
2943 *dst = '\0';
2944 }
2945
2946 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
2947 {
2948 const bool is_resizable = (obj->UserFlags & ImGuiInputTextFlags_CallbackResize) != 0;
2949 const int text_len = obj->CurLenW;
2950 IM_ASSERT(pos <= text_len);
2951
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))
2954 return false;
2955
2956 // Grow internal buffer if needed
2957 if (new_text_len + text_len + 1 > obj->TextW.Size)
2958 {
2959 if (!is_resizable)
2960 return false;
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);
2963 }
2964
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));
2969
2970 obj->CurLenW += new_text_len;
2971 obj->CurLenA += new_text_len_utf8;
2972 obj->TextW[obj->CurLenW] = '\0';
2973
2974 return true;
2975 }
2976
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
2993
2994 #define STB_TEXTEDIT_IMPLEMENTATION
2995 #include "imstb_textedit.h"
2996
2997 }
2998
2999 void ImGuiInputTextState::OnKeyPressed(int key)
3000 {
3001 stb_textedit_key(this, &StbState, key);
3002 CursorFollow = true;
3003 CursorAnimReset();
3004 }
3005
3006 ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
3007 {
3008 memset(this, 0, sizeof(*this));
3009 }
3010
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)
3015 {
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++)
3020 *dst++ = c;
3021 *dst = '\0';
3022
3023 if (CursorPos + bytes_count >= pos)
3024 CursorPos -= bytes_count;
3025 else if (CursorPos >= pos)
3026 CursorPos = pos;
3027 SelectionStart = SelectionEnd = CursorPos;
3028 BufDirty = true;
3029 BufTextLen -= bytes_count;
3030 }
3031
3032 void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
3033 {
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)
3037 {
3038 if (!is_resizable)
3039 return;
3040