Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mesa
Path: blob/21.2-virgl/src/imgui/imgui_widgets.cpp
4558 views
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
3041
// Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the midly similar code (until we remove the U16 buffer alltogether!)
3042
ImGuiContext& g = *GImGui;
3043
ImGuiInputTextState* edit_state = &g.InputTextState;
3044
IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
3045
IM_ASSERT(Buf == edit_state->TempBuffer.Data);
3046
int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
3047
edit_state->TempBuffer.reserve(new_buf_size + 1);
3048
Buf = edit_state->TempBuffer.Data;
3049
BufSize = edit_state->BufCapacityA = new_buf_size;
3050
}
3051
3052
if (BufTextLen != pos)
3053
memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
3054
memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
3055
Buf[BufTextLen + new_text_len] = '\0';
3056
3057
if (CursorPos >= pos)
3058
CursorPos += new_text_len;
3059
SelectionStart = SelectionEnd = CursorPos;
3060
BufDirty = true;
3061
BufTextLen += new_text_len;
3062
}
3063
3064
// Return false to discard a character.
3065
static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3066
{
3067
unsigned int c = *p_char;
3068
3069
if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
3070
{
3071
bool pass = false;
3072
pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
3073
pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
3074
if (!pass)
3075
return false;
3076
}
3077
3078
if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
3079
return false;
3080
3081
if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
3082
{
3083
if (flags & ImGuiInputTextFlags_CharsDecimal)
3084
if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
3085
return false;
3086
3087
if (flags & ImGuiInputTextFlags_CharsScientific)
3088
if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
3089
return false;
3090
3091
if (flags & ImGuiInputTextFlags_CharsHexadecimal)
3092
if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
3093
return false;
3094
3095
if (flags & ImGuiInputTextFlags_CharsUppercase)
3096
if (c >= 'a' && c <= 'z')
3097
*p_char = (c += (unsigned int)('A'-'a'));
3098
3099
if (flags & ImGuiInputTextFlags_CharsNoBlank)
3100
if (ImCharIsBlankW(c))
3101
return false;
3102
}
3103
3104
if (flags & ImGuiInputTextFlags_CallbackCharFilter)
3105
{
3106
ImGuiInputTextCallbackData callback_data;
3107
memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3108
callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
3109
callback_data.EventChar = (ImWchar)c;
3110
callback_data.Flags = flags;
3111
callback_data.UserData = user_data;
3112
if (callback(&callback_data) != 0)
3113
return false;
3114
*p_char = callback_data.EventChar;
3115
if (!callback_data.EventChar)
3116
return false;
3117
}
3118
3119
return true;
3120
}
3121
3122
// Edit a string of text
3123
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
3124
// This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
3125
// Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
3126
// - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect.
3127
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
3128
// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
3129
bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
3130
{
3131
ImGuiWindow* window = GetCurrentWindow();
3132
if (window->SkipItems)
3133
return false;
3134
3135
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
3136
IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
3137
3138
ImGuiContext& g = *GImGui;
3139
ImGuiIO& io = g.IO;
3140
const ImGuiStyle& style = g.Style;
3141
3142
const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
3143
const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
3144
const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
3145
const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
3146
const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
3147
if (is_resizable)
3148
IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
3149
3150
if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
3151
BeginGroup();
3152
const ImGuiID id = window->GetID(label);
3153
const ImVec2 label_size = CalcTextSize(label, NULL, true);
3154
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
3155
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3156
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
3157
3158
ImGuiWindow* draw_window = window;
3159
if (is_multiline)
3160
{
3161
if (!ItemAdd(total_bb, id, &frame_bb))
3162
{
3163
ItemSize(total_bb, style.FramePadding.y);
3164
EndGroup();
3165
return false;
3166
}
3167
if (!BeginChildFrame(id, frame_bb.GetSize()))
3168
{
3169
EndChildFrame();
3170
EndGroup();
3171
return false;
3172
}
3173
draw_window = GetCurrentWindow();
3174
draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
3175
size.x -= draw_window->ScrollbarSizes.x;
3176
}
3177
else
3178
{
3179
ItemSize(total_bb, style.FramePadding.y);
3180
if (!ItemAdd(total_bb, id, &frame_bb))
3181
return false;
3182
}
3183
const bool hovered = ItemHoverable(frame_bb, id);
3184
if (hovered)
3185
g.MouseCursor = ImGuiMouseCursor_TextInput;
3186
3187
// Password pushes a temporary font with only a fallback glyph
3188
if (is_password)
3189
{
3190
const ImFontGlyph* glyph = g.Font->FindGlyph('*');
3191
ImFont* password_font = &g.InputTextPasswordFont;
3192
password_font->FontSize = g.Font->FontSize;
3193
password_font->Scale = g.Font->Scale;
3194
password_font->DisplayOffset = g.Font->DisplayOffset;
3195
password_font->Ascent = g.Font->Ascent;
3196
password_font->Descent = g.Font->Descent;
3197
password_font->ContainerAtlas = g.Font->ContainerAtlas;
3198
password_font->FallbackGlyph = glyph;
3199
password_font->FallbackAdvanceX = glyph->AdvanceX;
3200
IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
3201
PushFont(password_font);
3202
}
3203
3204
// NB: we are only allowed to access 'edit_state' if we are the active widget.
3205
ImGuiInputTextState& edit_state = g.InputTextState;
3206
3207
const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
3208
const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
3209
const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
3210
3211
const bool user_clicked = hovered && io.MouseClicked[0];
3212
const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
3213
const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
3214
3215
bool clear_active_id = false;
3216
3217
bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
3218
if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
3219
{
3220
if (g.ActiveId != id)
3221
{
3222
// Start edition
3223
// Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
3224
// From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
3225
const int prev_len_w = edit_state.CurLenW;
3226
const int init_buf_len = (int)strlen(buf);
3227
edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
3228
edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
3229
memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1);
3230
const char* buf_end = NULL;
3231
edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end);
3232
edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
3233
edit_state.CursorAnimReset();
3234
3235
// Preserve cursor position and undo/redo stack if we come back to same widget
3236
// FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
3237
const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW);
3238
if (recycle_state)
3239
{
3240
// Recycle existing cursor/selection/undo stack but clamp position
3241
// Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
3242
edit_state.CursorClamp();
3243
}
3244
else
3245
{
3246
edit_state.ID = id;
3247
edit_state.ScrollX = 0.0f;
3248
stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
3249
if (!is_multiline && focus_requested_by_code)
3250
select_all = true;
3251
}
3252
if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
3253
edit_state.StbState.insert_mode = 1;
3254
if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
3255
select_all = true;
3256
}
3257
SetActiveID(id, window);
3258
SetFocusID(id, window);
3259
FocusWindow(window);
3260
g.ActiveIdBlockNavInputFlags = (1 << ImGuiNavInput_Cancel);
3261
if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
3262
g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
3263
}
3264
else if (io.MouseClicked[0])
3265
{
3266
// Release focus when we click outside
3267
clear_active_id = true;
3268
}
3269
3270
bool value_changed = false;
3271
bool enter_pressed = false;
3272
int backup_current_text_length = 0;
3273
3274
if (g.ActiveId == id)
3275
{
3276
if (!is_editable && !g.ActiveIdIsJustActivated)
3277
{
3278
// When read-only we always use the live data passed to the function
3279
edit_state.TextW.resize(buf_size+1);
3280
const char* buf_end = NULL;
3281
edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end);
3282
edit_state.CurLenA = (int)(buf_end - buf);
3283
edit_state.CursorClamp();
3284
}
3285
3286
backup_current_text_length = edit_state.CurLenA;
3287
edit_state.BufCapacityA = buf_size;
3288
edit_state.UserFlags = flags;
3289
edit_state.UserCallback = callback;
3290
edit_state.UserCallbackData = callback_user_data;
3291
3292
// Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
3293
// Down the line we should have a cleaner library-wide concept of Selected vs Active.
3294
g.ActiveIdAllowOverlap = !io.MouseDown[0];
3295
g.WantTextInputNextFrame = 1;
3296
3297
// Edit in progress
3298
const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
3299
const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
3300
3301
const bool is_osx = io.ConfigMacOSXBehaviors;
3302
if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
3303
{
3304
edit_state.SelectAll();
3305
edit_state.SelectedAllMouseLock = true;
3306
}
3307
else if (hovered && is_osx && io.MouseDoubleClicked[0])
3308
{
3309
// Double-click select a word only, OS X style (by simulating keystrokes)
3310
edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
3311
edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
3312
}
3313
else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
3314
{
3315
if (hovered)
3316
{
3317
stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
3318
edit_state.CursorAnimReset();
3319
}
3320
}
3321
else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
3322
{
3323
stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
3324
edit_state.CursorAnimReset();
3325
edit_state.CursorFollow = true;
3326
}
3327
if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
3328
edit_state.SelectedAllMouseLock = false;
3329
3330
if (io.InputQueueCharacters.Size > 0)
3331
{
3332
// Process text input (before we check for Return because using some IME will effectively send a Return?)
3333
// We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
3334
bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
3335
if (!ignore_inputs && is_editable && !user_nav_input_start)
3336
for (int n = 0; n < io.InputQueueCharacters.Size; n++)
3337
{
3338
// Insert character if they pass filtering
3339
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
3340
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3341
edit_state.OnKeyPressed((int)c);
3342
}
3343
3344
// Consume characters
3345
io.InputQueueCharacters.resize(0);
3346
}
3347
}
3348
3349
bool cancel_edit = false;
3350
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
3351
{
3352
// Handle key-presses
3353
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
3354
const bool is_osx = io.ConfigMacOSXBehaviors;
3355
const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
3356
const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
3357
const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
3358
const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
3359
const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
3360
const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
3361
3362
const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
3363
const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
3364
const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
3365
const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
3366
const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
3367
3368
if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
3369
else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
3370
else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
3371
else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
3372
else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
3373
else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
3374
else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
3375
else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
3376
{
3377
if (!edit_state.HasSelection())
3378
{
3379
if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
3380
else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
3381
}
3382
edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
3383
}
3384
else if (IsKeyPressedMap(ImGuiKey_Enter))
3385
{
3386
bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
3387
if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
3388
{
3389
enter_pressed = clear_active_id = true;
3390
}
3391
else if (is_editable)
3392
{
3393
unsigned int c = '\n'; // Insert new line
3394
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3395
edit_state.OnKeyPressed((int)c);
3396
}
3397
}
3398
else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
3399
{
3400
unsigned int c = '\t'; // Insert TAB
3401
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3402
edit_state.OnKeyPressed((int)c);
3403
}
3404
else if (IsKeyPressedMap(ImGuiKey_Escape))
3405
{
3406
clear_active_id = cancel_edit = true;
3407
}
3408
else if (is_undo || is_redo)
3409
{
3410
edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
3411
edit_state.ClearSelection();
3412
}
3413
else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
3414
{
3415
edit_state.SelectAll();
3416
edit_state.CursorFollow = true;
3417
}
3418
else if (is_cut || is_copy)
3419
{
3420
// Cut, Copy
3421
if (io.SetClipboardTextFn)
3422
{
3423
const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
3424
const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
3425
edit_state.TempBuffer.resize((ie-ib) * 4 + 1);
3426
ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie);
3427
SetClipboardText(edit_state.TempBuffer.Data);
3428
}
3429
if (is_cut)
3430
{
3431
if (!edit_state.HasSelection())
3432
edit_state.SelectAll();
3433
edit_state.CursorFollow = true;
3434
stb_textedit_cut(&edit_state, &edit_state.StbState);
3435
}
3436
}
3437
else if (is_paste)
3438
{
3439
if (const char* clipboard = GetClipboardText())
3440
{
3441
// Filter pasted buffer
3442
const int clipboard_len = (int)strlen(clipboard);
3443
ImWchar* clipboard_filtered = (ImWchar*)MemAlloc((clipboard_len+1) * sizeof(ImWchar));
3444
int clipboard_filtered_len = 0;
3445
for (const char* s = clipboard; *s; )
3446
{
3447
unsigned int c;
3448
s += ImTextCharFromUtf8(&c, s, NULL);
3449
if (c == 0)
3450
break;
3451
if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, callback_user_data))
3452
continue;
3453
clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
3454
}
3455
clipboard_filtered[clipboard_filtered_len] = 0;
3456
if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
3457
{
3458
stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
3459
edit_state.CursorFollow = true;
3460
}
3461
MemFree(clipboard_filtered);
3462
}
3463
}
3464
}
3465
3466
if (g.ActiveId == id)
3467
{
3468
const char* apply_new_text = NULL;
3469
int apply_new_text_length = 0;
3470
if (cancel_edit)
3471
{
3472
// Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
3473
if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0)
3474
{
3475
apply_new_text = edit_state.InitialText.Data;
3476
apply_new_text_length = edit_state.InitialText.Size - 1;
3477
}
3478
}
3479
3480
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
3481
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
3482
bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
3483
if (apply_edit_back_to_user_buffer)
3484
{
3485
// Apply new value immediately - copy modified buffer back
3486
// Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
3487
// FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
3488
// FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
3489
if (is_editable)
3490
{
3491
edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1);
3492
ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL);
3493
}
3494
3495
// User callback
3496
if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
3497
{
3498
IM_ASSERT(callback != NULL);
3499
3500
// The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
3501
ImGuiInputTextFlags event_flag = 0;
3502
ImGuiKey event_key = ImGuiKey_COUNT;
3503
if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
3504
{
3505
event_flag = ImGuiInputTextFlags_CallbackCompletion;
3506
event_key = ImGuiKey_Tab;
3507
}
3508
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
3509
{
3510
event_flag = ImGuiInputTextFlags_CallbackHistory;
3511
event_key = ImGuiKey_UpArrow;
3512
}
3513
else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
3514
{
3515
event_flag = ImGuiInputTextFlags_CallbackHistory;
3516
event_key = ImGuiKey_DownArrow;
3517
}
3518
else if (flags & ImGuiInputTextFlags_CallbackAlways)
3519
event_flag = ImGuiInputTextFlags_CallbackAlways;
3520
3521
if (event_flag)
3522
{
3523
ImGuiInputTextCallbackData callback_data;
3524
memset(&callback_data, 0, sizeof(ImGuiInputTextCallbackData));
3525
callback_data.EventFlag = event_flag;
3526
callback_data.Flags = flags;
3527
callback_data.UserData = callback_user_data;
3528
3529
callback_data.EventKey = event_key;
3530
callback_data.Buf = edit_state.TempBuffer.Data;
3531
callback_data.BufTextLen = edit_state.CurLenA;
3532
callback_data.BufSize = edit_state.BufCapacityA;
3533
callback_data.BufDirty = false;
3534
3535
// We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
3536
ImWchar* text = edit_state.TextW.Data;
3537
const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
3538
const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
3539
const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
3540
3541
// Call user code
3542
callback(&callback_data);
3543
3544
// Read back what user may have modified
3545
IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields
3546
IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA);
3547
IM_ASSERT(callback_data.Flags == flags);
3548
if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; }
3549
if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); }
3550
if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
3551
if (callback_data.BufDirty)
3552
{
3553
IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
3554
if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
3555
edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length));
3556
edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL);
3557
edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
3558
edit_state.CursorAnimReset();
3559
}
3560
}
3561
}
3562
3563
// Will copy result string if modified
3564
if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0)
3565
{
3566
apply_new_text = edit_state.TempBuffer.Data;
3567
apply_new_text_length = edit_state.CurLenA;
3568
}
3569
}
3570
3571
// Copy result to user buffer
3572
if (apply_new_text)
3573
{
3574
IM_ASSERT(apply_new_text_length >= 0);
3575
if (backup_current_text_length != apply_new_text_length && is_resizable)
3576
{
3577
ImGuiInputTextCallbackData callback_data;
3578
callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
3579
callback_data.Flags = flags;
3580
callback_data.Buf = buf;
3581
callback_data.BufTextLen = apply_new_text_length;
3582
callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
3583
callback_data.UserData = callback_user_data;
3584
callback(&callback_data);
3585
buf = callback_data.Buf;
3586
buf_size = callback_data.BufSize;
3587
apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
3588
IM_ASSERT(apply_new_text_length <= buf_size);
3589
}
3590
3591
// If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
3592
ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
3593
value_changed = true;
3594
}
3595
3596
// Clear temporary user storage
3597
edit_state.UserFlags = 0;
3598
edit_state.UserCallback = NULL;
3599
edit_state.UserCallbackData = NULL;
3600
}
3601
3602
// Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
3603
if (clear_active_id && g.ActiveId == id)
3604
ClearActiveID();
3605
3606
// Render
3607
// Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
3608
const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL;
3609
3610
// Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
3611
// without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
3612
// Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
3613
const int buf_display_max_length = 2 * 1024 * 1024;
3614
3615
if (!is_multiline)
3616
{
3617
RenderNavHighlight(frame_bb, id);
3618
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
3619
}
3620
3621
const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
3622
ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
3623
ImVec2 text_size(0.f, 0.f);
3624
const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
3625
if (g.ActiveId == id || is_currently_scrolling)
3626
{
3627
edit_state.CursorAnim += io.DeltaTime;
3628
3629
// This is going to be messy. We need to:
3630
// - Display the text (this alone can be more easily clipped)
3631
// - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
3632
// - Measure text height (for scrollbar)
3633
// We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
3634
// FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
3635
const ImWchar* text_begin = edit_state.TextW.Data;
3636
ImVec2 cursor_offset, select_start_offset;
3637
3638
{
3639
// Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
3640
const ImWchar* searches_input_ptr[2];
3641
searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
3642
searches_input_ptr[1] = NULL;
3643
int searches_remaining = 1;
3644
int searches_result_line_number[2] = { -1, -999 };
3645
if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3646
{
3647
searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
3648
searches_result_line_number[1] = -1;
3649
searches_remaining++;
3650
}
3651
3652
// Iterate all lines to find our line numbers
3653
// In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
3654
searches_remaining += is_multiline ? 1 : 0;
3655
int line_count = 0;
3656
//for (const ImWchar* s = text_begin; (s = (const ImWchar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bits
3657
for (const ImWchar* s = text_begin; *s != 0; s++)
3658
if (*s == '\n')
3659
{
3660
line_count++;
3661
if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
3662
if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
3663
}
3664
line_count++;
3665
if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
3666
if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
3667
3668
// Calculate 2d position by finding the beginning of the line and measuring distance
3669
cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
3670
cursor_offset.y = searches_result_line_number[0] * g.FontSize;
3671
if (searches_result_line_number[1] >= 0)
3672
{
3673
select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
3674
select_start_offset.y = searches_result_line_number[1] * g.FontSize;
3675
}
3676
3677
// Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
3678
if (is_multiline)
3679
text_size = ImVec2(size.x, line_count * g.FontSize);
3680
}
3681
3682
// Scroll
3683
if (edit_state.CursorFollow)
3684
{
3685
// Horizontal scroll in chunks of quarter width
3686
if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
3687
{
3688
const float scroll_increment_x = size.x * 0.25f;
3689
if (cursor_offset.x < edit_state.ScrollX)
3690
edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
3691
else if (cursor_offset.x - size.x >= edit_state.ScrollX)
3692
edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
3693
}
3694
else
3695
{
3696
edit_state.ScrollX = 0.0f;
3697
}
3698
3699
// Vertical scroll
3700
if (is_multiline)
3701
{
3702
float scroll_y = draw_window->Scroll.y;
3703
if (cursor_offset.y - g.FontSize < scroll_y)
3704
scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
3705
else if (cursor_offset.y - size.y >= scroll_y)
3706
scroll_y = cursor_offset.y - size.y;
3707
draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
3708
draw_window->Scroll.y = scroll_y;
3709
render_pos.y = draw_window->DC.CursorPos.y;
3710
}
3711
}
3712
edit_state.CursorFollow = false;
3713
const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
3714
3715
// Draw selection
3716
if (edit_state.StbState.select_start != edit_state.StbState.select_end)
3717
{
3718
const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
3719
const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
3720
3721
float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
3722
float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
3723
ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
3724
ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
3725
for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
3726
{
3727
if (rect_pos.y > clip_rect.w + g.FontSize)
3728
break;
3729
if (rect_pos.y < clip_rect.y)
3730
{
3731
//p = (const ImWchar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bits
3732
//p = p ? p + 1 : text_selected_end;
3733
while (p < text_selected_end)
3734
if (*p++ == '\n')
3735
break;
3736
}
3737
else
3738
{
3739
ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
3740
if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
3741
ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
3742
rect.ClipWith(clip_rect);
3743
if (rect.Overlaps(clip_rect))
3744
draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
3745
}
3746
rect_pos.x = render_pos.x - render_scroll.x;
3747
rect_pos.y += g.FontSize;
3748
}
3749
}
3750
3751
const int buf_display_len = edit_state.CurLenA;
3752
if (is_multiline || buf_display_len < buf_display_max_length)
3753
draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
3754
3755
// Draw blinking cursor
3756
bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
3757
ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
3758
ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
3759
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
3760
draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
3761
3762
// Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
3763
if (is_editable)
3764
g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
3765
}
3766
else
3767
{
3768
// Render text only
3769
const char* buf_end = NULL;
3770
if (is_multiline)
3771
text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
3772
else
3773
buf_end = buf_display + strlen(buf_display);
3774
if (is_multiline || (buf_end - buf_display) < buf_display_max_length)
3775
draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
3776
}
3777
3778
if (is_multiline)
3779
{
3780
Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
3781
EndChildFrame();
3782
EndGroup();
3783
}
3784
3785
if (is_password)
3786
PopFont();
3787
3788
// Log as text
3789
if (g.LogEnabled && !is_password)
3790
LogRenderedText(&render_pos, buf_display, NULL);
3791
3792
if (label_size.x > 0)
3793
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3794
3795
if (value_changed)
3796
MarkItemEdited(id);
3797
3798
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
3799
if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
3800
return enter_pressed;
3801
else
3802
return value_changed;
3803
}
3804
3805
//-------------------------------------------------------------------------
3806
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
3807
//-------------------------------------------------------------------------
3808
// - ColorEdit3()
3809
// - ColorEdit4()
3810
// - ColorPicker3()
3811
// - RenderColorRectWithAlphaCheckerboard() [Internal]
3812
// - ColorPicker4()
3813
// - ColorButton()
3814
// - SetColorEditOptions()
3815
// - ColorTooltip() [Internal]
3816
// - ColorEditOptionsPopup() [Internal]
3817
// - ColorPickerOptionsPopup() [Internal]
3818
//-------------------------------------------------------------------------
3819
3820
bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
3821
{
3822
return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
3823
}
3824
3825
// Edit colors components (each component in 0.0f..1.0f range).
3826
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
3827
// With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
3828
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
3829
{
3830
ImGuiWindow* window = GetCurrentWindow();
3831
if (window->SkipItems)
3832
return false;
3833
3834
ImGuiContext& g = *GImGui;
3835
const ImGuiStyle& style = g.Style;
3836
const float square_sz = GetFrameHeight();
3837
const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
3838
const float w_items_all = CalcItemWidth() - w_extra;
3839
const char* label_display_end = FindRenderedTextEnd(label);
3840
3841
BeginGroup();
3842
PushID(label);
3843
3844
// If we're not showing any slider there's no point in doing any HSV conversions
3845
const ImGuiColorEditFlags flags_untouched = flags;
3846
if (flags & ImGuiColorEditFlags_NoInputs)
3847
flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
3848
3849
// Context menu: display and modify options (before defaults are applied)
3850
if (!(flags & ImGuiColorEditFlags_NoOptions))
3851
ColorEditOptionsPopup(col, flags);
3852
3853
// Read stored options
3854
if (!(flags & ImGuiColorEditFlags__InputsMask))
3855
flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
3856
if (!(flags & ImGuiColorEditFlags__DataTypeMask))
3857
flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
3858
if (!(flags & ImGuiColorEditFlags__PickerMask))
3859
flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
3860
flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
3861
3862
const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
3863
const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
3864
const int components = alpha ? 4 : 3;
3865
3866
// Convert to the formats we need
3867
float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
3868
if (flags & ImGuiColorEditFlags_HSV)
3869
ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
3870
int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
3871
3872
bool value_changed = false;
3873
bool value_changed_as_float = false;
3874
3875
if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3876
{
3877
// RGB/HSV 0..255 Sliders
3878
const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
3879
const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
3880
3881
const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
3882
const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
3883
const char* fmt_table_int[3][4] =
3884
{
3885
{ "%3d", "%3d", "%3d", "%3d" }, // Short display
3886
{ "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
3887
{ "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
3888
};
3889
const char* fmt_table_float[3][4] =
3890
{
3891
{ "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
3892
{ "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
3893
{ "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
3894
};
3895
const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
3896
3897
PushItemWidth(w_item_one);
3898
for (int n = 0; n < components; n++)
3899
{
3900
if (n > 0)
3901
SameLine(0, style.ItemInnerSpacing.x);
3902
if (n + 1 == components)
3903
PushItemWidth(w_item_last);
3904
if (flags & ImGuiColorEditFlags_Float)
3905
{
3906
value_changed |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
3907
value_changed_as_float |= value_changed;
3908
}
3909
else
3910
{
3911
value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
3912
}
3913
if (!(flags & ImGuiColorEditFlags_NoOptions))
3914
OpenPopupOnItemClick("context");
3915
}
3916
PopItemWidth();
3917
PopItemWidth();
3918
}
3919
else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
3920
{
3921
// RGB Hexadecimal Input
3922
char buf[64];
3923
if (alpha)
3924
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
3925
else
3926
ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
3927
PushItemWidth(w_items_all);
3928
if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
3929
{
3930
value_changed = true;
3931
char* p = buf;
3932
while (*p == '#' || ImCharIsBlankA(*p))
3933
p++;
3934
i[0] = i[1] = i[2] = i[3] = 0;
3935
if (alpha)
3936
sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
3937
else
3938
sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
3939
}
3940
if (!(flags & ImGuiColorEditFlags_NoOptions))
3941
OpenPopupOnItemClick("context");
3942
PopItemWidth();
3943
}
3944
3945
ImGuiWindow* picker_active_window = NULL;
3946
if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
3947
{
3948
if (!(flags & ImGuiColorEditFlags_NoInputs))
3949
SameLine(0, style.ItemInnerSpacing.x);
3950
3951
const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
3952
if (ColorButton("##ColorButton", col_v4, flags))
3953
{
3954
if (!(flags & ImGuiColorEditFlags_NoPicker))
3955
{
3956
// Store current color and open a picker
3957
g.ColorPickerRef = col_v4;
3958
OpenPopup("picker");
3959
SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
3960
}
3961
}
3962
if (!(flags & ImGuiColorEditFlags_NoOptions))
3963
OpenPopupOnItemClick("context");
3964
3965
if (BeginPopup("picker"))
3966
{
3967
picker_active_window = g.CurrentWindow;
3968
if (label != label_display_end)
3969
{
3970
TextUnformatted(label, label_display_end);
3971
Spacing();
3972
}
3973
ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
3974
ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
3975
PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
3976
value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
3977
PopItemWidth();
3978
EndPopup();
3979
}
3980
}
3981
3982
if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
3983
{
3984
SameLine(0, style.ItemInnerSpacing.x);
3985
TextUnformatted(label, label_display_end);
3986
}
3987
3988
// Convert back
3989
if (picker_active_window == NULL)
3990
{
3991
if (!value_changed_as_float)
3992
for (int n = 0; n < 4; n++)
3993
f[n] = i[n] / 255.0f;
3994
if (flags & ImGuiColorEditFlags_HSV)
3995
ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
3996
if (value_changed)
3997
{
3998
col[0] = f[0];
3999
col[1] = f[1];
4000
col[2] = f[2];
4001
if (alpha)
4002
col[3] = f[3];
4003
}
4004
}
4005
4006
PopID();
4007
EndGroup();
4008
4009
// Drag and Drop Target
4010
// NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
4011
if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
4012
{
4013
if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
4014
{
4015
memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
4016
value_changed = true;
4017
}
4018
if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
4019
{
4020
memcpy((float*)col, payload->Data, sizeof(float) * components);
4021
value_changed = true;
4022
}
4023
EndDragDropTarget();
4024
}
4025
4026
// When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
4027
if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
4028
window->DC.LastItemId = g.ActiveId;
4029
4030
if (value_changed)
4031
MarkItemEdited(window->DC.LastItemId);
4032
4033
return value_changed;
4034
}
4035
4036
bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
4037
{
4038
float col4[4] = { col[0], col[1], col[2], 1.0f };
4039
if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
4040
return false;
4041
col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
4042
return true;
4043
}
4044
4045
static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
4046
{
4047
float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
4048
int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
4049
int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
4050
int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
4051
return IM_COL32(r, g, b, 0xFF);
4052
}
4053
4054
// Helper for ColorPicker4()
4055
// NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
4056
// I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
4057
void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
4058
{
4059
ImGuiWindow* window = GetCurrentWindow();
4060
if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
4061
{
4062
ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
4063
ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
4064
window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
4065
4066
int yi = 0;
4067
for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
4068
{
4069
float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
4070
if (y2 <= y1)
4071
continue;
4072
for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
4073
{
4074
float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
4075
if (x2 <= x1)
4076
continue;
4077
int rounding_corners_flags_cell = 0;
4078
if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
4079
if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
4080
rounding_corners_flags_cell &= rounding_corners_flags;
4081
window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
4082
}
4083
}
4084
}
4085
else
4086
{
4087
window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
4088
}
4089
}
4090
4091
// Helper for ColorPicker4()
4092
static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
4093
{
4094
ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
4095
ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
4096
ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK);
4097
ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
4098
}
4099
4100
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4101
// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
4102
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
4103
{
4104
ImGuiContext& g = *GImGui;
4105
ImGuiWindow* window = GetCurrentWindow();
4106
ImDrawList* draw_list = window->DrawList;
4107
4108
ImGuiStyle& style = g.Style;
4109
ImGuiIO& io = g.IO;
4110
4111
PushID(label);
4112
BeginGroup();
4113
4114
if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4115
flags |= ImGuiColorEditFlags_NoSmallPreview;
4116
4117
// Context menu: display and store options.
4118
if (!(flags & ImGuiColorEditFlags_NoOptions))
4119
ColorPickerOptionsPopup(col, flags);
4120
4121
// Read stored options
4122
if (!(flags & ImGuiColorEditFlags__PickerMask))
4123
flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
4124
IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
4125
if (!(flags & ImGuiColorEditFlags_NoOptions))
4126
flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
4127
4128
// Setup
4129
int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
4130
bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
4131
ImVec2 picker_pos = window->DC.CursorPos;
4132
float square_sz = GetFrameHeight();
4133
float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
4134
float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
4135
float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
4136
float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
4137
float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
4138
4139
float backup_initial_col[4];
4140
memcpy(backup_initial_col, col, components * sizeof(float));
4141
4142
float wheel_thickness = sv_picker_size * 0.08f;
4143
float wheel_r_outer = sv_picker_size * 0.50f;
4144
float wheel_r_inner = wheel_r_outer - wheel_thickness;
4145
ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
4146
4147
// Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
4148
float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
4149
ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
4150
ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
4151
ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
4152
4153
float H,S,V;
4154
ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
4155
4156
bool value_changed = false, value_changed_h = false, value_changed_sv = false;
4157
4158
PushItemFlag(ImGuiItemFlags_NoNav, true);
4159
if (flags & ImGuiColorEditFlags_PickerHueWheel)
4160
{
4161
// Hue wheel + SV triangle logic
4162
InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
4163
if (IsItemActive())
4164
{
4165
ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
4166
ImVec2 current_off = g.IO.MousePos - wheel_center;
4167
float initial_dist2 = ImLengthSqr(initial_off);
4168
if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
4169
{
4170
// Interactive with Hue wheel
4171
H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
4172
if (H < 0.0f)
4173
H += 1.0f;
4174
value_changed = value_changed_h = true;
4175
}
4176
float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
4177
float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
4178
if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
4179
{
4180
// Interacting with SV triangle
4181
ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
4182
if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
4183
current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
4184
float uu, vv, ww;
4185
ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
4186
V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
4187
S = ImClamp(uu / V, 0.0001f, 1.0f);
4188
value_changed = value_changed_sv = true;
4189
}
4190
}
4191
if (!(flags & ImGuiColorEditFlags_NoOptions))
4192
OpenPopupOnItemClick("context");
4193
}
4194
else if (flags & ImGuiColorEditFlags_PickerHueBar)
4195
{
4196
// SV rectangle logic
4197
InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
4198
if (IsItemActive())
4199
{
4200
S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
4201
V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4202
value_changed = value_changed_sv = true;
4203
}
4204
if (!(flags & ImGuiColorEditFlags_NoOptions))
4205
OpenPopupOnItemClick("context");
4206
4207
// Hue bar logic
4208
SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
4209
InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
4210
if (IsItemActive())
4211
{
4212
H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4213
value_changed = value_changed_h = true;
4214
}
4215
}
4216
4217
// Alpha bar logic
4218
if (alpha_bar)
4219
{
4220
SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
4221
InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
4222
if (IsItemActive())
4223
{
4224
col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
4225
value_changed = true;
4226
}
4227
}
4228
PopItemFlag(); // ImGuiItemFlags_NoNav
4229
4230
if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4231
{
4232
SameLine(0, style.ItemInnerSpacing.x);
4233
BeginGroup();
4234
}
4235
4236
if (!(flags & ImGuiColorEditFlags_NoLabel))
4237
{
4238
const char* label_display_end = FindRenderedTextEnd(label);
4239
if (label != label_display_end)
4240
{
4241
if ((flags & ImGuiColorEditFlags_NoSidePreview))
4242
SameLine(0, style.ItemInnerSpacing.x);
4243
TextUnformatted(label, label_display_end);
4244
}
4245
}
4246
4247
if (!(flags & ImGuiColorEditFlags_NoSidePreview))
4248
{
4249
PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
4250
ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4251
if ((flags & ImGuiColorEditFlags_NoLabel))
4252
Text("Current");
4253
ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
4254
if (ref_col != NULL)
4255
{
4256
Text("Original");
4257
ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
4258
if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
4259
{
4260
memcpy(col, ref_col, components * sizeof(float));
4261
value_changed = true;
4262
}
4263
}
4264
PopItemFlag();
4265
EndGroup();
4266
}
4267
4268
// Convert back color to RGB
4269
if (value_changed_h || value_changed_sv)
4270
ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
4271
4272
// R,G,B and H,S,V slider color editor
4273
bool value_changed_fix_hue_wrap = false;
4274
if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
4275
{
4276
PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
4277
ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
4278
ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
4279
if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4280
if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB))
4281
{
4282
// FIXME: Hackily differenciating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
4283
// For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050)
4284
value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
4285
value_changed = true;
4286
}
4287
if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4288
value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
4289
if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
4290
value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
4291
PopItemWidth();
4292
}
4293
4294
// Try to cancel hue wrap (after ColorEdit4 call), if any
4295
if (value_changed_fix_hue_wrap)
4296
{
4297
float new_H, new_S, new_V;
4298
ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
4299
if (new_H <= 0 && H > 0)
4300
{
4301
if (new_V <= 0 && V != new_V)
4302
ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
4303
else if (new_S <= 0)
4304
ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
4305
}
4306
}
4307
4308
ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
4309
ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
4310
ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
4311
4312
const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
4313
ImVec2 sv_cursor_pos;
4314
4315
if (flags & ImGuiColorEditFlags_PickerHueWheel)
4316
{
4317
// Render Hue Wheel
4318
const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
4319
const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
4320
for (int n = 0; n < 6; n++)
4321
{
4322
const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
4323
const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
4324
const int vert_start_idx = draw_list->VtxBuffer.Size;
4325
draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
4326
draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
4327
const int vert_end_idx = draw_list->VtxBuffer.Size;
4328
4329
// Paint colors over existing vertices
4330
ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
4331
ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
4332
ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
4333
}
4334
4335
// Render Cursor + preview on Hue Wheel
4336
float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
4337
float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
4338
ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
4339
float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
4340
int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
4341
draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
4342
draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
4343
draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
4344
4345
// Render SV triangle (rotated according to hue)
4346
ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
4347
ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
4348
ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
4349
ImVec2 uv_white = GetFontTexUvWhitePixel();
4350
draw_list->PrimReserve(6, 6);
4351
draw_list->PrimVtx(tra, uv_white, hue_color32);
4352
draw_list->PrimVtx(trb, uv_white, hue_color32);
4353
draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
4354
draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
4355
draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
4356
draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
4357
draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
4358
sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
4359
}
4360
else if (flags & ImGuiColorEditFlags_PickerHueBar)
4361
{
4362
// Render SV Square
4363
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
4364
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
4365
RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
4366
sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
4367
sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
4368
4369
// Render Hue Bar
4370
for (int i = 0; i < 6; ++i)
4371
draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
4372
float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
4373
RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
4374
RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
4375
}
4376
4377
// Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
4378
float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
4379
draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
4380
draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
4381
draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
4382
4383
// Render alpha bar
4384
if (alpha_bar)
4385
{
4386
float alpha = ImSaturate(col[3]);
4387
ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
4388
RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
4389
draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
4390
float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
4391
RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
4392
RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
4393
}
4394
4395
EndGroup();
4396
4397
if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
4398
value_changed = false;
4399
if (value_changed)
4400
MarkItemEdited(window->DC.LastItemId);
4401
4402
PopID();
4403
4404
return value_changed;
4405
}
4406
4407
// A little colored square. Return true when clicked.
4408
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
4409
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
4410
bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
4411
{
4412
ImGuiWindow* window = GetCurrentWindow();
4413
if (window->SkipItems)
4414
return false;
4415
4416
ImGuiContext& g = *GImGui;
4417
const ImGuiID id = window->GetID(desc_id);
4418
float default_size = GetFrameHeight();
4419
if (size.x == 0.0f)
4420
size.x = default_size;
4421
if (size.y == 0.0f)
4422
size.y = default_size;
4423
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
4424
ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
4425
if (!ItemAdd(bb, id))
4426
return false;
4427
4428
bool hovered, held;
4429
bool pressed = ButtonBehavior(bb, id, &hovered, &held);
4430
4431
if (flags & ImGuiColorEditFlags_NoAlpha)
4432
flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
4433
4434
ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
4435
float grid_step = ImMin(size.x, size.y) / 2.99f;
4436
float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
4437
ImRect bb_inner = bb;
4438
float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
4439
bb_inner.Expand(off);
4440
if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
4441
{
4442
float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
4443
RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
4444
window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
4445
}
4446
else
4447
{
4448
// Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
4449
ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
4450
if (col_source.w < 1.0f)
4451
RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
4452
else
4453
window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
4454
}
4455
RenderNavHighlight(bb, id);
4456
if (g.Style.FrameBorderSize > 0.0f)
4457
RenderFrameBorder(bb.Min, bb.Max, rounding);
4458
else
4459
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
4460
4461
// Drag and Drop Source
4462
// NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
4463
if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
4464
{
4465
if (flags & ImGuiColorEditFlags_NoAlpha)
4466
SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
4467
else
4468
SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
4469
ColorButton(desc_id, col, flags);
4470
SameLine();
4471
TextUnformatted("Color");
4472
EndDragDropSource();
4473
}
4474
4475
// Tooltip
4476
if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
4477
ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
4478
4479
if (pressed)
4480
MarkItemEdited(id);
4481
4482
return pressed;
4483
}
4484
4485
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
4486
{
4487
ImGuiContext& g = *GImGui;
4488
if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
4489
flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
4490
if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
4491
flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
4492
if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
4493
flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
4494
IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
4495
IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
4496
IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
4497
g.ColorEditOptions = flags;
4498
}
4499
4500
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4501
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
4502
{
4503
ImGuiContext& g = *GImGui;
4504
4505
int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4506
BeginTooltipEx(0, true);
4507
4508
const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
4509
if (text_end > text)
4510
{
4511
TextUnformatted(text, text_end);
4512
Separator();
4513
}
4514
4515
ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
4516
ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
4517
SameLine();
4518
if (flags & ImGuiColorEditFlags_NoAlpha)
4519
Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
4520
else
4521
Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
4522
EndTooltip();
4523
}
4524
4525
void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
4526
{
4527
bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
4528
bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
4529
if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
4530
return;
4531
ImGuiContext& g = *GImGui;
4532
ImGuiColorEditFlags opts = g.ColorEditOptions;
4533
if (allow_opt_inputs)
4534
{
4535
if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
4536
if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
4537
if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
4538
}
4539
if (allow_opt_datatype)
4540
{
4541
if (allow_opt_inputs) Separator();
4542
if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
4543
if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
4544
}
4545
4546
if (allow_opt_inputs || allow_opt_datatype)
4547
Separator();
4548
if (Button("Copy as..", ImVec2(-1,0)))
4549
OpenPopup("Copy");
4550
if (BeginPopup("Copy"))
4551
{
4552
int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
4553
char buf[64];
4554
ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
4555
if (Selectable(buf))
4556
SetClipboardText(buf);
4557
ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
4558
if (Selectable(buf))
4559
SetClipboardText(buf);
4560
if (flags & ImGuiColorEditFlags_NoAlpha)
4561
ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
4562
else
4563
ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
4564
if (Selectable(buf))
4565
SetClipboardText(buf);
4566
EndPopup();
4567
}
4568
4569
g.ColorEditOptions = opts;
4570
EndPopup();
4571
}
4572
4573
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
4574
{
4575
bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
4576
bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
4577
if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
4578
return;
4579
ImGuiContext& g = *GImGui;
4580
if (allow_opt_picker)
4581
{
4582
ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
4583
PushItemWidth(picker_size.x);
4584
for (int picker_type = 0; picker_type < 2; picker_type++)
4585
{
4586
// Draw small/thumbnail version of each picker type (over an invisible button for selection)
4587
if (picker_type > 0) Separator();
4588
PushID(picker_type);
4589
ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
4590
if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
4591
if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
4592
ImVec2 backup_pos = GetCursorScreenPos();
4593
if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
4594
g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
4595
SetCursorScreenPos(backup_pos);
4596
ImVec4 dummy_ref_col;
4597
memcpy(&dummy_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
4598
ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
4599
PopID();
4600
}
4601
PopItemWidth();
4602
}
4603
if (allow_opt_alpha_bar)
4604
{
4605
if (allow_opt_picker) Separator();
4606
CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
4607
}
4608
EndPopup();
4609
}
4610
4611
//-------------------------------------------------------------------------
4612
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
4613
//-------------------------------------------------------------------------
4614
// - TreeNode()
4615
// - TreeNodeV()
4616
// - TreeNodeEx()
4617
// - TreeNodeExV()
4618
// - TreeNodeBehavior() [Internal]
4619
// - TreePush()
4620
// - TreePop()
4621
// - TreeAdvanceToLabelPos()
4622
// - GetTreeNodeToLabelSpacing()
4623
// - SetNextTreeNodeOpen()
4624
// - CollapsingHeader()
4625
//-------------------------------------------------------------------------
4626
4627
bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
4628
{
4629
va_list args;
4630
va_start(args, fmt);
4631
bool is_open = TreeNodeExV(str_id, 0, fmt, args);
4632
va_end(args);
4633
return is_open;
4634
}
4635
4636
bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
4637
{
4638
va_list args;
4639
va_start(args, fmt);
4640
bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
4641
va_end(args);
4642
return is_open;
4643
}
4644
4645
bool ImGui::TreeNode(const char* label)
4646
{
4647
ImGuiWindow* window = GetCurrentWindow();
4648
if (window->SkipItems)
4649
return false;
4650
return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
4651
}
4652
4653
bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
4654
{
4655
return TreeNodeExV(str_id, 0, fmt, args);
4656
}
4657
4658
bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
4659
{
4660
return TreeNodeExV(ptr_id, 0, fmt, args);
4661
}
4662
4663
bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
4664
{
4665
ImGuiWindow* window = GetCurrentWindow();
4666
if (window->SkipItems)
4667
return false;
4668
4669
return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
4670
}
4671
4672
bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4673
{
4674
va_list args;
4675
va_start(args, fmt);
4676
bool is_open = TreeNodeExV(str_id, flags, fmt, args);
4677
va_end(args);
4678
return is_open;
4679
}
4680
4681
bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
4682
{
4683
va_list args;
4684
va_start(args, fmt);
4685
bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
4686
va_end(args);
4687
return is_open;
4688
}
4689
4690
bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4691
{
4692
ImGuiWindow* window = GetCurrentWindow();
4693
if (window->SkipItems)
4694
return false;
4695
4696
ImGuiContext& g = *GImGui;
4697
const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4698
return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
4699
}
4700
4701
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
4702
{
4703
ImGuiWindow* window = GetCurrentWindow();
4704
if (window->SkipItems)
4705
return false;
4706
4707
ImGuiContext& g = *GImGui;
4708
const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
4709
return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
4710
}
4711
4712
bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
4713
{
4714
if (flags & ImGuiTreeNodeFlags_Leaf)
4715
return true;
4716
4717
// We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
4718
ImGuiContext& g = *GImGui;
4719
ImGuiWindow* window = g.CurrentWindow;
4720
ImGuiStorage* storage = window->DC.StateStorage;
4721
4722
bool is_open;
4723
if (g.NextTreeNodeOpenCond != 0)
4724
{
4725
if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
4726
{
4727
is_open = g.NextTreeNodeOpenVal;
4728
storage->SetInt(id, is_open);
4729
}
4730
else
4731
{
4732
// We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
4733
const int stored_value = storage->GetInt(id, -1);
4734
if (stored_value == -1)
4735
{
4736
is_open = g.NextTreeNodeOpenVal;
4737
storage->SetInt(id, is_open);
4738
}
4739
else
4740
{
4741
is_open = stored_value != 0;
4742
}
4743
}
4744
g.NextTreeNodeOpenCond = 0;
4745
}
4746
else
4747
{
4748
is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
4749
}
4750
4751
// When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
4752
// NB- If we are above max depth we still allow manually opened nodes to be logged.
4753
if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
4754
is_open = true;
4755
4756
return is_open;
4757
}
4758
4759
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
4760
{
4761
ImGuiWindow* window = GetCurrentWindow();
4762
if (window->SkipItems)
4763
return false;
4764
4765
ImGuiContext& g = *GImGui;
4766
const ImGuiStyle& style = g.Style;
4767
const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
4768
const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
4769
4770
if (!label_end)
4771
label_end = FindRenderedTextEnd(label);
4772
const ImVec2 label_size = CalcTextSize(label, label_end, false);
4773
4774
// We vertically grow up to current line height up the typical widget height.
4775
const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
4776
const float frame_height = ImMax(ImMin(window->DC.CurrentLineSize.y, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
4777
ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
4778
if (display_frame)
4779
{
4780
// Framed header expand a little outside the default padding
4781
frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
4782
frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
4783
}
4784
4785
const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
4786
const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
4787
ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
4788
4789
// For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
4790
// (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
4791
const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
4792
bool is_open = TreeNodeBehaviorIsOpen(id, flags);
4793
bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
4794
4795
// Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
4796
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
4797
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
4798
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4799
window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
4800
4801
bool item_add = ItemAdd(interact_bb, id);
4802
window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
4803
window->DC.LastItemDisplayRect = frame_bb;
4804
4805
if (!item_add)
4806
{
4807
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4808
TreePushRawID(id);
4809
IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
4810
return is_open;
4811
}
4812
4813
// Flags that affects opening behavior:
4814
// - 0 (default) .................... single-click anywhere to open
4815
// - OpenOnDoubleClick .............. double-click anywhere to open
4816
// - OpenOnArrow .................... single-click on arrow to open
4817
// - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
4818
ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers;
4819
if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
4820
button_flags |= ImGuiButtonFlags_AllowItemOverlap;
4821
if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4822
button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
4823
if (!is_leaf)
4824
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
4825
4826
bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
4827
bool hovered, held;
4828
bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
4829
bool toggled = false;
4830
if (!is_leaf)
4831
{
4832
if (pressed)
4833
{
4834
toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
4835
if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
4836
toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
4837
if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
4838
toggled |= g.IO.MouseDoubleClicked[0];
4839
if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
4840
toggled = false;
4841
}
4842
4843
if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
4844
{
4845
toggled = true;
4846
NavMoveRequestCancel();
4847
}
4848
if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
4849
{
4850
toggled = true;
4851
NavMoveRequestCancel();
4852
}
4853
4854
if (toggled)
4855
{
4856
is_open = !is_open;
4857
window->DC.StateStorage->SetInt(id, is_open);
4858
}
4859
}
4860
if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
4861
SetItemAllowOverlap();
4862
4863
// Render
4864
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
4865
const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
4866
ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin;
4867
if (display_frame)
4868
{
4869
// Framed type
4870
RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
4871
RenderNavHighlight(frame_bb, id, nav_highlight_flags);
4872
RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
4873
if (g.LogEnabled)
4874
{
4875
// NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
4876
const char log_prefix[] = "\n##";
4877
const char log_suffix[] = "##";
4878
LogRenderedText(&text_pos, log_prefix, log_prefix+3);
4879
RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
4880
LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
4881
}
4882
else
4883
{
4884
RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
4885
}
4886
}
4887
else
4888
{
4889
// Unframed typed for tree nodes
4890
if (hovered || selected)
4891
{
4892
RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
4893
RenderNavHighlight(frame_bb, id, nav_highlight_flags);
4894
}
4895
4896
if (flags & ImGuiTreeNodeFlags_Bullet)
4897
RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
4898
else if (!is_leaf)
4899
RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
4900
if (g.LogEnabled)
4901
LogRenderedText(&text_pos, ">");
4902
RenderText(text_pos, label, label_end, false);
4903
}
4904
4905
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
4906
TreePushRawID(id);
4907
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
4908
return is_open;
4909
}
4910
4911
void ImGui::TreePush(const char* str_id)
4912
{
4913
ImGuiWindow* window = GetCurrentWindow();
4914
Indent();
4915
window->DC.TreeDepth++;
4916
PushID(str_id ? str_id : "#TreePush");
4917
}
4918
4919
void ImGui::TreePush(const void* ptr_id)
4920
{
4921
ImGuiWindow* window = GetCurrentWindow();
4922
Indent();
4923
window->DC.TreeDepth++;
4924
PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
4925
}
4926
4927
void ImGui::TreePushRawID(ImGuiID id)
4928
{
4929
ImGuiWindow* window = GetCurrentWindow();
4930
Indent();
4931
window->DC.TreeDepth++;
4932
window->IDStack.push_back(id);
4933
}
4934
4935
void ImGui::TreePop()
4936
{
4937
ImGuiContext& g = *GImGui;
4938
ImGuiWindow* window = g.CurrentWindow;
4939
Unindent();
4940
4941
window->DC.TreeDepth--;
4942
if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
4943
if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
4944
{
4945
SetNavID(window->IDStack.back(), g.NavLayer);
4946
NavMoveRequestCancel();
4947
}
4948
window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
4949
4950
IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
4951
PopID();
4952
}
4953
4954
void ImGui::TreeAdvanceToLabelPos()
4955
{
4956
ImGuiContext& g = *GImGui;
4957
g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
4958
}
4959
4960
// Horizontal distance preceding label when using TreeNode() or Bullet()
4961
float ImGui::GetTreeNodeToLabelSpacing()
4962
{
4963
ImGuiContext& g = *GImGui;
4964
return g.FontSize + (g.Style.FramePadding.x * 2.0f);
4965
}
4966
4967
void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
4968
{
4969
ImGuiContext& g = *GImGui;
4970
if (g.CurrentWindow->SkipItems)
4971
return;
4972
g.NextTreeNodeOpenVal = is_open;
4973
g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
4974
}
4975
4976
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
4977
// This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
4978
bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
4979
{
4980
ImGuiWindow* window = GetCurrentWindow();
4981
if (window->SkipItems)
4982
return false;
4983
4984
return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
4985
}
4986
4987
bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
4988
{
4989
ImGuiWindow* window = GetCurrentWindow();
4990
if (window->SkipItems)
4991
return false;
4992
4993
if (p_open && !*p_open)
4994
return false;
4995
4996
ImGuiID id = window->GetID(label);
4997
bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
4998
if (p_open)
4999
{
5000
// Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
5001
ImGuiContext& g = *GImGui;
5002
ImGuiItemHoveredDataBackup last_item_backup;
5003
float button_radius = g.FontSize * 0.5f;
5004
ImVec2 button_center = ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_radius, window->DC.LastItemRect.GetCenter().y);
5005
if (CloseButton(window->GetID((void*)((intptr_t)id+1)), button_center, button_radius))
5006
*p_open = false;
5007
last_item_backup.Restore();
5008
}
5009
5010
return is_open;
5011
}
5012
5013
//-------------------------------------------------------------------------
5014
// [SECTION] Widgets: Selectable
5015
//-------------------------------------------------------------------------
5016
// - Selectable()
5017
//-------------------------------------------------------------------------
5018
5019
// Tip: pass a non-visible label (e.g. "##dummy") then you can use the space to draw other text or image.
5020
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
5021
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
5022
{
5023
ImGuiWindow* window = GetCurrentWindow();
5024
if (window->SkipItems)
5025
return false;
5026
5027
ImGuiContext& g = *GImGui;
5028
const ImGuiStyle& style = g.Style;
5029
5030
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
5031
PopClipRect();
5032
5033
ImGuiID id = window->GetID(label);
5034
ImVec2 label_size = CalcTextSize(label, NULL, true);
5035
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
5036
ImVec2 pos = window->DC.CursorPos;
5037
pos.y += window->DC.CurrentLineTextBaseOffset;
5038
ImRect bb_inner(pos, pos + size);
5039
ItemSize(bb_inner);
5040
5041
// Fill horizontal space.
5042
ImVec2 window_padding = window->WindowPadding;
5043
float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
5044
float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
5045
ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
5046
ImRect bb(pos, pos + size_draw);
5047
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
5048
bb.Max.x += window_padding.x;
5049
5050
// Selectables are tightly packed together, we extend the box to cover spacing between selectable.
5051
float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
5052
float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
5053
float spacing_R = style.ItemSpacing.x - spacing_L;
5054
float spacing_D = style.ItemSpacing.y - spacing_U;
5055
bb.Min.x -= spacing_L;
5056
bb.Min.y -= spacing_U;
5057
bb.Max.x += spacing_R;
5058
bb.Max.y += spacing_D;
5059
if (!ItemAdd(bb, id))
5060
{
5061
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5062
PushColumnClipRect();
5063
return false;
5064
}
5065
5066
// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
5067
ImGuiButtonFlags button_flags = 0;
5068
if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
5069
if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
5070
if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
5071
if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
5072
if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
5073
if (flags & ImGuiSelectableFlags_Disabled)
5074
selected = false;
5075
5076
bool hovered, held;
5077
bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
5078
// Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
5079
if (pressed || hovered)
5080
if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
5081
{
5082
g.NavDisableHighlight = true;
5083
SetNavID(id, window->DC.NavLayerCurrent);
5084
}
5085
if (pressed)
5086
MarkItemEdited(id);
5087
5088
// Render
5089
if (hovered || selected)
5090
{
5091
const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
5092
RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
5093
RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
5094
}
5095
5096
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
5097
{
5098
PushColumnClipRect();
5099
bb.Max.x -= (GetContentRegionMax().x - max_x);
5100
}
5101
5102
if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5103
RenderTextClipped(bb_inner.Min, bb_inner.Max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
5104
if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
5105
5106
// Automatically close popups
5107
if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
5108
CloseCurrentPopup();
5109
return pressed;
5110
}
5111
5112
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
5113
{
5114
if (Selectable(label, *p_selected, flags, size_arg))
5115
{
5116
*p_selected = !*p_selected;
5117
return true;
5118
}
5119
return false;
5120
}
5121
5122
//-------------------------------------------------------------------------
5123
// [SECTION] Widgets: ListBox
5124
//-------------------------------------------------------------------------
5125
// - ListBox()
5126
// - ListBoxHeader()
5127
// - ListBoxFooter()
5128
//-------------------------------------------------------------------------
5129
5130
// FIXME: In principle this function should be called BeginListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5131
// Helper to calculate the size of a listbox and display a label on the right.
5132
// Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an non-visible label e.g. "##empty"
5133
bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
5134
{
5135
ImGuiWindow* window = GetCurrentWindow();
5136
if (window->SkipItems)
5137
return false;
5138
5139
const ImGuiStyle& style = GetStyle();
5140
const ImGuiID id = GetID(label);
5141
const ImVec2 label_size = CalcTextSize(label, NULL, true);
5142
5143
// Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
5144
ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
5145
ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
5146
ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
5147
ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
5148
window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
5149
5150
if (!IsRectVisible(bb.Min, bb.Max))
5151
{
5152
ItemSize(bb.GetSize(), style.FramePadding.y);
5153
ItemAdd(bb, 0, &frame_bb);
5154
return false;
5155
}
5156
5157
BeginGroup();
5158
if (label_size.x > 0)
5159
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
5160
5161
BeginChildFrame(id, frame_bb.GetSize());
5162
return true;
5163
}
5164
5165
// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5166
bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
5167
{
5168
// Size default to hold ~7.25 items.
5169
// We add +25% worth of item height to allow the user to see at a glance if there are more items up/down, without looking at the scrollbar.
5170
// We don't add this extra bit if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
5171
// I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
5172
if (height_in_items < 0)
5173
height_in_items = ImMin(items_count, 7);
5174
const ImGuiStyle& style = GetStyle();
5175
float height_in_items_f = (height_in_items < items_count) ? (height_in_items + 0.25f) : (height_in_items + 0.00f);
5176
5177
// We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
5178
ImVec2 size;
5179
size.x = 0.0f;
5180
size.y = GetTextLineHeightWithSpacing() * height_in_items_f + style.FramePadding.y * 2.0f;
5181
return ListBoxHeader(label, size);
5182
}
5183
5184
// FIXME: In principle this function should be called EndListBox(). We should rename it after re-evaluating if we want to keep the same signature.
5185
void ImGui::ListBoxFooter()
5186
{
5187
ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
5188
const ImRect bb = parent_window->DC.LastItemRect;
5189
const ImGuiStyle& style = GetStyle();
5190
5191
EndChildFrame();
5192
5193
// Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
5194
// We call SameLine() to restore DC.CurrentLine* data
5195
SameLine();
5196
parent_window->DC.CursorPos = bb.Min;
5197
ItemSize(bb, style.FramePadding.y);
5198
EndGroup();
5199
}
5200
5201
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
5202
{
5203
const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
5204
return value_changed;
5205
}
5206
5207
bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
5208
{
5209
if (!ListBoxHeader(label, items_count, height_in_items))
5210
return false;
5211
5212
// Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
5213
ImGuiContext& g = *GImGui;
5214
bool value_changed = false;
5215
ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
5216
while (clipper.Step())
5217
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
5218
{
5219
const bool item_selected = (i == *current_item);
5220
const char* item_text;
5221
if (!items_getter(data, i, &item_text))
5222
item_text = "*Unknown item*";
5223
5224
PushID(i);
5225
if (Selectable(item_text, item_selected))
5226
{
5227
*current_item = i;
5228
value_changed = true;
5229
}
5230
if (item_selected)
5231
SetItemDefaultFocus();
5232
PopID();
5233
}
5234
ListBoxFooter();
5235
if (value_changed)
5236
MarkItemEdited(g.CurrentWindow->DC.LastItemId);
5237
5238
return value_changed;
5239
}
5240
5241
//-------------------------------------------------------------------------
5242
// [SECTION] Widgets: PlotLines, PlotHistogram
5243
//-------------------------------------------------------------------------
5244
// - PlotEx() [Internal]
5245
// - PlotLines()
5246
// - PlotHistogram()
5247
//-------------------------------------------------------------------------
5248
5249
void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size)
5250
{
5251
ImGuiWindow* window = GetCurrentWindow();
5252
if (window->SkipItems)
5253
return;
5254
5255
ImGuiContext& g = *GImGui;
5256
const ImGuiStyle& style = g.Style;
5257
const ImGuiID id = window->GetID(label);
5258
5259
const ImVec2 label_size = CalcTextSize(label, NULL, true);
5260
if (frame_size.x == 0.0f)
5261
frame_size.x = CalcItemWidth();
5262
if (frame_size.y == 0.0f)
5263
frame_size.y = label_size.y + (style.FramePadding.y * 2);
5264
5265
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
5266
const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
5267
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
5268
ItemSize(total_bb, style.FramePadding.y);
5269
if (!ItemAdd(total_bb, 0, &frame_bb))
5270
return;
5271
const bool hovered = ItemHoverable(frame_bb, id);
5272
5273
// Determine scale from values if not specified
5274
if (scale_min == FLT_MAX || scale_max == FLT_MAX)
5275
{
5276
float v_min = FLT_MAX;
5277
float v_max = -FLT_MAX;
5278
for (int i = 0; i < values_count; i++)
5279
{
5280
const float v = values_getter(data, i);
5281
v_min = ImMin(v_min, v);
5282
v_max = ImMax(v_max, v);
5283
}
5284
if (scale_min == FLT_MAX)
5285
scale_min = v_min;
5286
if (scale_max == FLT_MAX)
5287
scale_max = v_max;
5288
}
5289
5290
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
5291
5292
if (values_count > 0)
5293
{
5294
int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5295
int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
5296
5297
// Tooltip on hover
5298
int v_hovered = -1;
5299
if (hovered && inner_bb.Contains(g.IO.MousePos))
5300
{
5301
const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
5302
const int v_idx = (int)(t * item_count);
5303
IM_ASSERT(v_idx >= 0 && v_idx < values_count);
5304
5305
const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
5306
const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
5307
if (plot_type == ImGuiPlotType_Lines)
5308
SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
5309
else if (plot_type == ImGuiPlotType_Histogram)
5310
SetTooltip("%d: %8.4g", v_idx, v0);
5311
v_hovered = v_idx;
5312
}
5313
5314
const float t_step = 1.0f / (float)res_w;
5315
const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
5316
5317
float v0 = values_getter(data, (0 + values_offset) % values_count);
5318
float t0 = 0.0f;
5319
ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
5320
float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
5321
5322
const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
5323
const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
5324
5325
for (int n = 0; n < res_w; n++)
5326
{
5327
const float t1 = t0 + t_step;
5328
const int v1_idx = (int)(t0 * item_count + 0.5f);
5329
IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
5330
const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
5331
const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
5332
5333
// NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
5334
ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
5335
ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
5336
if (plot_type == ImGuiPlotType_Lines)
5337
{
5338
window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
5339
}
5340
else if (plot_type == ImGuiPlotType_Histogram)
5341
{
5342
if (pos1.x >= pos0.x + 2.0f)
5343
pos1.x -= 1.0f;
5344
window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
5345
}
5346
5347
t0 = t1;
5348
tp0 = tp1;
5349
}
5350
}
5351
5352
// Text overlay
5353
if (overlay_text)
5354
RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
5355
5356
if (label_size.x > 0.0f)
5357
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
5358
}
5359
5360
struct ImGuiPlotArrayGetterData
5361
{
5362
const float* Values;
5363
int Stride;
5364
5365
ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
5366
};
5367
5368
static float Plot_ArrayGetter(void* data, int idx)
5369
{
5370
ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
5371
const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
5372
return v;
5373
}
5374
5375
void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
5376
{
5377
ImGuiPlotArrayGetterData data(values, stride);
5378
PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5379
}
5380
5381
void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
5382
{
5383
PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5384
}
5385
5386
void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
5387
{
5388
ImGuiPlotArrayGetterData data(values, stride);
5389
PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5390
}
5391
5392
void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
5393
{
5394
PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
5395
}
5396
5397
//-------------------------------------------------------------------------
5398
// [SECTION] Widgets: Value helpers
5399
// Those is not very useful, legacy API.
5400
//-------------------------------------------------------------------------
5401
// - Value()
5402
//-------------------------------------------------------------------------
5403
5404
void ImGui::Value(const char* prefix, bool b)
5405
{
5406
Text("%s: %s", prefix, (b ? "true" : "false"));
5407
}
5408
5409
void ImGui::Value(const char* prefix, int v)
5410
{
5411
Text("%s: %d", prefix, v);
5412
}
5413
5414
void ImGui::Value(const char* prefix, unsigned int v)
5415
{
5416
Text("%s: %d", prefix, v);
5417
}
5418
5419
void ImGui::Value(const char* prefix, float v, const char* float_format)
5420
{
5421
if (float_format)
5422
{
5423
char fmt[64];
5424
ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
5425
Text(fmt, prefix, v);
5426
}
5427
else
5428
{
5429
Text("%s: %.3f", prefix, v);
5430
}
5431
}
5432
5433
//-------------------------------------------------------------------------
5434
// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
5435
//-------------------------------------------------------------------------
5436
// - ImGuiMenuColumns [Internal]
5437
// - BeginMainMenuBar()
5438
// - EndMainMenuBar()
5439
// - BeginMenuBar()
5440
// - EndMenuBar()
5441
// - BeginMenu()
5442
// - EndMenu()
5443
// - MenuItem()
5444
//-------------------------------------------------------------------------
5445
5446
// Helpers for internal use
5447
ImGuiMenuColumns::ImGuiMenuColumns()
5448
{
5449
Count = 0;
5450
Spacing = Width = NextWidth = 0.0f;
5451
memset(Pos, 0, sizeof(Pos));
5452
memset(NextWidths, 0, sizeof(NextWidths));
5453
}
5454
5455
void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
5456
{
5457
IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
5458
Count = count;
5459
Width = NextWidth = 0.0f;
5460
Spacing = spacing;
5461
if (clear) memset(NextWidths, 0, sizeof(NextWidths));
5462
for (int i = 0; i < Count; i++)
5463
{
5464
if (i > 0 && NextWidths[i] > 0.0f)
5465
Width += Spacing;
5466
Pos[i] = (float)(int)Width;
5467
Width += NextWidths[i];
5468
NextWidths[i] = 0.0f;
5469
}
5470
}
5471
5472
float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
5473
{
5474
NextWidth = 0.0f;
5475
NextWidths[0] = ImMax(NextWidths[0], w0);
5476
NextWidths[1] = ImMax(NextWidths[1], w1);
5477
NextWidths[2] = ImMax(NextWidths[2], w2);
5478
for (int i = 0; i < 3; i++)
5479
NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
5480
return ImMax(Width, NextWidth);
5481
}
5482
5483
float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
5484
{
5485
return ImMax(0.0f, avail_w - Width);
5486
}
5487
5488
// For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
5489
bool ImGui::BeginMainMenuBar()
5490
{
5491
ImGuiContext& g = *GImGui;
5492
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
5493
SetNextWindowPos(ImVec2(0.0f, 0.0f));
5494
SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
5495
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
5496
PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
5497
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
5498
bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
5499
PopStyleVar(2);
5500
g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
5501
if (!is_open)
5502
{
5503
End();
5504
return false;
5505
}
5506
return true; //-V1020
5507
}
5508
5509
void ImGui::EndMainMenuBar()
5510
{
5511
EndMenuBar();
5512
5513
// When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
5514
ImGuiContext& g = *GImGui;
5515
if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
5516
FocusPreviousWindowIgnoringOne(g.NavWindow);
5517
5518
End();
5519
}
5520
5521
bool ImGui::BeginMenuBar()
5522
{
5523
ImGuiWindow* window = GetCurrentWindow();
5524
if (window->SkipItems)
5525
return false;
5526
if (!(window->Flags & ImGuiWindowFlags_MenuBar))
5527
return false;
5528
5529
IM_ASSERT(!window->DC.MenuBarAppending);
5530
BeginGroup(); // Backup position on layer 0
5531
PushID("##menubar");
5532
5533
// We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
5534
// We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
5535
ImRect bar_rect = window->MenuBarRect();
5536
ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
5537
clip_rect.ClipWith(window->OuterRectClipped);
5538
PushClipRect(clip_rect.Min, clip_rect.Max, false);
5539
5540
window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
5541
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
5542
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
5543
window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
5544
window->DC.MenuBarAppending = true;
5545
AlignTextToFramePadding();
5546
return true;
5547
}
5548
5549
void ImGui::EndMenuBar()
5550
{
5551
ImGuiWindow* window = GetCurrentWindow();
5552
if (window->SkipItems)
5553
return;
5554
ImGuiContext& g = *GImGui;
5555
5556
// Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
5557
if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
5558
{
5559
ImGuiWindow* nav_earliest_child = g.NavWindow;
5560
while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
5561
nav_earliest_child = nav_earliest_child->ParentWindow;
5562
if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
5563
{
5564
// To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
5565
// This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
5566
IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
5567
FocusWindow(window);
5568
SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
5569
g.NavLayer = ImGuiNavLayer_Menu;
5570
g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
5571
g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5572
NavMoveRequestCancel();
5573
}
5574
}
5575
5576
IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
5577
IM_ASSERT(window->DC.MenuBarAppending);
5578
PopClipRect();
5579
PopID();
5580
window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
5581
window->DC.GroupStack.back().AdvanceCursor = false;
5582
EndGroup(); // Restore position on layer 0
5583
window->DC.LayoutType = ImGuiLayoutType_Vertical;
5584
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
5585
window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
5586
window->DC.MenuBarAppending = false;
5587
}
5588
5589
bool ImGui::BeginMenu(const char* label, bool enabled)
5590
{
5591
ImGuiWindow* window = GetCurrentWindow();
5592
if (window->SkipItems)
5593
return false;
5594
5595
ImGuiContext& g = *GImGui;
5596
const ImGuiStyle& style = g.Style;
5597
const ImGuiID id = window->GetID(label);
5598
5599
ImVec2 label_size = CalcTextSize(label, NULL, true);
5600
5601
bool pressed;
5602
bool menu_is_open = IsPopupOpen(id);
5603
bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].OpenParentId == window->IDStack.back());
5604
ImGuiWindow* backed_nav_window = g.NavWindow;
5605
if (menuset_is_open)
5606
g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
5607
5608
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
5609
// However the final position is going to be different! It is choosen by FindBestWindowPosForPopup().
5610
// e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
5611
ImVec2 popup_pos, pos = window->DC.CursorPos;
5612
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5613
{
5614
// Menu inside an horizontal menu bar
5615
// Selectable extend their highlight by half ItemSpacing in each direction.
5616
// For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
5617
popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight());
5618
window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5619
PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
5620
float w = label_size.x;
5621
pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
5622
PopStyleVar();
5623
window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5624
}
5625
else
5626
{
5627
// Menu inside a menu
5628
popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
5629
float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
5630
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
5631
pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
5632
if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5633
RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
5634
if (!enabled) PopStyleColor();
5635
}
5636
5637
const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
5638
if (menuset_is_open)
5639
g.NavWindow = backed_nav_window;
5640
5641
bool want_open = false, want_close = false;
5642
if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5643
{
5644
// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
5645
bool moving_within_opened_triangle = false;
5646
if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
5647
{
5648
if (ImGuiWindow* next_window = g.OpenPopupStack[g.BeginPopupStack.Size].Window)
5649
{
5650
// FIXME-DPI: Values should be derived from a master "scale" factor.
5651
ImRect next_window_rect = next_window->Rect();
5652
ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
5653
ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
5654
ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
5655
float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
5656
ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
5657
tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
5658
tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
5659
moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
5660
//window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
5661
}
5662
}
5663
5664
want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
5665
want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
5666
5667
if (g.NavActivateId == id)
5668
{
5669
want_close = menu_is_open;
5670
want_open = !menu_is_open;
5671
}
5672
if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
5673
{
5674
want_open = true;
5675
NavMoveRequestCancel();
5676
}
5677
}
5678
else
5679
{
5680
// Menu bar
5681
if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
5682
{
5683
want_close = true;
5684
want_open = menu_is_open = false;
5685
}
5686
else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
5687
{
5688
want_open = true;
5689
}
5690
else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
5691
{
5692
want_open = true;
5693
NavMoveRequestCancel();
5694
}
5695
}
5696
5697
if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
5698
want_close = true;
5699
if (want_close && IsPopupOpen(id))
5700
ClosePopupToLevel(g.BeginPopupStack.Size, true);
5701
5702
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
5703
5704
if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
5705
{
5706
// Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
5707
OpenPopup(label);
5708
return false;
5709
}
5710
5711
menu_is_open |= want_open;
5712
if (want_open)
5713
OpenPopup(label);
5714
5715
if (menu_is_open)
5716
{
5717
// Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
5718
SetNextWindowPos(popup_pos, ImGuiCond_Always);
5719
ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
5720
if (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
5721
flags |= ImGuiWindowFlags_ChildWindow;
5722
menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5723
}
5724
5725
return menu_is_open;
5726
}
5727
5728
void ImGui::EndMenu()
5729
{
5730
// Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
5731
// A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
5732
// However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
5733
ImGuiContext& g = *GImGui;
5734
ImGuiWindow* window = g.CurrentWindow;
5735
if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
5736
{
5737
ClosePopupToLevel(g.BeginPopupStack.Size, true);
5738
NavMoveRequestCancel();
5739
}
5740
5741
EndPopup();
5742
}
5743
5744
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
5745
{
5746
ImGuiWindow* window = GetCurrentWindow();
5747
if (window->SkipItems)
5748
return false;
5749
5750
ImGuiContext& g = *GImGui;
5751
ImGuiStyle& style = g.Style;
5752
ImVec2 pos = window->DC.CursorPos;
5753
ImVec2 label_size = CalcTextSize(label, NULL, true);
5754
5755
ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
5756
bool pressed;
5757
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
5758
{
5759
// Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
5760
// Note that in this situation we render neither the shortcut neither the selected tick mark
5761
float w = label_size.x;
5762
window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
5763
PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
5764
pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
5765
PopStyleVar();
5766
window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
5767
}
5768
else
5769
{
5770
ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
5771
float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
5772
float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
5773
pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
5774
if (shortcut_size.x > 0.0f)
5775
{
5776
PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
5777
RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
5778
PopStyleColor();
5779
}
5780
if (selected)
5781
RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
5782
}
5783
5784
IMGUI_TEST_ENGINE_ITEM_INFO(window->DC.LastItemId, label, window->DC.ItemFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
5785
return pressed;
5786
}
5787
5788
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
5789
{
5790
if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
5791
{
5792
if (p_selected)
5793
*p_selected = !*p_selected;
5794
return true;
5795
}
5796
return false;
5797
}
5798
5799
//-------------------------------------------------------------------------
5800
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
5801
//-------------------------------------------------------------------------
5802
// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
5803
// and some of the construct which are not used in Master may be left here to facilitate merging.
5804
//-------------------------------------------------------------------------
5805
// - BeginTabBar()
5806
// - BeginTabBarEx() [Internal]
5807
// - EndTabBar()
5808
// - TabBarLayout() [Internal]
5809
// - TabBarCalcTabID() [Internal]
5810
// - TabBarCalcMaxTabWidth() [Internal]
5811
// - TabBarFindTabById() [Internal]
5812
// - TabBarRemoveTab() [Internal]
5813
// - TabBarCloseTab() [Internal]
5814
// - TabBarScrollClamp()v
5815
// - TabBarScrollToTab() [Internal]
5816
// - TabBarQueueChangeTabOrder() [Internal]
5817
// - TabBarScrollingButtons() [Internal]
5818
// - TabBarTabListPopupButton() [Internal]
5819
//-------------------------------------------------------------------------
5820
5821
namespace ImGui
5822
{
5823
static void TabBarLayout(ImGuiTabBar* tab_bar);
5824
static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label);
5825
static float TabBarCalcMaxTabWidth();
5826
static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
5827
static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab);
5828
static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar);
5829
static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
5830
}
5831
5832
ImGuiTabBar::ImGuiTabBar()
5833
{
5834
ID = 0;
5835
SelectedTabId = NextSelectedTabId = VisibleTabId = 0;
5836
CurrFrameVisible = PrevFrameVisible = -1;
5837
ContentsHeight = 0.0f;
5838
OffsetMax = OffsetNextTab = 0.0f;
5839
ScrollingAnim = ScrollingTarget = 0.0f;
5840
Flags = ImGuiTabBarFlags_None;
5841
ReorderRequestTabId = 0;
5842
ReorderRequestDir = 0;
5843
WantLayout = VisibleTabWasSubmitted = false;
5844
LastTabItemIdx = -1;
5845
}
5846
5847
static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs)
5848
{
5849
const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
5850
const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
5851
return (int)(a->Offset - b->Offset);
5852
}
5853
5854
static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs)
5855
{
5856
const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs;
5857
const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs;
5858
if (int d = (int)(b->Width - a->Width))
5859
return d;
5860
return (b->Index - a->Index);
5861
}
5862
5863
bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
5864
{
5865
ImGuiContext& g = *GImGui;
5866
ImGuiWindow* window = g.CurrentWindow;
5867
if (window->SkipItems)
5868
return false;
5869
5870
ImGuiID id = window->GetID(str_id);
5871
ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
5872
ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->InnerClipRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
5873
tab_bar->ID = id;
5874
return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused);
5875
}
5876
5877
bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
5878
{
5879
ImGuiContext& g = *GImGui;
5880
ImGuiWindow* window = g.CurrentWindow;
5881
if (window->SkipItems)
5882
return false;
5883
5884
if ((flags & ImGuiTabBarFlags_DockNode) == 0)
5885
window->IDStack.push_back(tab_bar->ID);
5886
5887
g.CurrentTabBar.push_back(tab_bar);
5888
if (tab_bar->CurrFrameVisible == g.FrameCount)
5889
{
5890
//IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount);
5891
IM_ASSERT(0);
5892
return true;
5893
}
5894
5895
// When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order.
5896
// Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user.
5897
if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1)
5898
ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset);
5899
5900
// Flags
5901
if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
5902
flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
5903
5904
tab_bar->Flags = flags;
5905
tab_bar->BarRect = tab_bar_bb;
5906
tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
5907
tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
5908
tab_bar->CurrFrameVisible = g.FrameCount;
5909
tab_bar->FramePadding = g.Style.FramePadding;
5910
5911
// Layout
5912
ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight()));
5913
window->DC.CursorPos.x = tab_bar->BarRect.Min.x;
5914
5915
// Draw separator
5916
const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab);
5917
const float y = tab_bar->BarRect.Max.y - 1.0f;
5918
{
5919
const float separator_min_x = tab_bar->BarRect.Min.x - window->WindowPadding.x;
5920
const float separator_max_x = tab_bar->BarRect.Max.x + window->WindowPadding.x;
5921
window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f);
5922
}
5923
return true;
5924
}
5925
5926
void ImGui::EndTabBar()
5927
{
5928
ImGuiContext& g = *GImGui;
5929
ImGuiWindow* window = g.CurrentWindow;
5930
if (window->SkipItems)
5931
return;
5932
5933
IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar
5934
ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
5935
if (tab_bar->WantLayout)
5936
TabBarLayout(tab_bar);
5937
5938
// Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
5939
const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
5940
if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
5941
tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f);
5942
else
5943
window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight;
5944
5945
if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
5946
PopID();
5947
g.CurrentTabBar.pop_back();
5948
}
5949
5950
// This is called only once a frame before by the first call to ItemTab()
5951
// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
5952
static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
5953
{
5954
ImGuiContext& g = *GImGui;
5955
tab_bar->WantLayout = false;
5956
5957
// Garbage collect
5958
int tab_dst_n = 0;
5959
for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
5960
{
5961
ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
5962
if (tab->LastFrameVisible < tab_bar->PrevFrameVisible)
5963
{
5964
if (tab->ID == tab_bar->SelectedTabId)
5965
tab_bar->SelectedTabId = 0;
5966
continue;
5967
}
5968
if (tab_dst_n != tab_src_n)
5969
tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
5970
tab_dst_n++;
5971
}
5972
if (tab_bar->Tabs.Size != tab_dst_n)
5973
tab_bar->Tabs.resize(tab_dst_n);
5974
5975
// Setup next selected tab
5976
ImGuiID scroll_track_selected_tab_id = 0;
5977
if (tab_bar->NextSelectedTabId)
5978
{
5979
tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
5980
tab_bar->NextSelectedTabId = 0;
5981
scroll_track_selected_tab_id = tab_bar->SelectedTabId;
5982
}
5983
5984
// Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
5985
if (tab_bar->ReorderRequestTabId != 0)
5986
{
5987
if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId))
5988
{
5989
//IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
5990
int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir;
5991
if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size)
5992
{
5993
ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
5994
ImGuiTabItem item_tmp = *tab1;
5995
*tab1 = *tab2;
5996
*tab2 = item_tmp;
5997
if (tab2->ID == tab_bar->SelectedTabId)
5998
scroll_track_selected_tab_id = tab2->ID;
5999
tab1 = tab2 = NULL;
6000
}
6001
if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
6002
MarkIniSettingsDirty();
6003
}
6004
tab_bar->ReorderRequestTabId = 0;
6005
}
6006
6007
// Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
6008
const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
6009
if (tab_list_popup_button)
6010
if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x!
6011
scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
6012
6013
ImVector<ImGuiTabBarSortItem>& width_sort_buffer = g.TabSortByWidthBuffer;
6014
width_sort_buffer.resize(tab_bar->Tabs.Size);
6015
6016
// Compute ideal widths
6017
float width_total_contents = 0.0f;
6018
ImGuiTabItem* most_recently_selected_tab = NULL;
6019
bool found_selected_tab_id = false;
6020
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6021
{
6022
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6023
IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
6024
6025
if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
6026
most_recently_selected_tab = tab;
6027
if (tab->ID == tab_bar->SelectedTabId)
6028
found_selected_tab_id = true;
6029
6030
// Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
6031
// Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
6032
// and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
6033
const char* tab_name = tab_bar->GetTabName(tab);
6034
tab->WidthContents = TabItemCalcSize(tab_name, (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true).x;
6035
6036
width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents;
6037
6038
// Store data so we can build an array sorted by width if we need to shrink tabs down
6039
width_sort_buffer[tab_n].Index = tab_n;
6040
width_sort_buffer[tab_n].Width = tab->WidthContents;
6041
}
6042
6043
// Compute width
6044
const float width_avail = tab_bar->BarRect.GetWidth();
6045
float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f;
6046
if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown))
6047
{
6048
// If we don't have enough room, resize down the largest tabs first
6049
if (tab_bar->Tabs.Size > 1)
6050
ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer);
6051
int tab_count_same_width = 1;
6052
while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size)
6053
{
6054
while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width)
6055
tab_count_same_width++;
6056
float width_to_remove_per_tab_max = (tab_count_same_width < tab_bar->Tabs.Size) ? (width_sort_buffer[0].Width - width_sort_buffer[tab_count_same_width].Width) : (width_sort_buffer[0].Width - 1.0f);
6057
float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max);
6058
for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++)
6059
width_sort_buffer[tab_n].Width -= width_to_remove_per_tab;
6060
width_excess -= width_to_remove_per_tab * tab_count_same_width;
6061
}
6062
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6063
tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width;
6064
}
6065
else
6066
{
6067
const float tab_max_width = TabBarCalcMaxTabWidth();
6068
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6069
{
6070
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6071
tab->Width = ImMin(tab->WidthContents, tab_max_width);
6072
}
6073
}
6074
6075
// Layout all active tabs
6076
float offset_x = 0.0f;
6077
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6078
{
6079
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6080
tab->Offset = offset_x;
6081
if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID)
6082
scroll_track_selected_tab_id = tab->ID;
6083
offset_x += tab->Width + g.Style.ItemInnerSpacing.x;
6084
}
6085
tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f);
6086
tab_bar->OffsetNextTab = 0.0f;
6087
6088
// Horizontal scrolling buttons
6089
const bool scrolling_buttons = (tab_bar->OffsetMax > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll);
6090
if (scrolling_buttons)
6091
if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x!
6092
scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
6093
6094
// If we have lost the selected tab, select the next most recently active one
6095
if (found_selected_tab_id == false)
6096
tab_bar->SelectedTabId = 0;
6097
if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
6098
scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
6099
6100
// Lock in visible tab
6101
tab_bar->VisibleTabId = tab_bar->SelectedTabId;
6102
tab_bar->VisibleTabWasSubmitted = false;
6103
6104
// Update scrolling
6105
if (scroll_track_selected_tab_id)
6106
if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id))
6107
TabBarScrollToTab(tab_bar, scroll_track_selected_tab);
6108
tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
6109
tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
6110
const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f);
6111
if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
6112
tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed);
6113
6114
// Clear name buffers
6115
if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
6116
tab_bar->TabsNames.Buf.resize(0);
6117
}
6118
6119
// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack.
6120
static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label)
6121
{
6122
if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
6123
{
6124
ImGuiID id = ImHashStr(label, 0);
6125
KeepAliveID(id);
6126
return id;
6127
}
6128
else
6129
{
6130
ImGuiWindow* window = GImGui->CurrentWindow;
6131
return window->GetID(label);
6132
}
6133
}
6134
6135
static float ImGui::TabBarCalcMaxTabWidth()
6136
{
6137
ImGuiContext& g = *GImGui;
6138
return g.FontSize * 20.0f;
6139
}
6140
6141
ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
6142
{
6143
if (tab_id != 0)
6144
for (int n = 0; n < tab_bar->Tabs.Size; n++)
6145
if (tab_bar->Tabs[n].ID == tab_id)
6146
return &tab_bar->Tabs[n];
6147
return NULL;
6148
}
6149
6150
// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
6151
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
6152
{
6153
if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
6154
tab_bar->Tabs.erase(tab);
6155
if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; }
6156
if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; }
6157
if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
6158
}
6159
6160
// Called on manual closure attempt
6161
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
6162
{
6163
if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
6164
{
6165
// This will remove a frame of lag for selecting another tab on closure.
6166
// However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure
6167
tab->LastFrameVisible = -1;
6168
tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
6169
}
6170
else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
6171
{
6172
// Actually select before expecting closure
6173
tab_bar->NextSelectedTabId = tab->ID;
6174
}
6175
}
6176
6177
static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
6178
{
6179
scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth());
6180
return ImMax(scrolling, 0.0f);
6181
}
6182
6183
static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
6184
{
6185
ImGuiContext& g = *GImGui;
6186
float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar)
6187
int order = tab_bar->GetTabOrder(tab);
6188
float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f);
6189
float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f);
6190
if (tab_bar->ScrollingTarget > tab_x1)
6191
tab_bar->ScrollingTarget = tab_x1;
6192
if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2)
6193
tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth();
6194
}
6195
6196
void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir)
6197
{
6198
IM_ASSERT(dir == -1 || dir == +1);
6199
IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
6200
tab_bar->ReorderRequestTabId = tab->ID;
6201
tab_bar->ReorderRequestDir = dir;
6202
}
6203
6204
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
6205
{
6206
ImGuiContext& g = *GImGui;
6207
ImGuiWindow* window = g.CurrentWindow;
6208
6209
const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
6210
const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
6211
6212
const ImVec2 backup_cursor_pos = window->DC.CursorPos;
6213
//window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255));
6214
6215
const ImRect avail_bar_rect = tab_bar->BarRect;
6216
bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f)));
6217
if (want_clip_rect)
6218
PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true);
6219
6220
ImGuiTabItem* tab_to_select = NULL;
6221
6222
int select_dir = 0;
6223
ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
6224
arrow_col.w *= 0.5f;
6225
6226
PushStyleColor(ImGuiCol_Text, arrow_col);
6227
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
6228
const float backup_repeat_delay = g.IO.KeyRepeatDelay;
6229
const float backup_repeat_rate = g.IO.KeyRepeatRate;
6230
g.IO.KeyRepeatDelay = 0.250f;
6231
g.IO.KeyRepeatRate = 0.200f;
6232
window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y);
6233
if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
6234
select_dir = -1;
6235
window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y);
6236
if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat))
6237
select_dir = +1;
6238
PopStyleColor(2);
6239
g.IO.KeyRepeatRate = backup_repeat_rate;
6240
g.IO.KeyRepeatDelay = backup_repeat_delay;
6241
6242
if (want_clip_rect)
6243
PopClipRect();
6244
6245
if (select_dir != 0)
6246
if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
6247
{
6248
int selected_order = tab_bar->GetTabOrder(tab_item);
6249
int target_order = selected_order + select_dir;
6250
tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible
6251
}
6252
window->DC.CursorPos = backup_cursor_pos;
6253
tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
6254
6255
return tab_to_select;
6256
}
6257
6258
static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
6259
{
6260
ImGuiContext& g = *GImGui;
6261
ImGuiWindow* window = g.CurrentWindow;
6262
6263
// We use g.Style.FramePadding.y to match the square ArrowButton size
6264
const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
6265
const ImVec2 backup_cursor_pos = window->DC.CursorPos;
6266
window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
6267
tab_bar->BarRect.Min.x += tab_list_popup_button_width;
6268
6269
ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
6270
arrow_col.w *= 0.5f;
6271
PushStyleColor(ImGuiCol_Text, arrow_col);
6272
PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
6273
bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview);
6274
PopStyleColor(2);
6275
6276
ImGuiTabItem* tab_to_select = NULL;
6277
if (open)
6278
{
6279
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
6280
{
6281
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
6282
const char* tab_name = tab_bar->GetTabName(tab);
6283
if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
6284
tab_to_select = tab;
6285
}
6286
EndCombo();
6287
}
6288
6289
window->DC.CursorPos = backup_cursor_pos;
6290
return tab_to_select;
6291
}
6292
6293
//-------------------------------------------------------------------------
6294
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
6295
//-------------------------------------------------------------------------
6296
// [BETA API] API may evolve! This code has been extracted out of the Docking branch,
6297
// and some of the construct which are not used in Master may be left here to facilitate merging.
6298
//-------------------------------------------------------------------------
6299
// - BeginTabItem()
6300
// - EndTabItem()
6301
// - TabItemEx() [Internal]
6302
// - SetTabItemClosed()
6303
// - TabItemCalcSize() [Internal]
6304
// - TabItemBackground() [Internal]
6305
// - TabItemLabelAndCloseButton() [Internal]
6306
//-------------------------------------------------------------------------
6307
6308
bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
6309
{
6310
ImGuiContext& g = *GImGui;
6311
if (g.CurrentWindow->SkipItems)
6312
return false;
6313
6314
IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6315
ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6316
bool ret = TabItemEx(tab_bar, label, p_open, flags);
6317
if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
6318
{
6319
ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
6320
g.CurrentWindow->IDStack.push_back(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
6321
}
6322
return ret;
6323
}
6324
6325
void ImGui::EndTabItem()
6326
{
6327
ImGuiContext& g = *GImGui;
6328
if (g.CurrentWindow->SkipItems)
6329
return;
6330
6331
IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!");
6332
ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6333
IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()");
6334
ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
6335
if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
6336
g.CurrentWindow->IDStack.pop_back();
6337
}
6338
6339
bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags)
6340
{
6341
// Layout whole tab bar if not already done
6342
if (tab_bar->WantLayout)
6343
TabBarLayout(tab_bar);
6344
6345
ImGuiContext& g = *GImGui;
6346
ImGuiWindow* window = g.CurrentWindow;
6347
if (window->SkipItems)
6348
return false;
6349
6350
const ImGuiStyle& style = g.Style;
6351
const ImGuiID id = TabBarCalcTabID(tab_bar, label);
6352
6353
// If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
6354
if (p_open && !*p_open)
6355
{
6356
PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
6357
ItemAdd(ImRect(), id);
6358
PopItemFlag();
6359
return false;
6360
}
6361
6362
// Calculate tab contents size
6363
ImVec2 size = TabItemCalcSize(label, p_open != NULL);
6364
6365
// Acquire tab data
6366
ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
6367
bool tab_is_new = false;
6368
if (tab == NULL)
6369
{
6370
tab_bar->Tabs.push_back(ImGuiTabItem());
6371
tab = &tab_bar->Tabs.back();
6372
tab->ID = id;
6373
tab->Width = size.x;
6374
tab_is_new = true;
6375
}
6376
tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_ptr(tab);
6377
tab->WidthContents = size.x;
6378
6379
if (p_open == NULL)
6380
flags |= ImGuiTabItemFlags_NoCloseButton;
6381
6382
const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
6383
const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
6384
const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
6385
tab->LastFrameVisible = g.FrameCount;
6386
tab->Flags = flags;
6387
6388
// Append name with zero-terminator
6389
tab->NameOffset = tab_bar->TabsNames.size();
6390
tab_bar->TabsNames.append(label, label + strlen(label) + 1);
6391
6392
// If we are not reorderable, always reset offset based on submission order.
6393
// (We already handled layout and sizing using the previous known order, but sizing is not affected by order!)
6394
if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
6395
{
6396
tab->Offset = tab_bar->OffsetNextTab;
6397
tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x;
6398
}
6399
6400
// Update selected tab
6401
if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
6402
if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
6403
tab_bar->NextSelectedTabId = id; // New tabs gets activated
6404
6405
// Lock visibility
6406
bool tab_contents_visible = (tab_bar->VisibleTabId == id);
6407
if (tab_contents_visible)
6408
tab_bar->VisibleTabWasSubmitted = true;
6409
6410
// On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
6411
if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing)
6412
if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
6413
tab_contents_visible = true;
6414
6415
if (tab_appearing && !(tab_bar_appearing && !tab_is_new))
6416
{
6417
PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true);
6418
ItemAdd(ImRect(), id);
6419
PopItemFlag();
6420
return tab_contents_visible;
6421
}
6422
6423
if (tab_bar->SelectedTabId == id)
6424
tab->LastFrameSelected = g.FrameCount;
6425
6426
// Backup current layout position
6427
const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
6428
6429
// Layout
6430
size.x = tab->Width;
6431
window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f);
6432
ImVec2 pos = window->DC.CursorPos;
6433
ImRect bb(pos, pos + size);
6434
6435
// We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation)
6436
bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x);
6437
if (want_clip_rect)
6438
PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true);
6439
6440
ItemSize(bb, style.FramePadding.y);
6441
if (!ItemAdd(bb, id))
6442
{
6443
if (want_clip_rect)
6444
PopClipRect();
6445
window->DC.CursorPos = backup_main_cursor_pos;
6446
return tab_contents_visible;
6447
}
6448
6449
// Click to Select a tab
6450
ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap);
6451
if (g.DragDropActive)
6452
button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
6453
bool hovered, held;
6454
bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6455
hovered |= (g.HoveredId == id);
6456
if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar
6457
tab_bar->NextSelectedTabId = id;
6458
6459
// Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered)
6460
if (!held)
6461
SetItemAllowOverlap();
6462
6463
// Drag and drop: re-order tabs
6464
if (held && !tab_appearing && IsMouseDragging(0))
6465
{
6466
if (!g.DragDropActive && (tab_bar->Flags & ImGuiTabBarFlags_Reorderable))
6467
{
6468
// While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
6469
if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
6470
{
6471
if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
6472
TabBarQueueChangeTabOrder(tab_bar, tab, -1);
6473
}
6474
else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
6475
{
6476
if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable)
6477
TabBarQueueChangeTabOrder(tab_bar, tab, +1);
6478
}
6479
}
6480
}
6481
6482
#if 0
6483
if (hovered && g.HoveredIdNotActiveTimer > 0.50f && bb.GetWidth() < tab->WidthContents)
6484
{
6485
// Enlarge tab display when hovering
6486
bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f));
6487
display_draw_list = GetOverlayDrawList(window);
6488
TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
6489
}
6490
#endif
6491
6492
// Render tab shape
6493
ImDrawList* display_draw_list = window->DrawList;
6494
const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused));
6495
TabItemBackground(display_draw_list, bb, flags, tab_col);
6496
RenderNavHighlight(bb, id);
6497
6498
// Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
6499
const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
6500
if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)))
6501
tab_bar->NextSelectedTabId = id;
6502
6503
if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
6504
flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
6505
6506
// Render tab label, process close button
6507
const ImGuiID close_button_id = p_open ? window->GetID((void*)((intptr_t)id + 1)) : 0;
6508
bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id);
6509
if (just_closed && p_open != NULL)
6510
{
6511
*p_open = false;
6512
TabBarCloseTab(tab_bar, tab);
6513
}
6514
6515
// Restore main window position so user can draw there
6516
if (want_clip_rect)
6517
PopClipRect();
6518
window->DC.CursorPos = backup_main_cursor_pos;
6519
6520
// Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer)
6521
if (g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > 0.50f)
6522
if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip))
6523
SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
6524
6525
return tab_contents_visible;
6526
}
6527
6528
// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
6529
// To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem()
6530
void ImGui::SetTabItemClosed(const char* label)
6531
{
6532
ImGuiContext& g = *GImGui;
6533
bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode);
6534
if (is_within_manual_tab_bar)
6535
{
6536
ImGuiTabBar* tab_bar = g.CurrentTabBar.back();
6537
IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem()
6538
ImGuiID tab_id = TabBarCalcTabID(tab_bar, label);
6539
TabBarRemoveTab(tab_bar, tab_id);
6540
}
6541
}
6542
6543
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
6544
{
6545
ImGuiContext& g = *GImGui;
6546
ImVec2 label_size = CalcTextSize(label, NULL, true);
6547
ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
6548
if (has_close_button)
6549
size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
6550
else
6551
size.x += g.Style.FramePadding.x + 1.0f;
6552
return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
6553
}
6554
6555
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
6556
{
6557
// While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.
6558
ImGuiContext& g = *GImGui;
6559
const float width = bb.GetWidth();
6560
IM_UNUSED(flags);
6561
IM_ASSERT(width > 0.0f);
6562
const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f));
6563
const float y1 = bb.Min.y + 1.0f;
6564
const float y2 = bb.Max.y - 1.0f;
6565
draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
6566
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
6567
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
6568
draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
6569
draw_list->PathFillConvex(col);
6570
if (g.Style.TabBorderSize > 0.0f)
6571
{
6572
draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
6573
draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
6574
draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
6575
draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
6576
draw_list->PathStroke(GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize);
6577
}
6578
}
6579
6580
// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
6581
// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
6582
bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id)
6583
{
6584
ImGuiContext& g = *GImGui;
6585
ImVec2 label_size = CalcTextSize(label, NULL, true);
6586
if (bb.GetWidth() <= 1.0f)
6587
return false;
6588
6589
// Render text label (with clipping + alpha gradient) + unsaved marker
6590
const char* TAB_UNSAVED_MARKER = "*";
6591
ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
6592
if (flags & ImGuiTabItemFlags_UnsavedDocument)
6593
{
6594
text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x;
6595
ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + frame_padding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + frame_padding.y + (float)(int)(-g.FontSize * 0.25f));
6596
RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - frame_padding, TAB_UNSAVED_MARKER, NULL, NULL);
6597
}
6598
ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
6599
6600
// Close Button
6601
// We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
6602
// 'hovered' will be true when hovering the Tab but NOT when hovering the close button
6603
// 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
6604
// 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
6605
bool close_button_pressed = false;
6606
bool close_button_visible = false;
6607
if (close_button_id != 0)
6608
if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id)
6609
close_button_visible = true;
6610
if (close_button_visible)
6611
{
6612
ImGuiItemHoveredDataBackup last_item_backup;
6613
const float close_button_sz = g.FontSize * 0.5f;
6614
if (CloseButton(close_button_id, ImVec2(bb.Max.x - frame_padding.x - close_button_sz, bb.Min.y + frame_padding.y + close_button_sz), close_button_sz))
6615
close_button_pressed = true;
6616
last_item_backup.Restore();
6617
6618
// Close with middle mouse button
6619
if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
6620
close_button_pressed = true;
6621
6622
text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f;
6623
}
6624
6625
// Label with ellipsis
6626
// FIXME: This should be extracted into a helper but the use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment
6627
const char* label_display_end = FindRenderedTextEnd(label);
6628
if (label_size.x > text_ellipsis_clip_bb.GetWidth())
6629
{
6630
const int ellipsis_dot_count = 3;
6631
const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f;
6632
const char* label_end = NULL;
6633
float label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, text_ellipsis_clip_bb.GetWidth() - ellipsis_width + 1.0f, 0.0f, label, label_display_end, &label_end).x;
6634
if (label_end == label && label_end < label_display_end) // Always display at least 1 character if there's no room for character + ellipsis
6635
{
6636
label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end);
6637
label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x;
6638
}
6639
while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space
6640
{
6641
label_end--;
6642
label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte
6643
}
6644
RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f));
6645
6646
const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f;
6647
if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x)
6648
RenderPixelEllipsis(draw_list, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text));
6649
}
6650
else
6651
{
6652
RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f));
6653
}
6654
6655
return close_button_pressed;
6656
}
6657
6658