OXYGENHUB
◆ rrdc-perf enhanced-x16-emulator / src / extern / imgui / imgui_widgets.cpp
504.3 KiB · C++ · last changed chore(): initial commit 25 days ago source blame history raw
1
// dear imgui, v1.91.5
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: Typing-Select support
22
// [SECTION] Widgets: Box-Select support
23
// [SECTION] Widgets: Multi-Select support
24
// [SECTION] Widgets: Multi-Select helpers
25
// [SECTION] Widgets: ListBox
26
// [SECTION] Widgets: PlotLines, PlotHistogram
27
// [SECTION] Widgets: Value helpers
28
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
29
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
30
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
31
// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
32
33
*/
34
35
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
36
#define _CRT_SECURE_NO_WARNINGS
37
#endif
38
39
#ifndef IMGUI_DEFINE_MATH_OPERATORS
40
#define IMGUI_DEFINE_MATH_OPERATORS
41
#endif
42
43
#include "imgui.h"
44
#ifndef IMGUI_DISABLE
45
#include "imgui_internal.h"
46
47
// System includes
48
#include <stdint.h>     // intptr_t
49
50
//-------------------------------------------------------------------------
51
// Warnings
52
//-------------------------------------------------------------------------
53
54
// Visual Studio warnings
55
#ifdef _MSC_VER
56
#pragma warning (disable: 4127)     // condition expression is constant
57
#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
58
#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
59
#pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
60
#endif
61
#pragma warning (disable: 26451)    // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
62
#pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
63
#endif
64
65
// Clang/GCC warnings with -Weverything
66
#if defined(__clang__)
67
#if __has_warning("-Wunknown-warning-option")
68
#pragma clang diagnostic ignored "-Wunknown-warning-option"         // warning: unknown warning group 'xxx'                      // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
69
#endif
70
#pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
71
#pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
72
#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.
73
#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.
74
#pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
75
#pragma clang diagnostic ignored "-Wunused-macros"                  // warning: macro is not used                                // we define snprintf/vsnprintf on Windows so they are available, but not always used.
76
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
77
#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.
78
#pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
79
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
80
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
81
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
82
#pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
83
#elif defined(__GNUC__)
84
#pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
85
#pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
86
#pragma GCC diagnostic ignored "-Wclass-memaccess"                  // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
87
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
88
#endif
89
90
//-------------------------------------------------------------------------
91
// Data
92
//-------------------------------------------------------------------------
93
94
// Widgets
95
static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
96
static const float          DRAG_MOUSE_THRESHOLD_FACTOR = 0.50f;    // Multiplier for the default value of io.MouseDragThreshold to make DragFloat/DragInt react faster to mouse drags.
97
98
// Those MIN/MAX values are not define because we need to point to them
99
static const signed char    IM_S8_MIN  = -128;
100
static const signed char    IM_S8_MAX  = 127;
101
static const unsigned char  IM_U8_MIN  = 0;
102
static const unsigned char  IM_U8_MAX  = 0xFF;
103
static const signed short   IM_S16_MIN = -32768;
104
static const signed short   IM_S16_MAX = 32767;
105
static const unsigned short IM_U16_MIN = 0;
106
static const unsigned short IM_U16_MAX = 0xFFFF;
107
static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
108
static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
109
static const ImU32          IM_U32_MIN = 0;
110
static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
111
#ifdef LLONG_MIN
112
static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
113
static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
114
#else
115
static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
116
static const ImS64          IM_S64_MAX = 9223372036854775807LL;
117
#endif
118
static const ImU64          IM_U64_MIN = 0;
119
#ifdef ULLONG_MAX
120
static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
121
#else
122
static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
123
#endif
124
125
//-------------------------------------------------------------------------
126
// [SECTION] Forward Declarations
127
//-------------------------------------------------------------------------
128
129
// For InputTextEx()
130
static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
131
static int      InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
132
static ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
133
134
//-------------------------------------------------------------------------
135
// [SECTION] Widgets: Text, etc.
136
//-------------------------------------------------------------------------
137
// - TextEx() [Internal]
138
// - TextUnformatted()
139
// - Text()
140
// - TextV()
141
// - TextColored()
142
// - TextColoredV()
143
// - TextDisabled()
144
// - TextDisabledV()
145
// - TextWrapped()
146
// - TextWrappedV()
147
// - LabelText()
148
// - LabelTextV()
149
// - BulletText()
150
// - BulletTextV()
151
//-------------------------------------------------------------------------
152
153
void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
154
{
155
    ImGuiWindow* window = GetCurrentWindow();
156
    if (window->SkipItems)
157
        return;
158
    ImGuiContext& g = *GImGui;
159
160
    // Accept null ranges
161
    if (text == text_end)
162
        text = text_end = "";
163
164
    // Calculate length
165
    const char* text_begin = text;
166
    if (text_end == NULL)
167
        text_end = text + strlen(text); // FIXME-OPT
168
169
    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
170
    const float wrap_pos_x = window->DC.TextWrapPos;
171
    const bool wrap_enabled = (wrap_pos_x >= 0.0f);
172
    if (text_end - text <= 2000 || wrap_enabled)
173
    {
174
        // Common case
175
        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
176
        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
177
178
        ImRect bb(text_pos, text_pos + text_size);
179
        ItemSize(text_size, 0.0f);
180
        if (!ItemAdd(bb, 0))
181
            return;
182
183
        // Render (we don't hide text after ## in this end-user function)
184
        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
185
    }
186
    else
187
    {
188
        // Long text!
189
        // Perform manual coarse clipping to optimize for long multi-line text
190
        // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
191
        // - 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.
192
        // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
193
        const char* line = text;
194
        const float line_height = GetTextLineHeight();
195
        ImVec2 text_size(0, 0);
196
197
        // Lines to skip (can't skip when logging text)
198
        ImVec2 pos = text_pos;
199
        if (!g.LogEnabled)
200
        {
201
            int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
202
            if (lines_skippable > 0)
203
            {
204
                int lines_skipped = 0;
205
                while (line < text_end && lines_skipped < lines_skippable)
206
                {
207
                    const char* line_end = (const char*)memchr(line, '\n', text_end - line);
208
                    if (!line_end)
209
                        line_end = text_end;
210
                    if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
211
                        text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
212
                    line = line_end + 1;
213
                    lines_skipped++;
214
                }
215
                pos.y += lines_skipped * line_height;
216
            }
217
        }
218
219
        // Lines to render
220
        if (line < text_end)
221
        {
222
            ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
223
            while (line < text_end)
224
            {
225
                if (IsClippedEx(line_rect, 0))
226
                    break;
227
228
                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
229
                if (!line_end)
230
                    line_end = text_end;
231
                text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
232
                RenderText(pos, line, line_end, false);
233
                line = line_end + 1;
234
                line_rect.Min.y += line_height;
235
                line_rect.Max.y += line_height;
236
                pos.y += line_height;
237
            }
238
239
            // Count remaining lines
240
            int lines_skipped = 0;
241
            while (line < text_end)
242
            {
243
                const char* line_end = (const char*)memchr(line, '\n', text_end - line);
244
                if (!line_end)
245
                    line_end = text_end;
246
                if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
247
                    text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
248
                line = line_end + 1;
249
                lines_skipped++;
250
            }
251
            pos.y += lines_skipped * line_height;
252
        }
253
        text_size.y = (pos - text_pos).y;
254
255
        ImRect bb(text_pos, text_pos + text_size);
256
        ItemSize(text_size, 0.0f);
257
        ItemAdd(bb, 0);
258
    }
259
}
260
261
void ImGui::TextUnformatted(const char* text, const char* text_end)
262
{
263
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
264
}
265
266
void ImGui::Text(const char* fmt, ...)
267
{
268
    va_list args;
269
    va_start(args, fmt);
270
    TextV(fmt, args);
271
    va_end(args);
272
}
273
274
void ImGui::TextV(const char* fmt, va_list args)
275
{
276
    ImGuiWindow* window = GetCurrentWindow();
277
    if (window->SkipItems)
278
        return;
279
280
    const char* text, *text_end;
281
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
282
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
283
}
284
285
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
286
{
287
    va_list args;
288
    va_start(args, fmt);
289
    TextColoredV(col, fmt, args);
290
    va_end(args);
291
}
292
293
void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
294
{
295
    PushStyleColor(ImGuiCol_Text, col);
296
    TextV(fmt, args);
297
    PopStyleColor();
298
}
299
300
void ImGui::TextDisabled(const char* fmt, ...)
301
{
302
    va_list args;
303
    va_start(args, fmt);
304
    TextDisabledV(fmt, args);
305
    va_end(args);
306
}
307
308
void ImGui::TextDisabledV(const char* fmt, va_list args)
309
{
310
    ImGuiContext& g = *GImGui;
311
    PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
312
    TextV(fmt, args);
313
    PopStyleColor();
314
}
315
316
void ImGui::TextWrapped(const char* fmt, ...)
317
{
318
    va_list args;
319
    va_start(args, fmt);
320
    TextWrappedV(fmt, args);
321
    va_end(args);
322
}
323
324
void ImGui::TextWrappedV(const char* fmt, va_list args)
325
{
326
    ImGuiContext& g = *GImGui;
327
    const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
328
    if (need_backup)
329
        PushTextWrapPos(0.0f);
330
    TextV(fmt, args);
331
    if (need_backup)
332
        PopTextWrapPos();
333
}
334
335
void ImGui::LabelText(const char* label, const char* fmt, ...)
336
{
337
    va_list args;
338
    va_start(args, fmt);
339
    LabelTextV(label, fmt, args);
340
    va_end(args);
341
}
342
343
// Add a label+text combo aligned to other label+value widgets
344
void ImGui::LabelTextV(const char* label, 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
    const float w = CalcItemWidth();
353
354
    const char* value_text_begin, *value_text_end;
355
    ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
356
    const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
357
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
358
359
    const ImVec2 pos = window->DC.CursorPos;
360
    const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
361
    const ImRect total_bb(pos, pos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), ImMax(value_size.y, label_size.y) + style.FramePadding.y * 2));
362
    ItemSize(total_bb, style.FramePadding.y);
363
    if (!ItemAdd(total_bb, 0))
364
        return;
365
366
    // Render
367
    RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
368
    if (label_size.x > 0.0f)
369
        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
370
}
371
372
void ImGui::BulletText(const char* fmt, ...)
373
{
374
    va_list args;
375
    va_start(args, fmt);
376
    BulletTextV(fmt, args);
377
    va_end(args);
378
}
379
380
// Text with a little bullet aligned to the typical tree node.
381
void ImGui::BulletTextV(const char* fmt, va_list args)
382
{
383
    ImGuiWindow* window = GetCurrentWindow();
384
    if (window->SkipItems)
385
        return;
386
387
    ImGuiContext& g = *GImGui;
388
    const ImGuiStyle& style = g.Style;
389
390
    const char* text_begin, *text_end;
391
    ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
392
    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
393
    const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y);  // Empty text doesn't add padding
394
    ImVec2 pos = window->DC.CursorPos;
395
    pos.y += window->DC.CurrLineTextBaseOffset;
396
    ItemSize(total_size, 0.0f);
397
    const ImRect bb(pos, pos + total_size);
398
    if (!ItemAdd(bb, 0))
399
        return;
400
401
    // Render
402
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
403
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
404
    RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
405
}
406
407
//-------------------------------------------------------------------------
408
// [SECTION] Widgets: Main
409
//-------------------------------------------------------------------------
410
// - ButtonBehavior() [Internal]
411
// - Button()
412
// - SmallButton()
413
// - InvisibleButton()
414
// - ArrowButton()
415
// - CloseButton() [Internal]
416
// - CollapseButton() [Internal]
417
// - GetWindowScrollbarID() [Internal]
418
// - GetWindowScrollbarRect() [Internal]
419
// - Scrollbar() [Internal]
420
// - ScrollbarEx() [Internal]
421
// - Image()
422
// - ImageButton()
423
// - Checkbox()
424
// - CheckboxFlagsT() [Internal]
425
// - CheckboxFlags()
426
// - RadioButton()
427
// - ProgressBar()
428
// - Bullet()
429
// - Hyperlink()
430
//-------------------------------------------------------------------------
431
432
// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
433
// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
434
// this code is a little complex.
435
// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
436
// See the series of events below and the corresponding state reported by dear imgui:
437
//------------------------------------------------------------------------------------------------------------------------------------------------
438
// with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
439
//   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
440
//   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
441
//   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
442
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
443
//   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
444
//   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
445
//   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
446
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
447
//   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
448
//------------------------------------------------------------------------------------------------------------------------------------------------
449
// with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
450
//   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
451
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
452
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
453
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
454
//------------------------------------------------------------------------------------------------------------------------------------------------
455
// with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
456
//   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
457
//   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
458
//   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
459
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
460
//------------------------------------------------------------------------------------------------------------------------------------------------
461
// with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
462
//   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
463
//   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
464
//   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
465
//   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
466
//   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
467
//   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
468
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
469
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
470
//------------------------------------------------------------------------------------------------------------------------------------------------
471
// Note that some combinations are supported,
472
// - PressedOnDragDropHold can generally be associated with any flag.
473
// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
474
//------------------------------------------------------------------------------------------------------------------------------------------------
475
// The behavior of the return-value changes when ImGuiButtonFlags_Repeat is set:
476
//                                         Repeat+                  Repeat+           Repeat+             Repeat+
477
//                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
478
//-------------------------------------------------------------------------------------------------------------------------------------------------
479
//   Frame N+0 (mouse button is down)       -                        true              -                   true
480
//   ...                                    -                        -                 -                   -
481
//   Frame N + RepeatDelay                  true                     true              -                   true
482
//   ...                                    -                        -                 -                   -
483
//   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
484
//-------------------------------------------------------------------------------------------------------------------------------------------------
485
486
// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
487
//   And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
488
//   For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
489
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
490
//   One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior()
491
//   with same ID and different MouseButton (see #8030). You can fix it by:
492
//       (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
493
//    or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
494
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
495
{
496
    ImGuiContext& g = *GImGui;
497
    ImGuiWindow* window = GetCurrentWindow();
498
499
    // Default behavior inherited from item flags
500
    // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
501
    ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
502
    if (flags & ImGuiButtonFlags_AllowOverlap)
503
        item_flags |= ImGuiItemFlags_AllowOverlap;
504
505
    // Default only reacts to left mouse button
506
    if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
507
        flags |= ImGuiButtonFlags_MouseButtonLeft;
508
509
    // Default behavior requires click + release inside bounding box
510
    if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
511
        flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_;
512
513
    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
514
    const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
515
    if (flatten_hovered_children)
516
        g.HoveredWindow = window;
517
518
#ifdef IMGUI_ENABLE_TEST_ENGINE
519
    // Alternate registration spot, for when caller didn't use ItemAdd()
520
    if (g.LastItemData.ID != id)
521
        IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
522
#endif
523
524
    bool pressed = false;
525
    bool hovered = ItemHoverable(bb, id, item_flags);
526
527
    // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
528
    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
529
        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
530
        {
531
            hovered = true;
532
            SetHoveredID(id);
533
            if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
534
            {
535
                pressed = true;
536
                g.DragDropHoldJustPressedId = id;
537
                FocusWindow(window);
538
            }
539
        }
540
541
    if (flatten_hovered_children)
542
        g.HoveredWindow = backup_hovered_window;
543
544
    // Mouse handling
545
    const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
546
    if (hovered)
547
    {
548
        IM_ASSERT(id != 0); // Lazily check inside rare path.
549
550
        // Poll mouse buttons
551
        // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
552
        // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
553
        int mouse_button_clicked = -1;
554
        int mouse_button_released = -1;
555
        for (int button = 0; button < 3; button++)
556
            if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
557
            {
558
                if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
559
                if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
560
            }
561
562
        // Process initial action
563
        const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt);
564
        if (mods_ok)
565
        {
566
            if (mouse_button_clicked != -1 && g.ActiveId != id)
567
            {
568
                if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
569
                    SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
570
                if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
571
                {
572
                    SetActiveID(id, window);
573
                    g.ActiveIdMouseButton = mouse_button_clicked;
574
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
575
                    {
576
                        SetFocusID(id, window);
577
                        FocusWindow(window);
578
                    }
579
                    else
580
                    {
581
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
582
                    }
583
                }
584
                if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
585
                {
586
                    pressed = true;
587
                    if (flags & ImGuiButtonFlags_NoHoldingActiveId)
588
                        ClearActiveID();
589
                    else
590
                        SetActiveID(id, window); // Hold on ID
591
                    g.ActiveIdMouseButton = mouse_button_clicked;
592
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
593
                    {
594
                        SetFocusID(id, window);
595
                        FocusWindow(window);
596
                    }
597
                    else
598
                    {
599
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
600
                    }
601
                }
602
            }
603
            if (flags & ImGuiButtonFlags_PressedOnRelease)
604
            {
605
                if (mouse_button_released != -1)
606
                {
607
                    const bool has_repeated_at_least_once = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior
608
                    if (!has_repeated_at_least_once)
609
                        pressed = true;
610
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
611
                        SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why.
612
                    ClearActiveID();
613
                }
614
            }
615
616
            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
617
            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
618
            if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
619
                if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))
620
                    pressed = true;
621
        }
622
623
        if (pressed && g.IO.ConfigNavCursorVisibleAuto)
624
            g.NavCursorVisible = false;
625
    }
626
627
    // Keyboard/Gamepad navigation handling
628
    // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
629
    if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
630
        if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
631
            hovered = true;
632
    if (g.NavActivateDownId == id)
633
    {
634
        bool nav_activated_by_code = (g.NavActivateId == id);
635
        bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
636
        if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
637
        {
638
            // Avoid pressing multiple keys from triggering excessive amount of repeat events
639
            const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
640
            const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
641
            const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
642
            const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
643
            nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
644
        }
645
        if (nav_activated_by_code || nav_activated_by_inputs)
646
        {
647
            // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
648
            pressed = true;
649
            SetActiveID(id, window);
650
            g.ActiveIdSource = g.NavInputSource;
651
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
652
                SetFocusID(id, window);
653
            if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
654
                g.ActiveIdFromShortcut = true;
655
        }
656
    }
657
658
    // Process while held
659
    bool held = false;
660
    if (g.ActiveId == id)
661
    {
662
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
663
        {
664
            if (g.ActiveIdIsJustActivated)
665
                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
666
667
            const int mouse_button = g.ActiveIdMouseButton;
668
            if (mouse_button == -1)
669
            {
670
                // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
671
                ClearActiveID();
672
            }
673
            else if (IsMouseDown(mouse_button, test_owner_id))
674
            {
675
                held = true;
676
            }
677
            else
678
            {
679
                bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
680
                bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
681
                if ((release_in || release_anywhere) && !g.DragDropActive)
682
                {
683
                    // Report as pressed when releasing the mouse (this is the most common path)
684
                    bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
685
                    bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
686
                    bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
687
                    if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
688
                        pressed = true;
689
                }
690
                ClearActiveID();
691
            }
692
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto)
693
                g.NavCursorVisible = false;
694
        }
695
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
696
        {
697
            // When activated using Nav, we hold on the ActiveID until activation button is released
698
            if (g.NavActivateDownId == id)
699
                held = true; // hovered == true not true as we are already likely hovered on direct activation.
700
            else
701
                ClearActiveID();
702
        }
703
        if (pressed)
704
            g.ActiveIdHasBeenPressedBefore = true;
705
    }
706
707
    // Activation highlight (this may be a remote activation)
708
    if (g.NavHighlightActivatedId == id)
709
        hovered = true;
710
711
    if (out_hovered) *out_hovered = hovered;
712
    if (out_held) *out_held = held;
713
714
    return pressed;
715
}
716
717
bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
718
{
719
    ImGuiWindow* window = GetCurrentWindow();
720
    if (window->SkipItems)
721
        return false;
722
723
    ImGuiContext& g = *GImGui;
724
    const ImGuiStyle& style = g.Style;
725
    const ImGuiID id = window->GetID(label);
726
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
727
728
    ImVec2 pos = window->DC.CursorPos;
729
    if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrLineTextBaseOffset) // 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)
730
        pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
731
    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
732
733
    const ImRect bb(pos, pos + size);
734
    ItemSize(size, style.FramePadding.y);
735
    if (!ItemAdd(bb, id))
736
        return false;
737
738
    bool hovered, held;
739
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
740
741
    // Render
742
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
743
    RenderNavCursor(bb, id);
744
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
745
746
    if (g.LogEnabled)
747
        LogSetNextTextDecoration("[", "]");
748
    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
749
750
    // Automatically close popups
751
    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
752
    //    CloseCurrentPopup();
753
754
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
755
    return pressed;
756
}
757
758
bool ImGui::Button(const char* label, const ImVec2& size_arg)
759
{
760
    return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
761
}
762
763
// Small buttons fits within text without additional vertical spacing.
764
bool ImGui::SmallButton(const char* label)
765
{
766
    ImGuiContext& g = *GImGui;
767
    float backup_padding_y = g.Style.FramePadding.y;
768
    g.Style.FramePadding.y = 0.0f;
769
    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
770
    g.Style.FramePadding.y = backup_padding_y;
771
    return pressed;
772
}
773
774
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
775
// 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)
776
bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
777
{
778
    ImGuiContext& g = *GImGui;
779
    ImGuiWindow* window = GetCurrentWindow();
780
    if (window->SkipItems)
781
        return false;
782
783
    // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
784
    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
785
786
    const ImGuiID id = window->GetID(str_id);
787
    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
788
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
789
    ItemSize(size);
790
    if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))
791
        return false;
792
793
    bool hovered, held;
794
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
795
    RenderNavCursor(bb, id);
796
797
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
798
    return pressed;
799
}
800
801
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
802
{
803
    ImGuiContext& g = *GImGui;
804
    ImGuiWindow* window = GetCurrentWindow();
805
    if (window->SkipItems)
806
        return false;
807
808
    const ImGuiID id = window->GetID(str_id);
809
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
810
    const float default_size = GetFrameHeight();
811
    ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
812
    if (!ItemAdd(bb, id))
813
        return false;
814
815
    bool hovered, held;
816
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
817
818
    // Render
819
    const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
820
    const ImU32 text_col = GetColorU32(ImGuiCol_Text);
821
    RenderNavCursor(bb, id);
822
    RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
823
    RenderArrow(window->DrawList, bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), text_col, dir);
824
825
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
826
    return pressed;
827
}
828
829
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
830
{
831
    float sz = GetFrameHeight();
832
    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
833
}
834
835
// Button to close a window
836
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
837
{
838
    ImGuiContext& g = *GImGui;
839
    ImGuiWindow* window = g.CurrentWindow;
840
841
    // Tweak 1: Shrink hit-testing area if button covers an abnormally large proportion of the visible region. That's in order to facilitate moving the window away. (#3825)
842
    // This may better be applied as a general hit-rect reduction mechanism for all widgets to ensure the area to move window is always accessible?
843
    const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
844
    ImRect bb_interact = bb;
845
    const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
846
    if (area_to_visible_ratio < 1.5f)
847
        bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
848
849
    // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
850
    // (this isn't the common behavior of buttons, but it doesn't affect the user because navigation tends to keep items visible in scrolling layer).
851
    bool is_clipped = !ItemAdd(bb_interact, id);
852
853
    bool hovered, held;
854
    bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
855
    if (is_clipped)
856
        return pressed;
857
858
    // Render
859
    ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
860
    if (hovered)
861
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
862
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
863
    ImU32 cross_col = GetColorU32(ImGuiCol_Text);
864
    ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
865
    float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
866
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f);
867
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f);
868
869
    return pressed;
870
}
871
872
// The Collapse button also functions as a Dock Menu button.
873
bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
874
{
875
    ImGuiContext& g = *GImGui;
876
    ImGuiWindow* window = g.CurrentWindow;
877
878
    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
879
    bool is_clipped = !ItemAdd(bb, id);
880
    bool hovered, held;
881
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
882
    if (is_clipped)
883
        return pressed;
884
885
    // Render
886
    //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
887
    ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
888
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
889
    if (hovered || held)
890
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
891
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
892
893
    if (dock_node)
894
        RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
895
    else
896
        RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
897
898
    // Switch to moving the window after mouse is moved beyond the initial drag threshold
899
    if (IsItemActive() && IsMouseDragging(0))
900
        StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button
901
902
    return pressed;
903
}
904
905
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
906
{
907
    return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
908
}
909
910
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
911
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
912
{
913
    const ImRect outer_rect = window->Rect();
914
    const ImRect inner_rect = window->InnerRect;
915
    const float border_size = window->WindowBorderSize;
916
    const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
917
    IM_ASSERT(scrollbar_size > 0.0f);
918
    if (axis == ImGuiAxis_X)
919
        return ImRect(inner_rect.Min.x, ImMax(outer_rect.Min.y, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
920
    else
921
        return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
922
}
923
924
void ImGui::Scrollbar(ImGuiAxis axis)
925
{
926
    ImGuiContext& g = *GImGui;
927
    ImGuiWindow* window = g.CurrentWindow;
928
    const ImGuiID id = GetWindowScrollbarID(window, axis);
929
930
    // Calculate scrollbar bounding box
931
    ImRect bb = GetWindowScrollbarRect(window, axis);
932
    ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
933
    if (axis == ImGuiAxis_X)
934
    {
935
        rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
936
        if (!window->ScrollbarY)
937
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
938
    }
939
    else
940
    {
941
        if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
942
            rounding_corners |= ImDrawFlags_RoundCornersTopRight;
943
        if (!window->ScrollbarX)
944
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
945
    }
946
    float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
947
    float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
948
    ImS64 scroll = (ImS64)window->Scroll[axis];
949
    ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);
950
    window->Scroll[axis] = (float)scroll;
951
}
952
953
// Vertical/Horizontal scrollbar
954
// The entire piece of code below is rather confusing because:
955
// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
956
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
957
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
958
// Still, the code should probably be made simpler..
959
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags flags)
960
{
961
    ImGuiContext& g = *GImGui;
962
    ImGuiWindow* window = g.CurrentWindow;
963
    if (window->SkipItems)
964
        return false;
965
966
    const float bb_frame_width = bb_frame.GetWidth();
967
    const float bb_frame_height = bb_frame.GetHeight();
968
    if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
969
        return false;
970
971
    // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the window resize grab)
972
    float alpha = 1.0f;
973
    if ((axis == ImGuiAxis_Y) && bb_frame_height < g.FontSize + g.Style.FramePadding.y * 2.0f)
974
        alpha = ImSaturate((bb_frame_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f));
975
    if (alpha <= 0.0f)
976
        return false;
977
978
    const ImGuiStyle& style = g.Style;
979
    const bool allow_interaction = (alpha >= 1.0f);
980
981
    ImRect bb = bb_frame;
982
    bb.Expand(ImVec2(-ImClamp(IM_TRUNC((bb_frame_width - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp(IM_TRUNC((bb_frame_height - 2.0f) * 0.5f), 0.0f, 3.0f)));
983
984
    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
985
    const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
986
987
    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
988
    // But we maintain a minimum size in pixel to allow for the user to still aim inside.
989
    IM_ASSERT(ImMax(size_contents_v, size_visible_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
990
    const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
991
    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), style.GrabMinSize, scrollbar_size_v);
992
    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
993
994
    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
995
    bool held = false;
996
    bool hovered = false;
997
    ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
998
    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
999
1000
    const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);
1001
    float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1002
    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
1003
    if (held && allow_interaction && grab_h_norm < 1.0f)
1004
    {
1005
        const float scrollbar_pos_v = bb.Min[axis];
1006
        const float mouse_pos_v = g.IO.MousePos[axis];
1007
1008
        // Click position in scrollbar normalized space (0.0f->1.0f)
1009
        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
1010
1011
        const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0;
1012
        if (g.ActiveIdIsJustActivated)
1013
        {
1014
            // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab
1015
            const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0);
1016
            g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir;
1017
            g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f;
1018
        }
1019
1020
        // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
1021
        // It is ok to modify Scroll here because we are being called in Begin() after the calculation of ContentSize and before setting up our starting position
1022
        if (g.ScrollbarSeekMode == 0)
1023
        {
1024
            // Absolute seeking
1025
            const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
1026
            *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
1027
        }
1028
        else
1029
        {
1030
            // Page by page
1031
            if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)
1032
            {
1033
                float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;
1034
                *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max);
1035
            }
1036
        }
1037
1038
        // Update values for rendering
1039
        scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1040
        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
1041
1042
        // Update distance to grab now that we have seek'ed and saturated
1043
        //if (seek_absolute)
1044
        //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
1045
    }
1046
1047
    // Render
1048
    const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
1049
    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
1050
    window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, flags);
1051
    ImRect grab_rect;
1052
    if (axis == ImGuiAxis_X)
1053
        grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, bb.Max.y);
1054
    else
1055
        grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels);
1056
    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
1057
1058
    return held;
1059
}
1060
1061
// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
1062
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
1063
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
1064
{
1065
    ImGuiWindow* window = GetCurrentWindow();
1066
    if (window->SkipItems)
1067
        return;
1068
1069
    const float border_size = (border_col.w > 0.0f) ? 1.0f : 0.0f;
1070
    const ImVec2 padding(border_size, border_size);
1071
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1072
    ItemSize(bb);
1073
    if (!ItemAdd(bb, 0))
1074
        return;
1075
1076
    // Render
1077
    if (border_size > 0.0f)
1078
        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f, ImDrawFlags_None, border_size);
1079
    window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1080
}
1081
1082
// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
1083
// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
1084
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
1085
{
1086
    ImGuiContext& g = *GImGui;
1087
    ImGuiWindow* window = GetCurrentWindow();
1088
    if (window->SkipItems)
1089
        return false;
1090
1091
    const ImVec2 padding = g.Style.FramePadding;
1092
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1093
    ItemSize(bb);
1094
    if (!ItemAdd(bb, id))
1095
        return false;
1096
1097
    bool hovered, held;
1098
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1099
1100
    // Render
1101
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1102
    RenderNavCursor(bb, id);
1103
    RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
1104
    if (bg_col.w > 0.0f)
1105
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1106
    window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1107
1108
    return pressed;
1109
}
1110
1111
// Note that ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
1112
bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1113
{
1114
    ImGuiContext& g = *GImGui;
1115
    ImGuiWindow* window = g.CurrentWindow;
1116
    if (window->SkipItems)
1117
        return false;
1118
1119
    return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col);
1120
}
1121
1122
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1123
// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
1124
// - old ImageButton() used ImTextureId as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)
1125
// - new ImageButton() requires an explicit 'const char* str_id'
1126
// - old ImageButton() had frame_padding' override argument.
1127
// - new ImageButton() always use style.FramePadding.
1128
/*
1129
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)
1130
{
1131
    // Default to using texture ID as ID. User can still push string/integer prefixes.
1132
    PushID((ImTextureID)(intptr_t)user_texture_id);
1133
    if (frame_padding >= 0)
1134
        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
1135
    bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col);
1136
    if (frame_padding >= 0)
1137
        PopStyleVar();
1138
    PopID();
1139
    return ret;
1140
}
1141
*/
1142
#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1143
1144
bool ImGui::Checkbox(const char* label, bool* v)
1145
{
1146
    ImGuiWindow* window = GetCurrentWindow();
1147
    if (window->SkipItems)
1148
        return false;
1149
1150
    ImGuiContext& g = *GImGui;
1151
    const ImGuiStyle& style = g.Style;
1152
    const ImGuiID id = window->GetID(label);
1153
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1154
1155
    const float square_sz = GetFrameHeight();
1156
    const ImVec2 pos = window->DC.CursorPos;
1157
    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));
1158
    ItemSize(total_bb, style.FramePadding.y);
1159
    const bool is_visible = ItemAdd(total_bb, id);
1160
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1161
    if (!is_visible)
1162
        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support
1163
        {
1164
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1165
            return false;
1166
        }
1167
1168
    // Range-Selection/Multi-selection support (header)
1169
    bool checked = *v;
1170
    if (is_multi_select)
1171
        MultiSelectItemHeader(id, &checked, NULL);
1172
1173
    bool hovered, held;
1174
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1175
1176
    // Range-Selection/Multi-selection support (footer)
1177
    if (is_multi_select)
1178
        MultiSelectItemFooter(id, &checked, &pressed);
1179
    else if (pressed)
1180
        checked = !checked;
1181
1182
    if (*v != checked)
1183
    {
1184
        *v = checked;
1185
        pressed = true; // return value
1186
        MarkItemEdited(id);
1187
    }
1188
1189
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1190
    const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
1191
    if (is_visible)
1192
    {
1193
        RenderNavCursor(total_bb, id);
1194
        RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
1195
        ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1196
        if (mixed_value)
1197
        {
1198
            // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1199
            // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1200
            ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
1201
            window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1202
        }
1203
        else if (*v)
1204
        {
1205
            const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1206
            RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1207
        }
1208
    }
1209
    const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1210
    if (g.LogEnabled)
1211
        LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1212
    if (is_visible && label_size.x > 0.0f)
1213
        RenderText(label_pos, label);
1214
1215
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1216
    return pressed;
1217
}
1218
1219
template<typename T>
1220
bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1221
{
1222
    bool all_on = (*flags & flags_value) == flags_value;
1223
    bool any_on = (*flags & flags_value) != 0;
1224
    bool pressed;
1225
    if (!all_on && any_on)
1226
    {
1227
        ImGuiContext& g = *GImGui;
1228
        g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1229
        pressed = Checkbox(label, &all_on);
1230
    }
1231
    else
1232
    {
1233
        pressed = Checkbox(label, &all_on);
1234
1235
    }
1236
    if (pressed)
1237
    {
1238
        if (all_on)
1239
            *flags |= flags_value;
1240
        else
1241
            *flags &= ~flags_value;
1242
    }
1243
    return pressed;
1244
}
1245
1246
bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1247
{
1248
    return CheckboxFlagsT(label, flags, flags_value);
1249
}
1250
1251
bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1252
{
1253
    return CheckboxFlagsT(label, flags, flags_value);
1254
}
1255
1256
bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1257
{
1258
    return CheckboxFlagsT(label, flags, flags_value);
1259
}
1260
1261
bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1262
{
1263
    return CheckboxFlagsT(label, flags, flags_value);
1264
}
1265
1266
bool ImGui::RadioButton(const char* label, bool active)
1267
{
1268
    ImGuiWindow* window = GetCurrentWindow();
1269
    if (window->SkipItems)
1270
        return false;
1271
1272
    ImGuiContext& g = *GImGui;
1273
    const ImGuiStyle& style = g.Style;
1274
    const ImGuiID id = window->GetID(label);
1275
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1276
1277
    const float square_sz = GetFrameHeight();
1278
    const ImVec2 pos = window->DC.CursorPos;
1279
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1280
    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));
1281
    ItemSize(total_bb, style.FramePadding.y);
1282
    if (!ItemAdd(total_bb, id))
1283
        return false;
1284
1285
    ImVec2 center = check_bb.GetCenter();
1286
    center.x = IM_ROUND(center.x);
1287
    center.y = IM_ROUND(center.y);
1288
    const float radius = (square_sz - 1.0f) * 0.5f;
1289
1290
    bool hovered, held;
1291
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1292
    if (pressed)
1293
        MarkItemEdited(id);
1294
1295
    RenderNavCursor(total_bb, id);
1296
    const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1297
    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
1298
    if (active)
1299
    {
1300
        const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1301
        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
1302
    }
1303
1304
    if (style.FrameBorderSize > 0.0f)
1305
    {
1306
        window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
1307
        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
1308
    }
1309
1310
    ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1311
    if (g.LogEnabled)
1312
        LogRenderedText(&label_pos, active ? "(x)" : "( )");
1313
    if (label_size.x > 0.0f)
1314
        RenderText(label_pos, label);
1315
1316
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1317
    return pressed;
1318
}
1319
1320
// FIXME: This would work nicely if it was a public template, e.g. 'template<T> RadioButton(const char* label, T* v, T v_button)', but I'm not sure how we would expose it..
1321
bool ImGui::RadioButton(const char* label, int* v, int v_button)
1322
{
1323
    const bool pressed = RadioButton(label, *v == v_button);
1324
    if (pressed)
1325
        *v = v_button;
1326
    return pressed;
1327
}
1328
1329
// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1330
void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1331
{
1332
    ImGuiWindow* window = GetCurrentWindow();
1333
    if (window->SkipItems)
1334
        return;
1335
1336
    ImGuiContext& g = *GImGui;
1337
    const ImGuiStyle& style = g.Style;
1338
1339
    ImVec2 pos = window->DC.CursorPos;
1340
    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1341
    ImRect bb(pos, pos + size);
1342
    ItemSize(size, style.FramePadding.y);
1343
    if (!ItemAdd(bb, 0))
1344
        return;
1345
1346
    // Fraction < 0.0f will display an indeterminate progress bar animation
1347
    // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works.
1348
    const bool is_indeterminate = (fraction < 0.0f);
1349
    if (!is_indeterminate)
1350
        fraction = ImSaturate(fraction);
1351
1352
    // Out of courtesy we accept a NaN fraction without crashing
1353
    float fill_n0 = 0.0f;
1354
    float fill_n1 = (fraction == fraction) ? fraction : 0.0f;
1355
1356
    if (is_indeterminate)
1357
    {
1358
        const float fill_width_n = 0.2f;
1359
        fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;
1360
        fill_n1 = ImSaturate(fill_n0 + fill_width_n);
1361
        fill_n0 = ImSaturate(fill_n0);
1362
    }
1363
1364
    // Render
1365
    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1366
    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1367
    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);
1368
1369
    // Default displaying the fraction as percentage string, but user can override it
1370
    // Don't display text for indeterminate bars by default
1371
    char overlay_buf[32];
1372
    if (!is_indeterminate || overlay != NULL)
1373
    {
1374
        if (!overlay)
1375
        {
1376
            ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1377
            overlay = overlay_buf;
1378
        }
1379
1380
        ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1381
        if (overlay_size.x > 0.0f)
1382
        {
1383
            float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x;
1384
            RenderTextClipped(ImVec2(ImClamp(text_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);
1385
        }
1386
    }
1387
}
1388
1389
void ImGui::Bullet()
1390
{
1391
    ImGuiWindow* window = GetCurrentWindow();
1392
    if (window->SkipItems)
1393
        return;
1394
1395
    ImGuiContext& g = *GImGui;
1396
    const ImGuiStyle& style = g.Style;
1397
    const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
1398
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1399
    ItemSize(bb);
1400
    if (!ItemAdd(bb, 0))
1401
    {
1402
        SameLine(0, style.FramePadding.x * 2);
1403
        return;
1404
    }
1405
1406
    // Render and stay on same line
1407
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1408
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1409
    SameLine(0, style.FramePadding.x * 2.0f);
1410
}
1411
1412
// This is provided as a convenience for being an often requested feature.
1413
// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.
1414
// Because of this we currently don't provide many styling options for this widget
1415
// (e.g. hovered/active colors are automatically inferred from a single color).
1416
bool ImGui::TextLink(const char* label)
1417
{
1418
    ImGuiWindow* window = GetCurrentWindow();
1419
    if (window->SkipItems)
1420
        return false;
1421
1422
    ImGuiContext& g = *GImGui;
1423
    const ImGuiID id = window->GetID(label);
1424
    const char* label_end = FindRenderedTextEnd(label);
1425
1426
    ImVec2 pos = window->DC.CursorPos;
1427
    ImVec2 size = CalcTextSize(label, label_end, true);
1428
    ImRect bb(pos, pos + size);
1429
    ItemSize(size, 0.0f);
1430
    if (!ItemAdd(bb, id))
1431
        return false;
1432
1433
    bool hovered, held;
1434
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1435
    RenderNavCursor(bb, id);
1436
1437
    if (hovered)
1438
        SetMouseCursor(ImGuiMouseCursor_Hand);
1439
1440
    ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];
1441
    ImVec4 line_colf = text_colf;
1442
    {
1443
        // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets,
1444
        // as we are currently experimenting/planning a different styling system.
1445
        float h, s, v;
1446
        ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);
1447
        if (held || hovered)
1448
        {
1449
            v = ImSaturate(v + (held ? 0.4f : 0.3f));
1450
            h = ImFmod(h + 0.02f, 1.0f);
1451
        }
1452
        ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);
1453
        v = ImSaturate(v - 0.20f);
1454
        ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
1455
    }
1456
1457
    float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f);
1458
    window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode.
1459
1460
    PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
1461
    RenderText(bb.Min, label, label_end);
1462
    PopStyleColor();
1463
1464
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1465
    return pressed;
1466
}
1467
1468
void ImGui::TextLinkOpenURL(const char* label, const char* url)
1469
{
1470
    ImGuiContext& g = *GImGui;
1471
    if (url == NULL)
1472
        url = label;
1473
    if (TextLink(label))
1474
        if (g.PlatformIO.Platform_OpenInShellFn != NULL)
1475
            g.PlatformIO.Platform_OpenInShellFn(&g, url);
1476
    SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label
1477
    if (BeginPopupContextItem())
1478
    {
1479
        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))
1480
            SetClipboardText(url);
1481
        EndPopup();
1482
    }
1483
}
1484
1485
//-------------------------------------------------------------------------
1486
// [SECTION] Widgets: Low-level Layout helpers
1487
//-------------------------------------------------------------------------
1488
// - Spacing()
1489
// - Dummy()
1490
// - NewLine()
1491
// - AlignTextToFramePadding()
1492
// - SeparatorEx() [Internal]
1493
// - Separator()
1494
// - SplitterBehavior() [Internal]
1495
// - ShrinkWidths() [Internal]
1496
//-------------------------------------------------------------------------
1497
1498
void ImGui::Spacing()
1499
{
1500
    ImGuiWindow* window = GetCurrentWindow();
1501
    if (window->SkipItems)
1502
        return;
1503
    ItemSize(ImVec2(0, 0));
1504
}
1505
1506
void ImGui::Dummy(const ImVec2& size)
1507
{
1508
    ImGuiWindow* window = GetCurrentWindow();
1509
    if (window->SkipItems)
1510
        return;
1511
1512
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1513
    ItemSize(size);
1514
    ItemAdd(bb, 0);
1515
}
1516
1517
void ImGui::NewLine()
1518
{
1519
    ImGuiWindow* window = GetCurrentWindow();
1520
    if (window->SkipItems)
1521
        return;
1522
1523
    ImGuiContext& g = *GImGui;
1524
    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1525
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1526
    window->DC.IsSameLine = false;
1527
    if (window->DC.CurrLineSize.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.
1528
        ItemSize(ImVec2(0, 0));
1529
    else
1530
        ItemSize(ImVec2(0.0f, g.FontSize));
1531
    window->DC.LayoutType = backup_layout_type;
1532
}
1533
1534
void ImGui::AlignTextToFramePadding()
1535
{
1536
    ImGuiWindow* window = GetCurrentWindow();
1537
    if (window->SkipItems)
1538
        return;
1539
1540
    ImGuiContext& g = *GImGui;
1541
    window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1542
    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1543
}
1544
1545
// Horizontal/vertical separating line
1546
// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1547
// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1548
void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1549
{
1550
    ImGuiWindow* window = GetCurrentWindow();
1551
    if (window->SkipItems)
1552
        return;
1553
1554
    ImGuiContext& g = *GImGui;
1555
    IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
1556
    IM_ASSERT(thickness > 0.0f);
1557
1558
    if (flags & ImGuiSeparatorFlags_Vertical)
1559
    {
1560
        // Vertical separator, for menu bars (use current line height).
1561
        float y1 = window->DC.CursorPos.y;
1562
        float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1563
        const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1564
        ItemSize(ImVec2(thickness, 0.0f));
1565
        if (!ItemAdd(bb, 0))
1566
            return;
1567
1568
        // Draw
1569
        window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1570
        if (g.LogEnabled)
1571
            LogText(" |");
1572
    }
1573
    else if (flags & ImGuiSeparatorFlags_Horizontal)
1574
    {
1575
        // Horizontal Separator
1576
        float x1 = window->DC.CursorPos.x;
1577
        float x2 = window->WorkRect.Max.x;
1578
1579
        // Preserve legacy behavior inside Columns()
1580
        // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1581
        // We currently don't need to provide the same feature for tables because tables naturally have border features.
1582
        ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1583
        if (columns)
1584
        {
1585
            x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1586
            x2 = window->Pos.x + window->Size.x;
1587
            PushColumnsBackground();
1588
        }
1589
1590
        // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1591
        // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1592
        const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.
1593
        const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1594
        ItemSize(ImVec2(0.0f, thickness_for_layout));
1595
1596
        if (ItemAdd(bb, 0))
1597
        {
1598
            // Draw
1599
            window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1600
            if (g.LogEnabled)
1601
                LogRenderedText(&bb.Min, "--------------------------------\n");
1602
1603
        }
1604
        if (columns)
1605
        {
1606
            PopColumnsBackground();
1607
            columns->LineMinY = window->DC.CursorPos.y;
1608
        }
1609
    }
1610
}
1611
1612
void ImGui::Separator()
1613
{
1614
    ImGuiContext& g = *GImGui;
1615
    ImGuiWindow* window = g.CurrentWindow;
1616
    if (window->SkipItems)
1617
        return;
1618
1619
    // Those flags should eventually be configurable by the user
1620
    // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
1621
    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1622
1623
    // Only applies to legacy Columns() api as they relied on Separator() a lot.
1624
    if (window->DC.CurrentColumns)
1625
        flags |= ImGuiSeparatorFlags_SpanAllColumns;
1626
1627
    SeparatorEx(flags, 1.0f);
1628
}
1629
1630
void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1631
{
1632
    ImGuiContext& g = *GImGui;
1633
    ImGuiWindow* window = g.CurrentWindow;
1634
    ImGuiStyle& style = g.Style;
1635
1636
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1637
    const ImVec2 pos = window->DC.CursorPos;
1638
    const ImVec2 padding = style.SeparatorTextPadding;
1639
1640
    const float separator_thickness = style.SeparatorTextBorderSize;
1641
    const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
1642
    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1643
    const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
1644
    ItemSize(min_size, text_baseline_y);
1645
    if (!ItemAdd(bb, id))
1646
        return;
1647
1648
    const float sep1_x1 = pos.x;
1649
    const float sep2_x2 = bb.Max.x;
1650
    const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1651
1652
    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
1653
    const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN
1654
1655
    // This allows using SameLine() to position something in the 'extra_w'
1656
    window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1657
1658
    const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
1659
    if (label_size.x > 0.0f)
1660
    {
1661
        const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1662
        const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1663
        if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1664
            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);
1665
        if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1666
            window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1667
        if (g.LogEnabled)
1668
            LogSetNextTextDecoration("---", NULL);
1669
        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size);
1670
    }
1671
    else
1672
    {
1673
        if (g.LogEnabled)
1674
            LogText("---");
1675
        if (separator_thickness > 0.0f)
1676
            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1677
    }
1678
}
1679
1680
void ImGui::SeparatorText(const char* label)
1681
{
1682
    ImGuiWindow* window = GetCurrentWindow();
1683
    if (window->SkipItems)
1684
        return;
1685
1686
    // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1687
    // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1688
    // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1689
    // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1690
    // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1691
    // and then we can turn this into a format function.
1692
    SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
1693
}
1694
1695
// 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.
1696
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, ImU32 bg_col)
1697
{
1698
    ImGuiContext& g = *GImGui;
1699
    ImGuiWindow* window = g.CurrentWindow;
1700
1701
    if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
1702
        return false;
1703
1704
    // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1705
    // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1706
    // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1707
    ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1708
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1709
    button_flags |= ImGuiButtonFlags_AllowOverlap;
1710
#endif
1711
1712
    bool hovered, held;
1713
    ImRect bb_interact = bb;
1714
    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1715
    ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
1716
    if (hovered)
1717
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1718
1719
    if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1720
        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1721
1722
    ImRect bb_render = bb;
1723
    if (held)
1724
    {
1725
        float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1726
1727
        // Minimum pane size
1728
        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1729
        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1730
        if (mouse_delta < -size_1_maximum_delta)
1731
            mouse_delta = -size_1_maximum_delta;
1732
        if (mouse_delta > size_2_maximum_delta)
1733
            mouse_delta = size_2_maximum_delta;
1734
1735
        // Apply resize
1736
        if (mouse_delta != 0.0f)
1737
        {
1738
            *size1 = ImMax(*size1 + mouse_delta, min_size1);
1739
            *size2 = ImMax(*size2 - mouse_delta, min_size2);
1740
            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1741
            MarkItemEdited(id);
1742
        }
1743
    }
1744
1745
    // Render at new position
1746
    if (bg_col & IM_COL32_A_MASK)
1747
        window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
1748
    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1749
    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1750
1751
    return held;
1752
}
1753
1754
static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1755
{
1756
    const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1757
    const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1758
    if (int d = (int)(b->Width - a->Width))
1759
        return d;
1760
    return (b->Index - a->Index);
1761
}
1762
1763
// Shrink excess width from a set of item, by removing width from the larger items first.
1764
// Set items Width to -1.0f to disable shrinking this item.
1765
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess)
1766
{
1767
    if (count == 1)
1768
    {
1769
        if (items[0].Width >= 0.0f)
1770
            items[0].Width = ImMax(items[0].Width - width_excess, 1.0f);
1771
        return;
1772
    }
1773
    ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer);
1774
    int count_same_width = 1;
1775
    while (width_excess > 0.0f && count_same_width < count)
1776
    {
1777
        while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1778
            count_same_width++;
1779
        float max_width_to_remove_per_item = (count_same_width < count && items[count_same_width].Width >= 0.0f) ? (items[0].Width - items[count_same_width].Width) : (items[0].Width - 1.0f);
1780
        if (max_width_to_remove_per_item <= 0.0f)
1781
            break;
1782
        float width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1783
        for (int item_n = 0; item_n < count_same_width; item_n++)
1784
            items[item_n].Width -= width_to_remove_per_item;
1785
        width_excess -= width_to_remove_per_item * count_same_width;
1786
    }
1787
1788
    // Round width and redistribute remainder
1789
    // Ensure that e.g. the right-most tab of a shrunk tab-bar always reaches exactly at the same distance from the right-most edge of the tab bar separator.
1790
    width_excess = 0.0f;
1791
    for (int n = 0; n < count; n++)
1792
    {
1793
        float width_rounded = ImTrunc(items[n].Width);
1794
        width_excess += items[n].Width - width_rounded;
1795
        items[n].Width = width_rounded;
1796
    }
1797
    while (width_excess > 0.0f)
1798
        for (int n = 0; n < count && width_excess > 0.0f; n++)
1799
        {
1800
            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
1801
            items[n].Width += width_to_add;
1802
            width_excess -= width_to_add;
1803
        }
1804
}
1805
1806
//-------------------------------------------------------------------------
1807
// [SECTION] Widgets: ComboBox
1808
//-------------------------------------------------------------------------
1809
// - CalcMaxPopupHeightFromItemCount() [Internal]
1810
// - BeginCombo()
1811
// - BeginComboPopup() [Internal]
1812
// - EndCombo()
1813
// - BeginComboPreview() [Internal]
1814
// - EndComboPreview() [Internal]
1815
// - Combo()
1816
//-------------------------------------------------------------------------
1817
1818
static float CalcMaxPopupHeightFromItemCount(int items_count)
1819
{
1820
    ImGuiContext& g = *GImGui;
1821
    if (items_count <= 0)
1822
        return FLT_MAX;
1823
    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1824
}
1825
1826
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1827
{
1828
    ImGuiContext& g = *GImGui;
1829
    ImGuiWindow* window = GetCurrentWindow();
1830
1831
    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.Flags;
1832
    g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1833
    if (window->SkipItems)
1834
        return false;
1835
1836
    const ImGuiStyle& style = g.Style;
1837
    const ImGuiID id = window->GetID(label);
1838
    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1839
    if (flags & ImGuiComboFlags_WidthFitPreview)
1840
        IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1841
1842
    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1843
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1844
    const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
1845
    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1846
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1847
    const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1848
    ItemSize(total_bb, style.FramePadding.y);
1849
    if (!ItemAdd(total_bb, id, &bb))
1850
        return false;
1851
1852
    // Open on click
1853
    bool hovered, held;
1854
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1855
    const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1856
    bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1857
    if (pressed && !popup_open)
1858
    {
1859
        OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1860
        popup_open = true;
1861
    }
1862
1863
    // Render shape
1864
    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1865
    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1866
    RenderNavCursor(bb, id);
1867
    if (!(flags & ImGuiComboFlags_NoPreview))
1868
        window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1869
    if (!(flags & ImGuiComboFlags_NoArrowButton))
1870
    {
1871
        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1872
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
1873
        window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1874
        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1875
            RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1876
    }
1877
    RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1878
1879
    // Custom preview
1880
    if (flags & ImGuiComboFlags_CustomPreview)
1881
    {
1882
        g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1883
        IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1884
        preview_value = NULL;
1885
    }
1886
1887
    // Render preview and label
1888
    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1889
    {
1890
        if (g.LogEnabled)
1891
            LogSetNextTextDecoration("{", "}");
1892
        RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1893
    }
1894
    if (label_size.x > 0)
1895
        RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1896
1897
    if (!popup_open)
1898
        return false;
1899
1900
    g.NextWindowData.Flags = backup_next_window_data_flags;
1901
    return BeginComboPopup(popup_id, bb, flags);
1902
}
1903
1904
bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1905
{
1906
    ImGuiContext& g = *GImGui;
1907
    if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1908
    {
1909
        g.NextWindowData.ClearFlags();
1910
        return false;
1911
    }
1912
1913
    // Set popup size
1914
    float w = bb.GetWidth();
1915
    if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1916
    {
1917
        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1918
    }
1919
    else
1920
    {
1921
        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1922
            flags |= ImGuiComboFlags_HeightRegular;
1923
        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1924
        int popup_max_height_in_items = -1;
1925
        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
1926
        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
1927
        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
1928
        ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
1929
        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
1930
            constraint_min.x = w;
1931
        if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1932
            constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
1933
        SetNextWindowSizeConstraints(constraint_min, constraint_max);
1934
    }
1935
1936
    // This is essentially a specialized version of BeginPopupEx()
1937
    char name[16];
1938
    ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
1939
1940
    // Set position given a custom constraint (peak into expected window size so we can position it)
1941
    // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1942
    // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1943
    if (ImGuiWindow* popup_window = FindWindowByName(name))
1944
        if (popup_window->WasActive)
1945
        {
1946
            // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1947
            ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1948
            popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1949
            ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1950
            ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1951
            SetNextWindowPos(pos);
1952
        }
1953
1954
    // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1955
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1956
    PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text
1957
    bool ret = Begin(name, NULL, window_flags);
1958
    PopStyleVar();
1959
    if (!ret)
1960
    {
1961
        EndPopup();
1962
        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
1963
        return false;
1964
    }
1965
    g.BeginComboDepth++;
1966
    return true;
1967
}
1968
1969
void ImGui::EndCombo()
1970
{
1971
    ImGuiContext& g = *GImGui;
1972
    EndPopup();
1973
    g.BeginComboDepth--;
1974
}
1975
1976
// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1977
// (Experimental, see GitHub issues: #1658, #4168)
1978
bool ImGui::BeginComboPreview()
1979
{
1980
    ImGuiContext& g = *GImGui;
1981
    ImGuiWindow* window = g.CurrentWindow;
1982
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1983
1984
    if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1985
        return false;
1986
    IM_ASSERT(g.LastItemData.Rect.Min.x == preview_data->PreviewRect.Min.x && g.LastItemData.Rect.Min.y == preview_data->PreviewRect.Min.y); // Didn't call after BeginCombo/EndCombo block or forgot to pass ImGuiComboFlags_CustomPreview flag?
1987
    if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
1988
        return false;
1989
1990
    // FIXME: This could be contained in a PushWorkRect() api
1991
    preview_data->BackupCursorPos = window->DC.CursorPos;
1992
    preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1993
    preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1994
    preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1995
    preview_data->BackupLayout = window->DC.LayoutType;
1996
    window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1997
    window->DC.CursorMaxPos = window->DC.CursorPos;
1998
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1999
    window->DC.IsSameLine = false;
2000
    PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
2001
2002
    return true;
2003
}
2004
2005
void ImGui::EndComboPreview()
2006
{
2007
    ImGuiContext& g = *GImGui;
2008
    ImGuiWindow* window = g.CurrentWindow;
2009
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
2010
2011
    // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
2012
    ImDrawList* draw_list = window->DrawList;
2013
    if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
2014
        if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
2015
        {
2016
            draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
2017
            draw_list->_TryMergeDrawCmds();
2018
        }
2019
    PopClipRect();
2020
    window->DC.CursorPos = preview_data->BackupCursorPos;
2021
    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
2022
    window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
2023
    window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
2024
    window->DC.LayoutType = preview_data->BackupLayout;
2025
    window->DC.IsSameLine = false;
2026
    preview_data->PreviewRect = ImRect();
2027
}
2028
2029
// Getter for the old Combo() API: const char*[]
2030
static const char* Items_ArrayGetter(void* data, int idx)
2031
{
2032
    const char* const* items = (const char* const*)data;
2033
    return items[idx];
2034
}
2035
2036
// Getter for the old Combo() API: "item1\0item2\0item3\0"
2037
static const char* Items_SingleStringGetter(void* data, int idx)
2038
{
2039
    const char* items_separated_by_zeros = (const char*)data;
2040
    int items_count = 0;
2041
    const char* p = items_separated_by_zeros;
2042
    while (*p)
2043
    {
2044
        if (idx == items_count)
2045
            break;
2046
        p += strlen(p) + 1;
2047
        items_count++;
2048
    }
2049
    return *p ? p : NULL;
2050
}
2051
2052
// Old API, prefer using BeginCombo() nowadays if you can.
2053
bool ImGui::Combo(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int popup_max_height_in_items)
2054
{
2055
    ImGuiContext& g = *GImGui;
2056
2057
    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
2058
    const char* preview_value = NULL;
2059
    if (*current_item >= 0 && *current_item < items_count)
2060
        preview_value = getter(user_data, *current_item);
2061
2062
    // 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.
2063
    if (popup_max_height_in_items != -1 && !(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint))
2064
        SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
2065
2066
    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
2067
        return false;
2068
2069
    // Display items
2070
    bool value_changed = false;
2071
    ImGuiListClipper clipper;
2072
    clipper.Begin(items_count);
2073
    clipper.IncludeItemByIndex(*current_item);
2074
    while (clipper.Step())
2075
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
2076
        {
2077
            const char* item_text = getter(user_data, i);
2078
            if (item_text == NULL)
2079
                item_text = "*Unknown item*";
2080
2081
            PushID(i);
2082
            const bool item_selected = (i == *current_item);
2083
            if (Selectable(item_text, item_selected) && *current_item != i)
2084
            {
2085
                value_changed = true;
2086
                *current_item = i;
2087
            }
2088
            if (item_selected)
2089
                SetItemDefaultFocus();
2090
            PopID();
2091
        }
2092
2093
    EndCombo();
2094
    if (value_changed)
2095
        MarkItemEdited(g.LastItemData.ID);
2096
2097
    return value_changed;
2098
}
2099
2100
// Combo box helper allowing to pass an array of strings.
2101
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
2102
{
2103
    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
2104
    return value_changed;
2105
}
2106
2107
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
2108
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
2109
{
2110
    int items_count = 0;
2111
    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
2112
    while (*p)
2113
    {
2114
        p += strlen(p) + 1;
2115
        items_count++;
2116
    }
2117
    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
2118
    return value_changed;
2119
}
2120
2121
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2122
2123
struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
2124
static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
2125
{
2126
    ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
2127
    const char* s = NULL;
2128
    data->OldCallback(data->UserData, idx, &s);
2129
    return s;
2130
}
2131
2132
bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items)
2133
{
2134
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
2135
    return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);
2136
}
2137
bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items)
2138
{
2139
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
2140
    return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items);
2141
}
2142
2143
#endif
2144
2145
//-------------------------------------------------------------------------
2146
// [SECTION] Data Type and Data Formatting Helpers [Internal]
2147
//-------------------------------------------------------------------------
2148
// - DataTypeGetInfo()
2149
// - DataTypeFormatString()
2150
// - DataTypeApplyOp()
2151
// - DataTypeApplyFromText()
2152
// - DataTypeCompare()
2153
// - DataTypeClamp()
2154
// - GetMinimumStepAtDecimalPrecision
2155
// - RoundScalarWithFormat<>()
2156
//-------------------------------------------------------------------------
2157
2158
static const ImGuiDataTypeInfo GDataTypeInfo[] =
2159
{
2160
    { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
2161
    { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
2162
    { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
2163
    { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
2164
    { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
2165
    { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
2166
#ifdef _MSC_VER
2167
    { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
2168
    { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
2169
#else
2170
    { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
2171
    { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
2172
#endif
2173
    { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
2174
    { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
2175
    { sizeof(bool),             "bool", "%d",   "%d"    },  // ImGuiDataType_Bool
2176
};
2177
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
2178
2179
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
2180
{
2181
    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2182
    return &GDataTypeInfo[data_type];
2183
}
2184
2185
int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
2186
{
2187
    // Signedness doesn't matter when pushing integer arguments
2188
    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
2189
        return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
2190
    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2191
        return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
2192
    if (data_type == ImGuiDataType_Float)
2193
        return ImFormatString(buf, buf_size, format, *(const float*)p_data);
2194
    if (data_type == ImGuiDataType_Double)
2195
        return ImFormatString(buf, buf_size, format, *(const double*)p_data);
2196
    if (data_type == ImGuiDataType_S8)
2197
        return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
2198
    if (data_type == ImGuiDataType_U8)
2199
        return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
2200
    if (data_type == ImGuiDataType_S16)
2201
        return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
2202
    if (data_type == ImGuiDataType_U16)
2203
        return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
2204
    IM_ASSERT(0);
2205
    return 0;
2206
}
2207
2208
void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
2209
{
2210
    IM_ASSERT(op == '+' || op == '-');
2211
    switch (data_type)
2212
    {
2213
        case ImGuiDataType_S8:
2214
            if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
2215
            if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
2216
            return;
2217
        case ImGuiDataType_U8:
2218
            if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
2219
            if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
2220
            return;
2221
        case ImGuiDataType_S16:
2222
            if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2223
            if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2224
            return;
2225
        case ImGuiDataType_U16:
2226
            if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2227
            if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2228
            return;
2229
        case ImGuiDataType_S32:
2230
            if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2231
            if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2232
            return;
2233
        case ImGuiDataType_U32:
2234
            if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2235
            if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2236
            return;
2237
        case ImGuiDataType_S64:
2238
            if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2239
            if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2240
            return;
2241
        case ImGuiDataType_U64:
2242
            if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2243
            if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2244
            return;
2245
        case ImGuiDataType_Float:
2246
            if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
2247
            if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
2248
            return;
2249
        case ImGuiDataType_Double:
2250
            if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
2251
            if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
2252
            return;
2253
        case ImGuiDataType_COUNT: break;
2254
    }
2255
    IM_ASSERT(0);
2256
}
2257
2258
// User can input math operators (e.g. +100) to edit a numerical values.
2259
// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
2260
bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)
2261
{
2262
    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2263
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2264
    ImGuiDataTypeStorage data_backup;
2265
    memcpy(&data_backup, p_data, type_info->Size);
2266
2267
    while (ImCharIsBlankA(*buf))
2268
        buf++;
2269
    if (!buf[0])
2270
    {
2271
        if (p_data_when_empty != NULL)
2272
        {
2273
            memcpy(p_data, p_data_when_empty, type_info->Size);
2274
            return memcmp(&data_backup, p_data, type_info->Size) != 0;
2275
        }
2276
        return false;
2277
    }
2278
2279
    // Sanitize format
2280
    // - For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
2281
    // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
2282
    char format_sanitized[32];
2283
    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2284
        format = type_info->ScanFmt;
2285
    else
2286
        format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
2287
2288
    // Small types need a 32-bit buffer to receive the result from scanf()
2289
    int v32 = 0;
2290
    if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
2291
        return false;
2292
    if (type_info->Size < 4)
2293
    {
2294
        if (data_type == ImGuiDataType_S8)
2295
            *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
2296
        else if (data_type == ImGuiDataType_U8)
2297
            *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
2298
        else if (data_type == ImGuiDataType_S16)
2299
            *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
2300
        else if (data_type == ImGuiDataType_U16)
2301
            *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
2302
        else
2303
            IM_ASSERT(0);
2304
    }
2305
2306
    return memcmp(&data_backup, p_data, type_info->Size) != 0;
2307
}
2308
2309
template<typename T>
2310
static int DataTypeCompareT(const T* lhs, const T* rhs)
2311
{
2312
    if (*lhs < *rhs) return -1;
2313
    if (*lhs > *rhs) return +1;
2314
    return 0;
2315
}
2316
2317
int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2318
{
2319
    switch (data_type)
2320
    {
2321
    case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
2322
    case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
2323
    case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
2324
    case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
2325
    case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
2326
    case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
2327
    case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
2328
    case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
2329
    case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
2330
    case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
2331
    case ImGuiDataType_COUNT:  break;
2332
    }
2333
    IM_ASSERT(0);
2334
    return 0;
2335
}
2336
2337
template<typename T>
2338
static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2339
{
2340
    // Clamp, both sides are optional, return true if modified
2341
    if (v_min && *v < *v_min) { *v = *v_min; return true; }
2342
    if (v_max && *v > *v_max) { *v = *v_max; return true; }
2343
    return false;
2344
}
2345
2346
bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2347
{
2348
    switch (data_type)
2349
    {
2350
    case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
2351
    case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
2352
    case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
2353
    case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
2354
    case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
2355
    case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
2356
    case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
2357
    case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
2358
    case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
2359
    case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
2360
    case ImGuiDataType_COUNT:  break;
2361
    }
2362
    IM_ASSERT(0);
2363
    return false;
2364
}
2365
2366
bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data)
2367
{
2368
    ImGuiContext& g = *GImGui;
2369
    return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0;
2370
}
2371
2372
static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2373
{
2374
    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 };
2375
    if (decimal_precision < 0)
2376
        return FLT_MIN;
2377
    return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
2378
}
2379
2380
template<typename TYPE>
2381
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2382
{
2383
    IM_UNUSED(data_type);
2384
    IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2385
    const char* fmt_start = ImParseFormatFindStart(format);
2386
    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2387
        return v;
2388
2389
    // Sanitize format
2390
    char fmt_sanitized[32];
2391
    ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
2392
    fmt_start = fmt_sanitized;
2393
2394
    // Format value with our rounding, and read back
2395
    char v_str[64];
2396
    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
2397
    const char* p = v_str;
2398
    while (*p == ' ')
2399
        p++;
2400
    v = (TYPE)ImAtof(p);
2401
2402
    return v;
2403
}
2404
2405
//-------------------------------------------------------------------------
2406
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2407
//-------------------------------------------------------------------------
2408
// - DragBehaviorT<>() [Internal]
2409
// - DragBehavior() [Internal]
2410
// - DragScalar()
2411
// - DragScalarN()
2412
// - DragFloat()
2413
// - DragFloat2()
2414
// - DragFloat3()
2415
// - DragFloat4()
2416
// - DragFloatRange2()
2417
// - DragInt()
2418
// - DragInt2()
2419
// - DragInt3()
2420
// - DragInt4()
2421
// - DragIntRange2()
2422
//-------------------------------------------------------------------------
2423
2424
// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2425
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2426
bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2427
{
2428
    ImGuiContext& g = *GImGui;
2429
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2430
    const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange)));
2431
    const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);
2432
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2433
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2434
2435
    // Default tweak speed
2436
    if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))
2437
        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2438
2439
    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2440
    float adjust_delta = 0.0f;
2441
    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2442
    {
2443
        adjust_delta = g.IO.MouseDelta[axis];
2444
        if (g.IO.KeyAlt)
2445
            adjust_delta *= 1.0f / 100.0f;
2446
        if (g.IO.KeyShift)
2447
            adjust_delta *= 10.0f;
2448
    }
2449
    else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2450
    {
2451
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2452
        const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2453
        const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2454
        const float tweak_factor = tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
2455
        adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
2456
        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
2457
    }
2458
    adjust_delta *= v_speed;
2459
2460
    // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2461
    if (axis == ImGuiAxis_Y)
2462
        adjust_delta = -adjust_delta;
2463
2464
    // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2465
    if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2466
        adjust_delta /= (float)(v_max - v_min);
2467
2468
    // Clear current value on activation
2469
    // 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.
2470
    const bool is_just_activated = g.ActiveIdIsJustActivated;
2471
    const bool is_already_past_limits_and_pushing_outward = is_bounded && !is_wrapped && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
2472
    if (is_just_activated || is_already_past_limits_and_pushing_outward)
2473
    {
2474
        g.DragCurrentAccum = 0.0f;
2475
        g.DragCurrentAccumDirty = false;
2476
    }
2477
    else if (adjust_delta != 0.0f)
2478
    {
2479
        g.DragCurrentAccum += adjust_delta;
2480
        g.DragCurrentAccumDirty = true;
2481
    }
2482
2483
    if (!g.DragCurrentAccumDirty)
2484
        return false;
2485
2486
    TYPE v_cur = *v;
2487
    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2488
2489
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2490
    const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2491
    if (is_logarithmic)
2492
    {
2493
        // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
2494
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2495
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2496
2497
        // Convert to parametric space, apply delta, convert back
2498
        float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2499
        float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2500
        v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2501
        v_old_ref_for_accum_remainder = v_old_parametric;
2502
    }
2503
    else
2504
    {
2505
        v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2506
    }
2507
2508
    // Round to user desired precision based on format string
2509
    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2510
        v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
2511
2512
    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2513
    g.DragCurrentAccumDirty = false;
2514
    if (is_logarithmic)
2515
    {
2516
        // Convert to parametric space, apply delta, convert back
2517
        float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2518
        g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2519
    }
2520
    else
2521
    {
2522
        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2523
    }
2524
2525
    // Lose zero sign for float/double
2526
    if (v_cur == (TYPE)-0)
2527
        v_cur = (TYPE)0;
2528
2529
    if (*v != v_cur && is_bounded)
2530
    {
2531
        if (is_wrapped)
2532
        {
2533
            // Wrap values
2534
            if (v_cur < v_min)
2535
                v_cur += v_max - v_min + (is_floating_point ? 0 : 1);
2536
            if (v_cur > v_max)
2537
                v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);
2538
        }
2539
        else
2540
        {
2541
            // Clamp values + handle overflow/wrap-around for integer types.
2542
            if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2543
                v_cur = v_min;
2544
            if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2545
                v_cur = v_max;
2546
        }
2547
    }
2548
2549
    // Apply result
2550
    if (*v == v_cur)
2551
        return false;
2552
    *v = v_cur;
2553
    return true;
2554
}
2555
2556
bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2557
{
2558
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2559
    IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
2560
2561
    ImGuiContext& g = *GImGui;
2562
    if (g.ActiveId == id)
2563
    {
2564
        // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
2565
        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2566
            ClearActiveID();
2567
        else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2568
            ClearActiveID();
2569
    }
2570
    if (g.ActiveId != id)
2571
        return false;
2572
    if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2573
        return false;
2574
2575
    switch (data_type)
2576
    {
2577
    case ImGuiDataType_S8:     { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS8*) p_min : IM_S8_MIN,  p_max ? *(const ImS8*)p_max  : IM_S8_MAX,  format, flags); if (r) *(ImS8*)p_v = (ImS8)v32; return r; }
2578
    case ImGuiDataType_U8:     { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU8*) p_min : IM_U8_MIN,  p_max ? *(const ImU8*)p_max  : IM_U8_MAX,  format, flags); if (r) *(ImU8*)p_v = (ImU8)v32; return r; }
2579
    case ImGuiDataType_S16:    { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = DragBehaviorT<ImS32, ImS32, float>(ImGuiDataType_S32, &v32, v_speed, p_min ? *(const ImS16*)p_min : IM_S16_MIN, p_max ? *(const ImS16*)p_max : IM_S16_MAX, format, flags); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
2580
    case ImGuiDataType_U16:    { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = DragBehaviorT<ImU32, ImS32, float>(ImGuiDataType_U32, &v32, v_speed, p_min ? *(const ImU16*)p_min : IM_U16_MIN, p_max ? *(const ImU16*)p_max : IM_U16_MAX, format, flags); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
2581
    case ImGuiDataType_S32:    return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)p_v,  v_speed, p_min ? *(const ImS32* )p_min : IM_S32_MIN, p_max ? *(const ImS32* )p_max : IM_S32_MAX, format, flags);
2582
    case ImGuiDataType_U32:    return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)p_v,  v_speed, p_min ? *(const ImU32* )p_min : IM_U32_MIN, p_max ? *(const ImU32* )p_max : IM_U32_MAX, format, flags);
2583
    case ImGuiDataType_S64:    return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)p_v,  v_speed, p_min ? *(const ImS64* )p_min : IM_S64_MIN, p_max ? *(const ImS64* )p_max : IM_S64_MAX, format, flags);
2584
    case ImGuiDataType_U64:    return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)p_v,  v_speed, p_min ? *(const ImU64* )p_min : IM_U64_MIN, p_max ? *(const ImU64* )p_max : IM_U64_MAX, format, flags);
2585
    case ImGuiDataType_Float:  return DragBehaviorT<float, float, float >(data_type, (float*)p_v,  v_speed, p_min ? *(const float* )p_min : -FLT_MAX,   p_max ? *(const float* )p_max : FLT_MAX,    format, flags);
2586
    case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)p_v, v_speed, p_min ? *(const double*)p_min : -DBL_MAX,   p_max ? *(const double*)p_max : DBL_MAX,    format, flags);
2587
    case ImGuiDataType_COUNT:  break;
2588
    }
2589
    IM_ASSERT(0);
2590
    return false;
2591
}
2592
2593
// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.
2594
// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
2595
bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2596
{
2597
    ImGuiWindow* window = GetCurrentWindow();
2598
    if (window->SkipItems)
2599
        return false;
2600
2601
    ImGuiContext& g = *GImGui;
2602
    const ImGuiStyle& style = g.Style;
2603
    const ImGuiID id = window->GetID(label);
2604
    const float w = CalcItemWidth();
2605
2606
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
2607
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2608
    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));
2609
2610
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2611
    ItemSize(total_bb, style.FramePadding.y);
2612
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
2613
        return false;
2614
2615
    // Default format string when passing NULL
2616
    if (format == NULL)
2617
        format = DataTypeGetInfo(data_type)->PrintFmt;
2618
2619
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
2620
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2621
    if (!temp_input_is_active)
2622
    {
2623
        // Tabbing or CTRL-clicking on Drag turns it into an InputText
2624
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
2625
        const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
2626
        const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
2627
        if (make_active && (clicked || double_clicked))
2628
            SetKeyOwner(ImGuiKey_MouseLeft, id);
2629
        if (make_active && temp_input_allowed)
2630
            if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
2631
                temp_input_is_active = true;
2632
2633
        // (Optional) simple click (without moving) turns Drag into an InputText
2634
        if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2635
            if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2636
            {
2637
                g.NavActivateId = id;
2638
                g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
2639
                temp_input_is_active = true;
2640
            }
2641
2642
        if (make_active && !temp_input_is_active)
2643
        {
2644
            SetActiveID(id, window);
2645
            SetFocusID(id, window);
2646
            FocusWindow(window);
2647
            g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2648
        }
2649
    }
2650
2651
    if (temp_input_is_active)
2652
    {
2653
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
2654
        bool clamp_enabled = false;
2655
        if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
2656
        {
2657
            const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max
2658
            if (p_min == NULL || p_max == NULL || clamp_range_dir < 0)
2659
                clamp_enabled = true;
2660
            else if (clamp_range_dir == 0)
2661
                clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;
2662
        }
2663
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
2664
    }
2665
2666
    // Draw frame
2667
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2668
    RenderNavCursor(frame_bb, id);
2669
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
2670
2671
    // Drag behavior
2672
    const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
2673
    if (value_changed)
2674
        MarkItemEdited(id);
2675
2676
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2677
    char value_buf[64];
2678
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
2679
    if (g.LogEnabled)
2680
        LogSetNextTextDecoration("{", "}");
2681
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
2682
2683
    if (label_size.x > 0.0f)
2684
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
2685
2686
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
2687
    return value_changed;
2688
}
2689
2690
bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
2691
{
2692
    ImGuiWindow* window = GetCurrentWindow();
2693
    if (window->SkipItems)
2694
        return false;
2695
2696
    ImGuiContext& g = *GImGui;
2697
    bool value_changed = false;
2698
    BeginGroup();
2699
    PushID(label);
2700
    PushMultiItemsWidths(components, CalcItemWidth());
2701
    size_t type_size = GDataTypeInfo[data_type].Size;
2702
    for (int i = 0; i < components; i++)
2703
    {
2704
        PushID(i);
2705
        if (i > 0)
2706
            SameLine(0, g.Style.ItemInnerSpacing.x);
2707
        value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
2708
        PopID();
2709
        PopItemWidth();
2710
        p_data = (void*)((char*)p_data + type_size);
2711
    }
2712
    PopID();
2713
2714
    const char* label_end = FindRenderedTextEnd(label);
2715
    if (label != label_end)
2716
    {
2717
        SameLine(0, g.Style.ItemInnerSpacing.x);
2718
        TextEx(label, label_end);
2719
    }
2720
2721
    EndGroup();
2722
    return value_changed;
2723
}
2724
2725
bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2726
{
2727
    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
2728
}
2729
2730
bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2731
{
2732
    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
2733
}
2734
2735
bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2736
{
2737
    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
2738
}
2739
2740
bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2741
{
2742
    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
2743
}
2744
2745
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2746
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, ImGuiSliderFlags flags)
2747
{
2748
    ImGuiWindow* window = GetCurrentWindow();
2749
    if (window->SkipItems)
2750
        return false;
2751
2752
    ImGuiContext& g = *GImGui;
2753
    PushID(label);
2754
    BeginGroup();
2755
    PushMultiItemsWidths(2, CalcItemWidth());
2756
2757
    float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2758
    float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2759
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2760
    bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
2761
    PopItemWidth();
2762
    SameLine(0, g.Style.ItemInnerSpacing.x);
2763
2764
    float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2765
    float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2766
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2767
    value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
2768
    PopItemWidth();
2769
    SameLine(0, g.Style.ItemInnerSpacing.x);
2770
2771
    TextEx(label, FindRenderedTextEnd(label));
2772
    EndGroup();
2773
    PopID();
2774
2775
    return value_changed;
2776
}
2777
2778
// NB: v_speed is float to allow adjusting the drag speed with more precision
2779
bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2780
{
2781
    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
2782
}
2783
2784
bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2785
{
2786
    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
2787
}
2788
2789
bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2790
{
2791
    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
2792
}
2793
2794
bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2795
{
2796
    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
2797
}
2798
2799
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2800
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, ImGuiSliderFlags flags)
2801
{
2802
    ImGuiWindow* window = GetCurrentWindow();
2803
    if (window->SkipItems)
2804
        return false;
2805
2806
    ImGuiContext& g = *GImGui;
2807
    PushID(label);
2808
    BeginGroup();
2809
    PushMultiItemsWidths(2, CalcItemWidth());
2810
2811
    int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2812
    int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2813
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2814
    bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
2815
    PopItemWidth();
2816
    SameLine(0, g.Style.ItemInnerSpacing.x);
2817
2818
    int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2819
    int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2820
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2821
    value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
2822
    PopItemWidth();
2823
    SameLine(0, g.Style.ItemInnerSpacing.x);
2824
2825
    TextEx(label, FindRenderedTextEnd(label));
2826
    EndGroup();
2827
    PopID();
2828
2829
    return value_changed;
2830
}
2831
2832
//-------------------------------------------------------------------------
2833
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2834
//-------------------------------------------------------------------------
2835
// - ScaleRatioFromValueT<> [Internal]
2836
// - ScaleValueFromRatioT<> [Internal]
2837
// - SliderBehaviorT<>() [Internal]
2838
// - SliderBehavior() [Internal]
2839
// - SliderScalar()
2840
// - SliderScalarN()
2841
// - SliderFloat()
2842
// - SliderFloat2()
2843
// - SliderFloat3()
2844
// - SliderFloat4()
2845
// - SliderAngle()
2846
// - SliderInt()
2847
// - SliderInt2()
2848
// - SliderInt3()
2849
// - SliderInt4()
2850
// - VSliderScalar()
2851
// - VSliderFloat()
2852
// - VSliderInt()
2853
//-------------------------------------------------------------------------
2854
2855
// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2856
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2857
float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2858
{
2859
    if (v_min == v_max)
2860
        return 0.0f;
2861
    IM_UNUSED(data_type);
2862
2863
    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2864
    if (is_logarithmic)
2865
    {
2866
        bool flipped = v_max < v_min;
2867
2868
        if (flipped) // Handle the case where the range is backwards
2869
            ImSwap(v_min, v_max);
2870
2871
        // Fudge min/max to avoid getting close to log(0)
2872
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2873
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2874
2875
        // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2876
        if ((v_min == 0.0f) && (v_max < 0.0f))
2877
            v_min_fudged = -logarithmic_zero_epsilon;
2878
        else if ((v_max == 0.0f) && (v_min < 0.0f))
2879
            v_max_fudged = -logarithmic_zero_epsilon;
2880
2881
        float result;
2882
        if (v_clamped <= v_min_fudged)
2883
            result = 0.0f; // Workaround for values that are in-range but below our fudge
2884
        else if (v_clamped >= v_max_fudged)
2885
            result = 1.0f; // Workaround for values that are in-range but above our fudge
2886
        else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
2887
        {
2888
            float zero_point_center = (-(float)v_min) / ((float)v_max - (float)v_min); // The zero point in parametric space.  There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine)
2889
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2890
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2891
            if (v == 0.0f)
2892
                result = zero_point_center; // Special case for exactly zero
2893
            else if (v < 0.0f)
2894
                result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
2895
            else
2896
                result = zero_point_snap_R + ((float)(ImLog((FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(v_max_fudged / logarithmic_zero_epsilon)) * (1.0f - zero_point_snap_R));
2897
        }
2898
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2899
            result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
2900
        else
2901
            result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
2902
2903
        return flipped ? (1.0f - result) : result;
2904
    }
2905
    else
2906
    {
2907
        // Linear slider
2908
        return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
2909
    }
2910
}
2911
2912
// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
2913
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2914
TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2915
{
2916
    // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
2917
    // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
2918
    if (t <= 0.0f || v_min == v_max)
2919
        return v_min;
2920
    if (t >= 1.0f)
2921
        return v_max;
2922
2923
    TYPE result = (TYPE)0;
2924
    if (is_logarithmic)
2925
    {
2926
        // Fudge min/max to avoid getting silly results close to zero
2927
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
2928
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
2929
2930
        const bool flipped = v_max < v_min; // Check if range is "backwards"
2931
        if (flipped)
2932
            ImSwap(v_min_fudged, v_max_fudged);
2933
2934
        // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
2935
        if ((v_max == 0.0f) && (v_min < 0.0f))
2936
            v_max_fudged = -logarithmic_zero_epsilon;
2937
2938
        float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
2939
2940
        if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
2941
        {
2942
            float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
2943
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
2944
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
2945
            if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
2946
                result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
2947
            else if (t_with_flip < zero_point_center)
2948
                result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
2949
            else
2950
                result = (TYPE)(logarithmic_zero_epsilon * ImPow(v_max_fudged / logarithmic_zero_epsilon, (FLOATTYPE)((t_with_flip - zero_point_snap_R) / (1.0f - zero_point_snap_R))));
2951
        }
2952
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
2953
            result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
2954
        else
2955
            result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
2956
    }
2957
    else
2958
    {
2959
        // Linear slider
2960
        const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2961
        if (is_floating_point)
2962
        {
2963
            result = ImLerp(v_min, v_max, t);
2964
        }
2965
        else if (t < 1.0)
2966
        {
2967
            // - For integer values we want the clicking position to match the grab box so we round above
2968
            //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
2969
            // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64
2970
            //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
2971
            FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
2972
            result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
2973
        }
2974
    }
2975
2976
    return result;
2977
}
2978
2979
// FIXME: Try to move more of the code into shared SliderBehavior()
2980
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2981
bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
2982
{
2983
    ImGuiContext& g = *GImGui;
2984
    const ImGuiStyle& style = g.Style;
2985
2986
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2987
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2988
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2989
    const float v_range_f = (float)(v_min < v_max ? v_max - v_min : v_min - v_max); // We don't need high precision for what we do with it.
2990
2991
    // Calculate bounds
2992
    const float grab_padding = 2.0f; // FIXME: Should be part of style.
2993
    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
2994
    float grab_sz = style.GrabMinSize;
2995
    if (!is_floating_point && v_range_f >= 0.0f)                         // v_range_f < 0 may happen on integer overflows
2996
        grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
2997
    grab_sz = ImMin(grab_sz, slider_sz);
2998
    const float slider_usable_sz = slider_sz - grab_sz;
2999
    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
3000
    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
3001
3002
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
3003
    float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
3004
    if (is_logarithmic)
3005
    {
3006
        // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound.
3007
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
3008
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
3009
        zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
3010
    }
3011
3012
    // Process interacting with the slider
3013
    bool value_changed = false;
3014
    if (g.ActiveId == id)
3015
    {
3016
        bool set_new_value = false;
3017
        float clicked_t = 0.0f;
3018
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
3019
        {
3020
            if (!g.IO.MouseDown[0])
3021
            {
3022
                ClearActiveID();
3023
            }
3024
            else
3025
            {
3026
                const float mouse_abs_pos = g.IO.MousePos[axis];
3027
                if (g.ActiveIdIsJustActivated)
3028
                {
3029
                    float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3030
                    if (axis == ImGuiAxis_Y)
3031
                        grab_t = 1.0f - grab_t;
3032
                    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
3033
                    const bool clicked_around_grab = (mouse_abs_pos >= grab_pos - grab_sz * 0.5f - 1.0f) && (mouse_abs_pos <= grab_pos + grab_sz * 0.5f + 1.0f); // No harm being extra generous here.
3034
                    g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
3035
                }
3036
                if (slider_usable_sz > 0.0f)
3037
                    clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
3038
                if (axis == ImGuiAxis_Y)
3039
                    clicked_t = 1.0f - clicked_t;
3040
                set_new_value = true;
3041
            }
3042
        }
3043
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
3044
        {
3045
            if (g.ActiveIdIsJustActivated)
3046
            {
3047
                g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
3048
                g.SliderCurrentAccumDirty = false;
3049
            }
3050
3051
            float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
3052
            if (input_delta != 0.0f)
3053
            {
3054
                const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
3055
                const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
3056
                const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
3057
                if (decimal_precision > 0)
3058
                {
3059
                    input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds
3060
                    if (tweak_slow)
3061
                        input_delta /= 10.0f;
3062
                }
3063
                else
3064
                {
3065
                    if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
3066
                        input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps
3067
                    else
3068
                        input_delta /= 100.0f;
3069
                }
3070
                if (tweak_fast)
3071
                    input_delta *= 10.0f;
3072
3073
                g.SliderCurrentAccum += input_delta;
3074
                g.SliderCurrentAccumDirty = true;
3075
            }
3076
3077
            float delta = g.SliderCurrentAccum;
3078
            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
3079
            {
3080
                ClearActiveID();
3081
            }
3082
            else if (g.SliderCurrentAccumDirty)
3083
            {
3084
                clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3085
3086
                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
3087
                {
3088
                    set_new_value = false;
3089
                    g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
3090
                }
3091
                else
3092
                {
3093
                    set_new_value = true;
3094
                    float old_clicked_t = clicked_t;
3095
                    clicked_t = ImSaturate(clicked_t + delta);
3096
3097
                    // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
3098
                    TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3099
                    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
3100
                        v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
3101
                    float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3102
3103
                    if (delta > 0)
3104
                        g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
3105
                    else
3106
                        g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
3107
                }
3108
3109
                g.SliderCurrentAccumDirty = false;
3110
            }
3111
        }
3112
3113
        if (set_new_value)
3114
            if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
3115
                set_new_value = false;
3116
3117
        if (set_new_value)
3118
        {
3119
            TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3120
3121
            // Round to user desired precision based on format string
3122
            if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
3123
                v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
3124
3125
            // Apply result
3126
            if (*v != v_new)
3127
            {
3128
                *v = v_new;
3129
                value_changed = true;
3130
            }
3131
        }
3132
    }
3133
3134
    if (slider_sz < 1.0f)
3135
    {
3136
        *out_grab_bb = ImRect(bb.Min, bb.Min);
3137
    }
3138
    else
3139
    {
3140
        // Output grab position so it can be displayed by the caller
3141
        float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3142
        if (axis == ImGuiAxis_Y)
3143
            grab_t = 1.0f - grab_t;
3144
        const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
3145
        if (axis == ImGuiAxis_X)
3146
            *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);
3147
        else
3148
            *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);
3149
    }
3150
3151
    return value_changed;
3152
}
3153
3154
// For 32-bit and larger types, slider bounds are limited to half the natural type range.
3155
// 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.
3156
// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
3157
bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* p_v, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, ImRect* out_grab_bb)
3158
{
3159
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
3160
    IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flags! Has the legacy 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
3161
    IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()
3162
3163
    switch (data_type)
3164
    {
3165
    case ImGuiDataType_S8:  { ImS32 v32 = (ImS32)*(ImS8*)p_v;  bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS8*)p_min,  *(const ImS8*)p_max,  format, flags, out_grab_bb); if (r) *(ImS8*)p_v  = (ImS8)v32;  return r; }
3166
    case ImGuiDataType_U8:  { ImU32 v32 = (ImU32)*(ImU8*)p_v;  bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU8*)p_min,  *(const ImU8*)p_max,  format, flags, out_grab_bb); if (r) *(ImU8*)p_v  = (ImU8)v32;  return r; }
3167
    case ImGuiDataType_S16: { ImS32 v32 = (ImS32)*(ImS16*)p_v; bool r = SliderBehaviorT<ImS32, ImS32, float>(bb, id, ImGuiDataType_S32, &v32, *(const ImS16*)p_min, *(const ImS16*)p_max, format, flags, out_grab_bb); if (r) *(ImS16*)p_v = (ImS16)v32; return r; }
3168
    case ImGuiDataType_U16: { ImU32 v32 = (ImU32)*(ImU16*)p_v; bool r = SliderBehaviorT<ImU32, ImS32, float>(bb, id, ImGuiDataType_U32, &v32, *(const ImU16*)p_min, *(const ImU16*)p_max, format, flags, out_grab_bb); if (r) *(ImU16*)p_v = (ImU16)v32; return r; }
3169
    case ImGuiDataType_S32:
3170
        IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
3171
        return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)p_v,  *(const ImS32*)p_min,  *(const ImS32*)p_max,  format, flags, out_grab_bb);
3172
    case ImGuiDataType_U32:
3173
        IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
3174
        return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)p_v,  *(const ImU32*)p_min,  *(const ImU32*)p_max,  format, flags, out_grab_bb);
3175
    case ImGuiDataType_S64:
3176
        IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
3177
        return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)p_v,  *(const ImS64*)p_min,  *(const ImS64*)p_max,  format, flags, out_grab_bb);
3178
    case ImGuiDataType_U64:
3179
        IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
3180
        return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)p_v,  *(const ImU64*)p_min,  *(const ImU64*)p_max,  format, flags, out_grab_bb);
3181
    case ImGuiDataType_Float:
3182
        IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
3183
        return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)p_v,  *(const float*)p_min,  *(const float*)p_max,  format, flags, out_grab_bb);
3184
    case ImGuiDataType_Double:
3185
        IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
3186
        return SliderBehaviorT<double, double, double>(bb, id, data_type, (double*)p_v, *(const double*)p_min, *(const double*)p_max, format, flags, out_grab_bb);
3187
    case ImGuiDataType_COUNT: break;
3188
    }
3189
    IM_ASSERT(0);
3190
    return false;
3191
}
3192
3193
// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
3194
// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3195
bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
3196
{
3197
    ImGuiWindow* window = GetCurrentWindow();
3198
    if (window->SkipItems)
3199
        return false;
3200
3201
    ImGuiContext& g = *GImGui;
3202
    const ImGuiStyle& style = g.Style;
3203
    const ImGuiID id = window->GetID(label);
3204
    const float w = CalcItemWidth();
3205
3206
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
3207
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
3208
    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));
3209
3210
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
3211
    ItemSize(total_bb, style.FramePadding.y);
3212
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
3213
        return false;
3214
3215
    // Default format string when passing NULL
3216
    if (format == NULL)
3217
        format = DataTypeGetInfo(data_type)->PrintFmt;
3218
3219
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
3220
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
3221
    if (!temp_input_is_active)
3222
    {
3223
        // Tabbing or CTRL-clicking on Slider turns it into an input box
3224
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
3225
        const bool make_active = (clicked || g.NavActivateId == id);
3226
        if (make_active && clicked)
3227
            SetKeyOwner(ImGuiKey_MouseLeft, id);
3228
        if (make_active && temp_input_allowed)
3229
            if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
3230
                temp_input_is_active = true;
3231
3232
        if (make_active && !temp_input_is_active)
3233
        {
3234
            SetActiveID(id, window);
3235
            SetFocusID(id, window);
3236
            FocusWindow(window);
3237
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
3238
        }
3239
    }
3240
3241
    if (temp_input_is_active)
3242
    {
3243
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
3244
        const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0;
3245
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
3246
    }
3247
3248
    // Draw frame
3249
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3250
    RenderNavCursor(frame_bb, id);
3251
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3252
3253
    // Slider behavior
3254
    ImRect grab_bb;
3255
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
3256
    if (value_changed)
3257
        MarkItemEdited(id);
3258
3259
    // Render grab
3260
    if (grab_bb.Max.x > grab_bb.Min.x)
3261
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3262
3263
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3264
    char value_buf[64];
3265
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3266
    if (g.LogEnabled)
3267
        LogSetNextTextDecoration("{", "}");
3268
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
3269
3270
    if (label_size.x > 0.0f)
3271
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3272
3273
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
3274
    return value_changed;
3275
}
3276
3277
// Add multiple sliders on 1 line for compact edition of multiple components
3278
bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, ImGuiSliderFlags flags)
3279
{
3280
    ImGuiWindow* window = GetCurrentWindow();
3281
    if (window->SkipItems)
3282
        return false;
3283
3284
    ImGuiContext& g = *GImGui;
3285
    bool value_changed = false;
3286
    BeginGroup();
3287
    PushID(label);
3288
    PushMultiItemsWidths(components, CalcItemWidth());
3289
    size_t type_size = GDataTypeInfo[data_type].Size;
3290
    for (int i = 0; i < components; i++)
3291
    {
3292
        PushID(i);
3293
        if (i > 0)
3294
            SameLine(0, g.Style.ItemInnerSpacing.x);
3295
        value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
3296
        PopID();
3297
        PopItemWidth();
3298
        v = (void*)((char*)v + type_size);
3299
    }
3300
    PopID();
3301
3302
    const char* label_end = FindRenderedTextEnd(label);
3303
    if (label != label_end)
3304
    {
3305
        SameLine(0, g.Style.ItemInnerSpacing.x);
3306
        TextEx(label, label_end);
3307
    }
3308
3309
    EndGroup();
3310
    return value_changed;
3311
}
3312
3313
bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3314
{
3315
    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3316
}
3317
3318
bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3319
{
3320
    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
3321
}
3322
3323
bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3324
{
3325
    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
3326
}
3327
3328
bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3329
{
3330
    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
3331
}
3332
3333
bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
3334
{
3335
    if (format == NULL)
3336
        format = "%.0f deg";
3337
    float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
3338
    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
3339
    *v_rad = v_deg * (2 * IM_PI) / 360.0f;
3340
    return value_changed;
3341
}
3342
3343
bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3344
{
3345
    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3346
}
3347
3348
bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3349
{
3350
    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
3351
}
3352
3353
bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3354
{
3355
    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
3356
}
3357
3358
bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3359
{
3360
    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
3361
}
3362
3363
bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
3364
{
3365
    ImGuiWindow* window = GetCurrentWindow();
3366
    if (window->SkipItems)
3367
        return false;
3368
3369
    ImGuiContext& g = *GImGui;
3370
    const ImGuiStyle& style = g.Style;
3371
    const ImGuiID id = window->GetID(label);
3372
3373
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
3374
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3375
    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));
3376
3377
    ItemSize(bb, style.FramePadding.y);
3378
    if (!ItemAdd(frame_bb, id))
3379
        return false;
3380
3381
    // Default format string when passing NULL
3382
    if (format == NULL)
3383
        format = DataTypeGetInfo(data_type)->PrintFmt;
3384
3385
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
3386
    const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
3387
    if (clicked || g.NavActivateId == id)
3388
    {
3389
        if (clicked)
3390
            SetKeyOwner(ImGuiKey_MouseLeft, id);
3391
        SetActiveID(id, window);
3392
        SetFocusID(id, window);
3393
        FocusWindow(window);
3394
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
3395
    }
3396
3397
    // Draw frame
3398
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3399
    RenderNavCursor(frame_bb, id);
3400
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3401
3402
    // Slider behavior
3403
    ImRect grab_bb;
3404
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
3405
    if (value_changed)
3406
        MarkItemEdited(id);
3407
3408
    // Render grab
3409
    if (grab_bb.Max.y > grab_bb.Min.y)
3410
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3411
3412
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3413
    // For the vertical slider we allow centered text to overlap the frame padding
3414
    char value_buf[64];
3415
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
3416
    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));
3417
    if (label_size.x > 0.0f)
3418
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
3419
3420
    return value_changed;
3421
}
3422
3423
bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3424
{
3425
    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3426
}
3427
3428
bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3429
{
3430
    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3431
}
3432
3433
//-------------------------------------------------------------------------
3434
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
3435
//-------------------------------------------------------------------------
3436
// - ImParseFormatFindStart() [Internal]
3437
// - ImParseFormatFindEnd() [Internal]
3438
// - ImParseFormatTrimDecorations() [Internal]
3439
// - ImParseFormatSanitizeForPrinting() [Internal]
3440
// - ImParseFormatSanitizeForScanning() [Internal]
3441
// - ImParseFormatPrecision() [Internal]
3442
// - TempInputTextScalar() [Internal]
3443
// - InputScalar()
3444
// - InputScalarN()
3445
// - InputFloat()
3446
// - InputFloat2()
3447
// - InputFloat3()
3448
// - InputFloat4()
3449
// - InputInt()
3450
// - InputInt2()
3451
// - InputInt3()
3452
// - InputInt4()
3453
// - InputDouble()
3454
//-------------------------------------------------------------------------
3455
3456
// We don't use strchr() because our strings are usually very short and often start with '%'
3457
const char* ImParseFormatFindStart(const char* fmt)
3458
{
3459
    while (char c = fmt[0])
3460
    {
3461
        if (c == '%' && fmt[1] != '%')
3462
            return fmt;
3463
        else if (c == '%')
3464
            fmt++;
3465
        fmt++;
3466
    }
3467
    return fmt;
3468
}
3469
3470
const char* ImParseFormatFindEnd(const char* fmt)
3471
{
3472
    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
3473
    if (fmt[0] != '%')
3474
        return fmt;
3475
    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
3476
    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
3477
    for (char c; (c = *fmt) != 0; fmt++)
3478
    {
3479
        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
3480
            return fmt + 1;
3481
        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
3482
            return fmt + 1;
3483
    }
3484
    return fmt;
3485
}
3486
3487
// Extract the format out of a format string with leading or trailing decorations
3488
//  fmt = "blah blah"  -> return ""
3489
//  fmt = "%.3f"       -> return fmt
3490
//  fmt = "hello %.3f" -> return fmt + 6
3491
//  fmt = "%.3f hello" -> return buf written with "%.3f"
3492
const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
3493
{
3494
    const char* fmt_start = ImParseFormatFindStart(fmt);
3495
    if (fmt_start[0] != '%')
3496
        return "";
3497
    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
3498
    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
3499
        return fmt_start;
3500
    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
3501
    return buf;
3502
}
3503
3504
// Sanitize format
3505
// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
3506
// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
3507
void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3508
{
3509
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3510
    IM_UNUSED(fmt_out_size);
3511
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3512
    while (fmt_in < fmt_end)
3513
    {
3514
        char c = *fmt_in++;
3515
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3516
            *(fmt_out++) = c;
3517
    }
3518
    *fmt_out = 0; // Zero-terminate
3519
}
3520
3521
// - For scanning we need to remove all width and precision fields and flags "%+3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
3522
const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3523
{
3524
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3525
    const char* fmt_out_begin = fmt_out;
3526
    IM_UNUSED(fmt_out_size);
3527
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3528
    bool has_type = false;
3529
    while (fmt_in < fmt_end)
3530
    {
3531
        char c = *fmt_in++;
3532
        if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
3533
            continue;
3534
        has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
3535
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3536
            *(fmt_out++) = c;
3537
    }
3538
    *fmt_out = 0; // Zero-terminate
3539
    return fmt_out_begin;
3540
}
3541
3542
template<typename TYPE>
3543
static const char* ImAtoi(const char* src, TYPE* output)
3544
{
3545
    int negative = 0;
3546
    if (*src == '-') { negative = 1; src++; }
3547
    if (*src == '+') { src++; }
3548
    TYPE v = 0;
3549
    while (*src >= '0' && *src <= '9')
3550
        v = (v * 10) + (*src++ - '0');
3551
    *output = negative ? -v : v;
3552
    return src;
3553
}
3554
3555
// Parse display precision back from the display format string
3556
// 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.
3557
int ImParseFormatPrecision(const char* fmt, int default_precision)
3558
{
3559
    fmt = ImParseFormatFindStart(fmt);
3560
    if (fmt[0] != '%')
3561
        return default_precision;
3562
    fmt++;
3563
    while (*fmt >= '0' && *fmt <= '9')
3564
        fmt++;
3565
    int precision = INT_MAX;
3566
    if (*fmt == '.')
3567
    {
3568
        fmt = ImAtoi<int>(fmt + 1, &precision);
3569
        if (precision < 0 || precision > 99)
3570
            precision = default_precision;
3571
    }
3572
    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
3573
        precision = -1;
3574
    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
3575
        precision = -1;
3576
    return (precision == INT_MAX) ? default_precision : precision;
3577
}
3578
3579
// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
3580
// FIXME: Facilitate using this in variety of other situations.
3581
// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but
3582
// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.
3583
bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
3584
{
3585
    // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
3586
    // We clear ActiveID on the first frame to allow the InputText() taking it back.
3587
    ImGuiContext& g = *GImGui;
3588
    const bool init = (g.TempInputId != id);
3589
    if (init)
3590
        ClearActiveID();
3591
3592
    g.CurrentWindow->DC.CursorPos = bb.Min;
3593
    g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId;
3594
    bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
3595
    if (init)
3596
    {
3597
        // First frame we started displaying the InputText widget, we expect it to take the active id.
3598
        IM_ASSERT(g.ActiveId == id);
3599
        g.TempInputId = g.ActiveId;
3600
    }
3601
    return value_changed;
3602
}
3603
3604
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
3605
// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
3606
// However this may not be ideal for all uses, as some user code may break on out of bound values.
3607
bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
3608
{
3609
    // FIXME: May need to clarify display behavior if format doesn't contain %.
3610
    // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
3611
    ImGuiContext& g = *GImGui;
3612
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
3613
    char fmt_buf[32];
3614
    char data_buf[32];
3615
    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
3616
    if (format[0] == 0)
3617
        format = type_info->PrintFmt;
3618
    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
3619
    ImStrTrimBlanks(data_buf);
3620
3621
    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
3622
    g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
3623
    bool value_changed = false;
3624
    if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
3625
    {
3626
        // Backup old value
3627
        size_t data_type_size = type_info->Size;
3628
        ImGuiDataTypeStorage data_backup;
3629
        memcpy(&data_backup, p_data, data_type_size);
3630
3631
        // Apply new value (or operations) then clamp
3632
        DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);
3633
        if (p_clamp_min || p_clamp_max)
3634
        {
3635
            if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
3636
                ImSwap(p_clamp_min, p_clamp_max);
3637
            DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
3638
        }
3639
3640
        // Only mark as edited if new value is different
3641
        g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
3642
        value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
3643
        if (value_changed)
3644
            MarkItemEdited(id);
3645
    }
3646
    return value_changed;
3647
}
3648
3649
void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)
3650
{
3651
    ImGuiContext& g = *GImGui;
3652
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal;
3653
    memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);
3654
}
3655
3656
// Note: p_data, p_step, p_step_fast are _pointers_ to a memory address holding the data. For an Input widget, p_step and p_step_fast are optional.
3657
// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3658
bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3659
{
3660
    ImGuiWindow* window = GetCurrentWindow();
3661
    if (window->SkipItems)
3662
        return false;
3663
3664
    ImGuiContext& g = *GImGui;
3665
    ImGuiStyle& style = g.Style;
3666
    IM_ASSERT((flags & ImGuiInputTextFlags_EnterReturnsTrue) == 0); // Not supported by InputScalar(). Please open an issue if you this would be useful to you. Otherwise use IsItemDeactivatedAfterEdit()!
3667
3668
    if (format == NULL)
3669
        format = DataTypeGetInfo(data_type)->PrintFmt;
3670
3671
    void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;
3672
3673
    char buf[64];
3674
    if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
3675
        buf[0] = 0;
3676
    else
3677
        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
3678
3679
    // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
3680
    // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
3681
    g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
3682
    flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
3683
3684
    bool value_changed = false;
3685
    if (p_step == NULL)
3686
    {
3687
        if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
3688
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
3689
    }
3690
    else
3691
    {
3692
        const float button_size = GetFrameHeight();
3693
3694
        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
3695
        PushID(label);
3696
        SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
3697
        if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
3698
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
3699
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
3700
3701
        // Step buttons
3702
        const ImVec2 backup_frame_padding = style.FramePadding;
3703
        style.FramePadding.x = style.FramePadding.y;
3704
        if (flags & ImGuiInputTextFlags_ReadOnly)
3705
            BeginDisabled();
3706
        PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
3707
        SameLine(0, style.ItemInnerSpacing.x);
3708
        if (ButtonEx("-", ImVec2(button_size, button_size)))
3709
        {
3710
            DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3711
            value_changed = true;
3712
        }
3713
        SameLine(0, style.ItemInnerSpacing.x);
3714
        if (ButtonEx("+", ImVec2(button_size, button_size)))
3715
        {
3716
            DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3717
            value_changed = true;
3718
        }
3719
        PopItemFlag();
3720
        if (flags & ImGuiInputTextFlags_ReadOnly)
3721
            EndDisabled();
3722
3723
        const char* label_end = FindRenderedTextEnd(label);
3724
        if (label != label_end)
3725
        {
3726
            SameLine(0, style.ItemInnerSpacing.x);
3727
            TextEx(label, label_end);
3728
        }
3729
        style.FramePadding = backup_frame_padding;
3730
3731
        PopID();
3732
        EndGroup();
3733
    }
3734
3735
    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
3736
    if (value_changed)
3737
        MarkItemEdited(g.LastItemData.ID);
3738
3739
    return value_changed;
3740
}
3741
3742
bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_step, const void* p_step_fast, const char* format, ImGuiInputTextFlags flags)
3743
{
3744
    ImGuiWindow* window = GetCurrentWindow();
3745
    if (window->SkipItems)
3746
        return false;
3747
3748
    ImGuiContext& g = *GImGui;
3749
    bool value_changed = false;
3750
    BeginGroup();
3751
    PushID(label);
3752
    PushMultiItemsWidths(components, CalcItemWidth());
3753
    size_t type_size = GDataTypeInfo[data_type].Size;
3754
    for (int i = 0; i < components; i++)
3755
    {
3756
        PushID(i);
3757
        if (i > 0)
3758
            SameLine(0, g.Style.ItemInnerSpacing.x);
3759
        value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
3760
        PopID();
3761
        PopItemWidth();
3762
        p_data = (void*)((char*)p_data + type_size);
3763
    }
3764
    PopID();
3765
3766
    const char* label_end = FindRenderedTextEnd(label);
3767
    if (label != label_end)
3768
    {
3769
        SameLine(0.0f, g.Style.ItemInnerSpacing.x);
3770
        TextEx(label, label_end);
3771
    }
3772
3773
    EndGroup();
3774
    return value_changed;
3775
}
3776
3777
bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
3778
{
3779
    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
3780
}
3781
3782
bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
3783
{
3784
    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
3785
}
3786
3787
bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
3788
{
3789
    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
3790
}
3791
3792
bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
3793
{
3794
    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
3795
}
3796
3797
bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
3798
{
3799
    // 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.
3800
    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
3801
    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
3802
}
3803
3804
bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
3805
{
3806
    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
3807
}
3808
3809
bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
3810
{
3811
    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
3812
}
3813
3814
bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
3815
{
3816
    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
3817
}
3818
3819
bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
3820
{
3821
    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
3822
}
3823
3824
//-------------------------------------------------------------------------
3825
// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
3826
//-------------------------------------------------------------------------
3827
// - imstb_textedit.h include
3828
// - InputText()
3829
// - InputTextWithHint()
3830
// - InputTextMultiline()
3831
// - InputTextGetCharInfo() [Internal]
3832
// - InputTextReindexLines() [Internal]
3833
// - InputTextReindexLinesRange() [Internal]
3834
// - InputTextEx() [Internal]
3835
// - DebugNodeInputTextState() [Internal]
3836
//-------------------------------------------------------------------------
3837
3838
namespace ImStb
3839
{
3840
#include "imstb_textedit.h"
3841
}
3842
3843
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3844
{
3845
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
3846
    return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3847
}
3848
3849
bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3850
{
3851
    return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
3852
}
3853
3854
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3855
{
3856
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.
3857
    return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
3858
}
3859
3860
// This is only used in the path where the multiline widget is inactivate.
3861
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
3862
{
3863
    int line_count = 0;
3864
    const char* s = text_begin;
3865
    while (true)
3866
    {
3867
        const char* s_eol = strchr(s, '\n');
3868
        line_count++;
3869
        if (s_eol == NULL)
3870
        {
3871
            s = s + strlen(s);
3872
            break;
3873
        }
3874
        s = s_eol + 1;
3875
    }
3876
    *out_text_end = s;
3877
    return line_count;
3878
}
3879
3880
// FIXME: Ideally we'd share code with ImFont::CalcTextSizeA()
3881
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)
3882
{
3883
    ImGuiContext& g = *ctx;
3884
    ImFont* font = g.Font;
3885
    const float line_height = g.FontSize;
3886
    const float scale = line_height / font->FontSize;
3887
3888
    ImVec2 text_size = ImVec2(0, 0);
3889
    float line_width = 0.0f;
3890
3891
    const char* s = text_begin;
3892
    while (s < text_end)
3893
    {
3894
        unsigned int c = (unsigned int)*s;
3895
        if (c < 0x80)
3896
            s += 1;
3897
        else
3898
            s += ImTextCharFromUtf8(&c, s, text_end);
3899
3900
        if (c == '\n')
3901
        {
3902
            text_size.x = ImMax(text_size.x, line_width);
3903
            text_size.y += line_height;
3904
            line_width = 0.0f;
3905
            if (stop_on_new_line)
3906
                break;
3907
            continue;
3908
        }
3909
        if (c == '\r')
3910
            continue;
3911
3912
        const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;
3913
        line_width += char_width;
3914
    }
3915
3916
    if (text_size.x < line_width)
3917
        text_size.x = line_width;
3918
3919
    if (out_offset)
3920
        *out_offset = ImVec2(line_width, text_size.y + line_height);  // offset allow for the possibility of sitting after a trailing \n
3921
3922
    if (line_width > 0 || text_size.y == 0.0f)                        // whereas size.y will ignore the trailing \n
3923
        text_size.y += line_height;
3924
3925
    if (remaining)
3926
        *remaining = s;
3927
3928
    return text_size;
3929
}
3930
3931
// 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)
3932
// With our UTF-8 use of stb_textedit:
3933
// - STB_TEXTEDIT_GETCHAR is nothing more than a a "GETBYTE". It's only used to compare to ascii or to copy blocks of text so we are fine.
3934
// - One exception is the STB_TEXTEDIT_IS_SPACE feature which would expect a full char in order to handle full-width space such as 0x3000 (see ImCharIsBlankW).
3935
// - ...but we don't use that feature.
3936
namespace ImStb
3937
{
3938
static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->TextLen; }
3939
static char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx <= obj->TextLen); return obj->TextA[idx]; }
3940
static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { unsigned int c; ImTextCharFromUtf8(&c, obj->TextA.Data + line_start_idx + char_idx, obj->TextA.Data + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; }
3941
static char    STB_TEXTEDIT_NEWLINE = '\n';
3942
static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
3943
{
3944
    const char* text = obj->TextA.Data;
3945
    const char* text_remaining = NULL;
3946
    const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, &text_remaining, NULL, true);
3947
    r->x0 = 0.0f;
3948
    r->x1 = size.x;
3949
    r->baseline_y_delta = size.y;
3950
    r->ymin = 0.0f;
3951
    r->ymax = size.y;
3952
    r->num_chars = (int)(text_remaining - (text + line_start_idx));
3953
}
3954
3955
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX  IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL
3956
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX  IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL
3957
3958
static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
3959
{
3960
    if (idx >= obj->TextLen)
3961
        return obj->TextLen + 1;
3962
    unsigned int c;
3963
    return idx + ImTextCharFromUtf8(&c, obj->TextA.Data + idx, obj->TextA.Data + obj->TextLen);
3964
}
3965
3966
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
3967
{
3968
    if (idx <= 0)
3969
        return -1;
3970
    const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, obj->TextA.Data + idx);
3971
    return (int)(p - obj->TextA.Data);
3972
}
3973
3974
static bool ImCharIsSeparatorW(unsigned int c)
3975
{
3976
    static const unsigned int separator_list[] =
3977
    {
3978
        ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,
3979
        '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F,
3980
        '\n', '\r',
3981
    };
3982
    for (unsigned int separator : separator_list)
3983
        if (c == separator)
3984
            return true;
3985
    return false;
3986
}
3987
3988
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
3989
{
3990
    // When ImGuiInputTextFlags_Password is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators.
3991
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
3992
        return 0;
3993
3994
    const char* curr_p = obj->TextA.Data + idx;
3995
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
3996
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextA.Data + obj->TextLen);
3997
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextA.Data + obj->TextLen);
3998
3999
    bool prev_white = ImCharIsBlankW(prev_c);
4000
    bool prev_separ = ImCharIsSeparatorW(prev_c);
4001
    bool curr_white = ImCharIsBlankW(curr_c);
4002
    bool curr_separ = ImCharIsSeparatorW(curr_c);
4003
    return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
4004
}
4005
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
4006
{
4007
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
4008
        return 0;
4009
4010
    const char* curr_p = obj->TextA.Data + idx;
4011
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextA.Data, curr_p);
4012
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextA.Data + obj->TextLen);
4013
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextA.Data + obj->TextLen);
4014
4015
    bool prev_white = ImCharIsBlankW(prev_c);
4016
    bool prev_separ = ImCharIsSeparatorW(prev_c);
4017
    bool curr_white = ImCharIsBlankW(curr_c);
4018
    bool curr_separ = ImCharIsSeparatorW(curr_c);
4019
    return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
4020
}
4021
static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)
4022
{
4023
    idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
4024
    while (idx >= 0 && !is_word_boundary_from_right(obj, idx))
4025
        idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
4026
    return idx < 0 ? 0 : idx;
4027
}
4028
static int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx)
4029
{
4030
    int len = obj->TextLen;
4031
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4032
    while (idx < len && !is_word_boundary_from_left(obj, idx))
4033
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4034
    return idx > len ? len : idx;
4035
}
4036
static int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx)
4037
{
4038
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4039
    int len = obj->TextLen;
4040
    while (idx < len && !is_word_boundary_from_right(obj, idx))
4041
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4042
    return idx > len ? len : idx;
4043
}
4044
static int  STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx)  { ImGuiContext& g = *obj->Ctx; if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
4045
#define STB_TEXTEDIT_MOVEWORDLEFT       STB_TEXTEDIT_MOVEWORDLEFT_IMPL  // They need to be #define for stb_textedit.h
4046
#define STB_TEXTEDIT_MOVEWORDRIGHT      STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
4047
4048
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
4049
{
4050
    char* dst = obj->TextA.Data + pos;
4051
4052
    obj->Edited = true;
4053
    obj->TextLen -= n;
4054
4055
    // Offset remaining text (FIXME-OPT: Use memmove)
4056
    const char* src = obj->TextA.Data + pos + n;
4057
    while (char c = *src++)
4058
        *dst++ = c;
4059
    *dst = '\0';
4060
}
4061
4062
static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)
4063
{
4064
    const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4065
    const int text_len = obj->TextLen;
4066
    IM_ASSERT(pos <= text_len);
4067
4068
    if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))
4069
        return false;
4070
4071
    // Grow internal buffer if needed
4072
    if (new_text_len + text_len + 1 > obj->TextA.Size)
4073
    {
4074
        if (!is_resizable)
4075
            return false;
4076
        obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
4077
    }
4078
4079
    char* text = obj->TextA.Data;
4080
    if (pos != text_len)
4081
        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));
4082
    memcpy(text + pos, new_text, (size_t)new_text_len);
4083
4084
    obj->Edited = true;
4085
    obj->TextLen += new_text_len;
4086
    obj->TextA[obj->TextLen] = '\0';
4087
4088
    return true;
4089
}
4090
4091
// 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)
4092
#define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
4093
#define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
4094
#define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
4095
#define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
4096
#define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
4097
#define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
4098
#define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
4099
#define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
4100
#define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
4101
#define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
4102
#define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
4103
#define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
4104
#define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
4105
#define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
4106
#define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
4107
#define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
4108
#define STB_TEXTEDIT_K_SHIFT        0x400000
4109
4110
#define IMSTB_TEXTEDIT_IMPLEMENTATION
4111
#define IMSTB_TEXTEDIT_memmove memmove
4112
#include "imstb_textedit.h"
4113
4114
// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
4115
// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
4116
static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
4117
{
4118
    stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len);
4119
    ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen);
4120
    state->cursor = state->select_start = state->select_end = 0;
4121
    if (text_len <= 0)
4122
        return;
4123
    if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
4124
    {
4125
        state->cursor = state->select_start = state->select_end = text_len;
4126
        state->has_preferred_x = 0;
4127
        return;
4128
    }
4129
    IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
4130
}
4131
4132
} // namespace ImStb
4133
4134
// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators.
4135
ImGuiInputTextState::ImGuiInputTextState()
4136
{
4137
    memset(this, 0, sizeof(*this));
4138
    Stb = IM_NEW(ImStbTexteditState);
4139
    memset(Stb, 0, sizeof(*Stb));
4140
}
4141
4142
ImGuiInputTextState::~ImGuiInputTextState()
4143
{
4144
    IM_DELETE(Stb);
4145
}
4146
4147
void ImGuiInputTextState::OnKeyPressed(int key)
4148
{
4149
    stb_textedit_key(this, Stb, key);
4150
    CursorFollow = true;
4151
    CursorAnimReset();
4152
}
4153
4154
void ImGuiInputTextState::OnCharPressed(unsigned int c)
4155
{
4156
    // Convert the key to a UTF8 byte sequence.
4157
    // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
4158
    char utf8[5];
4159
    ImTextCharToUtf8(utf8, c);
4160
    stb_textedit_text(this, Stb, utf8, (int)strlen(utf8));
4161
    CursorFollow = true;
4162
    CursorAnimReset();
4163
}
4164
4165
// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header.
4166
void ImGuiInputTextState::CursorAnimReset()                 { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
4167
void ImGuiInputTextState::CursorClamp()                     { Stb->cursor = ImMin(Stb->cursor, TextLen); Stb->select_start = ImMin(Stb->select_start, TextLen); Stb->select_end = ImMin(Stb->select_end, TextLen); }
4168
bool ImGuiInputTextState::HasSelection() const              { return Stb->select_start != Stb->select_end; }
4169
void ImGuiInputTextState::ClearSelection()                  { Stb->select_start = Stb->select_end = Stb->cursor; }
4170
int  ImGuiInputTextState::GetCursorPos() const              { return Stb->cursor; }
4171
int  ImGuiInputTextState::GetSelectionStart() const         { return Stb->select_start; }
4172
int  ImGuiInputTextState::GetSelectionEnd() const           { return Stb->select_end; }
4173
void ImGuiInputTextState::SelectAll()                       { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
4174
void ImGuiInputTextState::ReloadUserBufAndSelectAll()       { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
4175
void ImGuiInputTextState::ReloadUserBufAndKeepSelection()   { ReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
4176
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd()       { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
4177
4178
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
4179
{
4180
    memset(this, 0, sizeof(*this));
4181
}
4182
4183
// Public API to manipulate UTF-8 text
4184
// We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
4185
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
4186
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
4187
{
4188
    IM_ASSERT(pos + bytes_count <= BufTextLen);
4189
    char* dst = Buf + pos;
4190
    const char* src = Buf + pos + bytes_count;
4191
    while (char c = *src++)
4192
        *dst++ = c;
4193
    *dst = '\0';
4194
4195
    if (CursorPos >= pos + bytes_count)
4196
        CursorPos -= bytes_count;
4197
    else if (CursorPos >= pos)
4198
        CursorPos = pos;
4199
    SelectionStart = SelectionEnd = CursorPos;
4200
    BufDirty = true;
4201
    BufTextLen -= bytes_count;
4202
}
4203
4204
void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
4205
{
4206
    // Accept null ranges
4207
    if (new_text == new_text_end)
4208
        return;
4209
4210
    // Grow internal buffer if needed
4211
    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4212
    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
4213
    if (new_text_len + BufTextLen >= BufSize)
4214
    {
4215
        if (!is_resizable)
4216
            return;
4217
4218
        // Contrary to STB_TEXTEDIT_INSERTCHARS() this is working in the UTF8 buffer, hence the mildly similar code (until we remove the U16 buffer altogether!)
4219
        ImGuiContext& g = *Ctx;
4220
        ImGuiInputTextState* edit_state = &g.InputTextState;
4221
        IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
4222
        IM_ASSERT(Buf == edit_state->TextA.Data);
4223
        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
4224
        edit_state->TextA.resize(new_buf_size + 1);
4225
        Buf = edit_state->TextA.Data;
4226
        BufSize = edit_state->BufCapacity = new_buf_size;
4227
    }
4228
4229
    if (BufTextLen != pos)
4230
        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
4231
    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
4232
    Buf[BufTextLen + new_text_len] = '\0';
4233
4234
    if (CursorPos >= pos)
4235
        CursorPos += new_text_len;
4236
    SelectionStart = SelectionEnd = CursorPos;
4237
    BufDirty = true;
4238
    BufTextLen += new_text_len;
4239
}
4240
4241
// Return false to discard a character.
4242
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
4243
{
4244
    unsigned int c = *p_char;
4245
4246
    // Filter non-printable (NB: isprint is unreliable! see #2467)
4247
    bool apply_named_filters = true;
4248
    if (c < 0x20)
4249
    {
4250
        bool pass = false;
4251
        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0; // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
4252
        pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
4253
        if (!pass)
4254
            return false;
4255
        apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
4256
    }
4257
4258
    if (input_source_is_clipboard == false)
4259
    {
4260
        // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
4261
        if (c == 127)
4262
            return false;
4263
4264
        // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
4265
        if (c >= 0xE000 && c <= 0xF8FF)
4266
            return false;
4267
    }
4268
4269
    // Filter Unicode ranges we are not handling in this build
4270
    if (c > IM_UNICODE_CODEPOINT_MAX)
4271
        return false;
4272
4273
    // Generic named filters
4274
    if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
4275
    {
4276
        // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'.
4277
        // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
4278
        // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point.
4279
        // Change the default decimal_point with:
4280
        //   ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;
4281
        // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions.
4282
        ImGuiContext& g = *ctx;
4283
        const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;
4284
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
4285
            if (c == '.' || c == ',')
4286
                c = c_decimal_point;
4287
4288
        // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
4289
        // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
4290
        // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
4291
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
4292
            if (c >= 0xFF01 && c <= 0xFF5E)
4293
                c = c - 0xFF01 + 0x21;
4294
4295
        // Allow 0-9 . - + * /
4296
        if (flags & ImGuiInputTextFlags_CharsDecimal)
4297
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
4298
                return false;
4299
4300
        // Allow 0-9 . - + * / e E
4301
        if (flags & ImGuiInputTextFlags_CharsScientific)
4302
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
4303
                return false;
4304
4305
        // Allow 0-9 a-F A-F
4306
        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
4307
            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
4308
                return false;
4309
4310
        // Turn a-z into A-Z
4311
        if (flags & ImGuiInputTextFlags_CharsUppercase)
4312
            if (c >= 'a' && c <= 'z')
4313
                c += (unsigned int)('A' - 'a');
4314
4315
        if (flags & ImGuiInputTextFlags_CharsNoBlank)
4316
            if (ImCharIsBlankW(c))
4317
                return false;
4318
4319
        *p_char = c;
4320
    }
4321
4322
    // Custom callback filter
4323
    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
4324
    {
4325
        ImGuiContext& g = *GImGui;
4326
        ImGuiInputTextCallbackData callback_data;
4327
        callback_data.Ctx = &g;
4328
        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
4329
        callback_data.EventChar = (ImWchar)c;
4330
        callback_data.Flags = flags;
4331
        callback_data.UserData = user_data;
4332
        if (callback(&callback_data) != 0)
4333
            return false;
4334
        *p_char = callback_data.EventChar;
4335
        if (!callback_data.EventChar)
4336
            return false;
4337
    }
4338
4339
    return true;
4340
}
4341
4342
// Find the shortest single replacement we can make to get the new text from the old text.
4343
// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end.
4344
// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly.
4345
static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a)
4346
{
4347
    const char* old_buf = state->CallbackTextBackup.Data;
4348
    const int old_length = state->CallbackTextBackup.Size - 1;
4349
4350
    const int shorter_length = ImMin(old_length, new_length_a);
4351
    int first_diff;
4352
    for (first_diff = 0; first_diff < shorter_length; first_diff++)
4353
        if (old_buf[first_diff] != new_buf_a[first_diff])
4354
            break;
4355
    if (first_diff == old_length && first_diff == new_length_a)
4356
        return;
4357
4358
    int old_last_diff = old_length   - 1;
4359
    int new_last_diff = new_length_a - 1;
4360
    for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
4361
        if (old_buf[old_last_diff] != new_buf_a[new_last_diff])
4362
            break;
4363
4364
    const int insert_len = new_last_diff - first_diff + 1;
4365
    const int delete_len = old_last_diff - first_diff + 1;
4366
    if (insert_len > 0 || delete_len > 0)
4367
        if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len))
4368
            for (int i = 0; i < delete_len; i++)
4369
                p[i] = old_buf[first_diff + i];
4370
}
4371
4372
// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
4373
// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
4374
// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
4375
// but that more likely be attractive when we do have _NoLiveEdit flag available.
4376
void ImGui::InputTextDeactivateHook(ImGuiID id)
4377
{
4378
    ImGuiContext& g = *GImGui;
4379
    ImGuiInputTextState* state = &g.InputTextState;
4380
    if (id == 0 || state->ID != id)
4381
        return;
4382
    g.InputTextDeactivatedState.ID = state->ID;
4383
    if (state->Flags & ImGuiInputTextFlags_ReadOnly)
4384
    {
4385
        g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
4386
    }
4387
    else
4388
    {
4389
        IM_ASSERT(state->TextA.Data != 0);
4390
        g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1);
4391
        memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1);
4392
    }
4393
}
4394
4395
// Edit a string of text
4396
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
4397
//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
4398
//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
4399
// - 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.
4400
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
4401
// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
4402
//  doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
4403
bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data)
4404
{
4405
    ImGuiWindow* window = GetCurrentWindow();
4406
    if (window->SkipItems)
4407
        return false;
4408
4409
    IM_ASSERT(buf != NULL && buf_size >= 0);
4410
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
4411
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
4412
4413
    ImGuiContext& g = *GImGui;
4414
    ImGuiIO& io = g.IO;
4415
    const ImGuiStyle& style = g.Style;
4416
4417
    const bool RENDER_SELECTION_WHEN_INACTIVE = false;
4418
    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
4419
4420
    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
4421
        BeginGroup();
4422
    const ImGuiID id = window->GetID(label);
4423
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
4424
    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? g.FontSize * 8.0f : label_size.y) + style.FramePadding.y * 2.0f); // Arbitrary default of 8 lines high for multi-line
4425
    const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
4426
4427
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
4428
    const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
4429
4430
    ImGuiWindow* draw_window = window;
4431
    ImVec2 inner_size = frame_size;
4432
    ImGuiLastItemData item_data_backup;
4433
    if (is_multiline)
4434
    {
4435
        ImVec2 backup_pos = window->DC.CursorPos;
4436
        ItemSize(total_bb, style.FramePadding.y);
4437
        if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
4438
        {
4439
            EndGroup();
4440
            return false;
4441
        }
4442
        item_data_backup = g.LastItemData;
4443
        window->DC.CursorPos = backup_pos;
4444
4445
        // Prevent NavActivation from Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
4446
        if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && (flags & ImGuiInputTextFlags_AllowTabInput))
4447
            g.NavActivateId = 0;
4448
4449
        // Prevent NavActivate reactivating in BeginChild() when we are already active.
4450
        const ImGuiID backup_activate_id = g.NavActivateId;
4451
        if (g.ActiveId == id) // Prevent reactivation
4452
            g.NavActivateId = 0;
4453
4454
        // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
4455
        PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
4456
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
4457
        PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
4458
        PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
4459
        bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);
4460
        g.NavActivateId = backup_activate_id;
4461
        PopStyleVar(3);
4462
        PopStyleColor();
4463
        if (!child_visible)
4464
        {
4465
            EndChild();
4466
            EndGroup();
4467
            return false;
4468
        }
4469
        draw_window = g.CurrentWindow; // Child window
4470
        draw_window->DC.NavLayersActiveMaskNext |= (1 << draw_window->DC.NavLayerCurrent); // This is to ensure that EndChild() will display a navigation highlight so we can "enter" into it.
4471
        draw_window->DC.CursorPos += style.FramePadding;
4472
        inner_size.x -= draw_window->ScrollbarSizes.x;
4473
    }
4474
    else
4475
    {
4476
        // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
4477
        ItemSize(total_bb, style.FramePadding.y);
4478
        if (!(flags & ImGuiInputTextFlags_MergedItem))
4479
            if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
4480
                return false;
4481
    }
4482
4483
    // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)
4484
    bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);
4485
    if (hovered)
4486
        SetMouseCursor(ImGuiMouseCursor_TextInput);
4487
    if (hovered && g.NavHighlightItemUnderNav)
4488
        hovered = false;
4489
4490
    // We are only allowed to access the state if we are already the active widget.
4491
    ImGuiInputTextState* state = GetInputTextState(id);
4492
4493
    if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)
4494
        flags |= ImGuiInputTextFlags_ReadOnly;
4495
    const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
4496
    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
4497
    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
4498
    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
4499
    if (is_resizable)
4500
        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
4501
4502
    const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
4503
4504
    const bool user_clicked = hovered && io.MouseClicked[0];
4505
    const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4506
    const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
4507
    bool clear_active_id = false;
4508
    bool select_all = false;
4509
4510
    float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
4511
4512
    const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf);
4513
    const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
4514
    const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
4515
    const bool init_state = (init_make_active || user_scroll_active);
4516
    if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf)
4517
    {
4518
        // Access state even if we don't own it yet.
4519
        state = &g.InputTextState;
4520
        state->CursorAnimReset();
4521
        state->ReloadUserBuf = false;
4522
4523
        // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714)
4524
        InputTextDeactivateHook(state->ID);
4525
4526
        // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
4527
        const int buf_len = (int)strlen(buf);
4528
        if (!init_reload_from_user_buf)
4529
        {
4530
            // Take a copy of the initial buffer value.
4531
            state->TextToRevertTo.resize(buf_len + 1);    // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string.
4532
            memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
4533
        }
4534
4535
        // Preserve cursor position and undo/redo stack if we come back to same widget
4536
        // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
4537
        bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf);
4538
        if (recycle_state && (state->TextLen != buf_len || (strncmp(state->TextA.Data, buf, buf_len) != 0)))
4539
            recycle_state = false;
4540
4541
        // Start edition
4542
        state->ID = id;
4543
        state->TextA.resize(buf_size + 1);          // we use +1 to make sure that .Data is always pointing to at least an empty string.
4544
        state->TextLen = (int)strlen(buf);
4545
        memcpy(state->TextA.Data, buf, state->TextLen + 1);
4546
4547
        if (recycle_state)
4548
        {
4549
            // Recycle existing cursor/selection/undo stack but clamp position
4550
            // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
4551
            state->CursorClamp();
4552
        }
4553
        else
4554
        {
4555
            state->Scroll = ImVec2(0.0f, 0.0f);
4556
            stb_textedit_initialize_state(state->Stb, !is_multiline);
4557
        }
4558
4559
        if (init_reload_from_user_buf)
4560
        {
4561
            state->Stb->select_start = state->ReloadSelectionStart;
4562
            state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;
4563
            state->CursorClamp();
4564
        }
4565
        else if (!is_multiline)
4566
        {
4567
            if (flags & ImGuiInputTextFlags_AutoSelectAll)
4568
                select_all = true;
4569
            if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
4570
                select_all = true;
4571
            if (user_clicked && io.KeyCtrl)
4572
                select_all = true;
4573
        }
4574
4575
        if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
4576
            state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)
4577
    }
4578
4579
    const bool is_osx = io.ConfigMacOSXBehaviors;
4580
    if (g.ActiveId != id && init_make_active)
4581
    {
4582
        IM_ASSERT(state && state->ID == id);
4583
        SetActiveID(id, window);
4584
        SetFocusID(id, window);
4585
        FocusWindow(window);
4586
    }
4587
    if (g.ActiveId == id)
4588
    {
4589
        // Declare some inputs, the other are registered and polled via Shortcut() routing system.
4590
        // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts.
4591
        const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };
4592
        for (ImGuiKey key : always_owned_keys)
4593
            SetKeyOwner(key, id);
4594
        if (user_clicked)
4595
            SetKeyOwner(ImGuiKey_MouseLeft, id);
4596
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
4597
        if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
4598
        {
4599
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
4600
            SetKeyOwner(ImGuiKey_UpArrow, id);
4601
            SetKeyOwner(ImGuiKey_DownArrow, id);
4602
        }
4603
        if (is_multiline)
4604
        {
4605
            SetKeyOwner(ImGuiKey_PageUp, id);
4606
            SetKeyOwner(ImGuiKey_PageDown, id);
4607
        }
4608
        // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu
4609
        if (is_osx)
4610
            SetKeyOwner(ImGuiMod_Alt, id);
4611
4612
        // Expose scroll in a manner that is agnostic to us using a child window
4613
        if (is_multiline && state != NULL)
4614
            state->Scroll.y = draw_window->Scroll.y;
4615
    }
4616
4617
    // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
4618
    if (g.ActiveId == id && state == NULL)
4619
        ClearActiveID();
4620
4621
    // Release focus when we click outside
4622
    if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
4623
        clear_active_id = true;
4624
4625
    // Lock the decision of whether we are going to take the path displaying the cursor or selection
4626
    bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
4627
    bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4628
    bool value_changed = false;
4629
    bool validated = false;
4630
4631
    // Select the buffer to render.
4632
    const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
4633
    const bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4634
4635
    // Password pushes a temporary font with only a fallback glyph
4636
    if (is_password && !is_displaying_hint)
4637
    {
4638
        const ImFontGlyph* glyph = g.Font->FindGlyph('*');
4639
        ImFont* password_font = &g.InputTextPasswordFont;
4640
        password_font->FontSize = g.Font->FontSize;
4641
        password_font->Scale = g.Font->Scale;
4642
        password_font->Ascent = g.Font->Ascent;
4643
        password_font->Descent = g.Font->Descent;
4644
        password_font->ContainerAtlas = g.Font->ContainerAtlas;
4645
        password_font->FallbackGlyph = glyph;
4646
        password_font->FallbackAdvanceX = glyph->AdvanceX;
4647
        IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
4648
        PushFont(password_font);
4649
    }
4650
4651
    // Process mouse inputs and character inputs
4652
    if (g.ActiveId == id)
4653
    {
4654
        IM_ASSERT(state != NULL);
4655
        state->Edited = false;
4656
        state->BufCapacity = buf_size;
4657
        state->Flags = flags;
4658
4659
        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
4660
        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
4661
        g.ActiveIdAllowOverlap = !io.MouseDown[0];
4662
4663
        // Edit in progress
4664
        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x;
4665
        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
4666
4667
        if (select_all)
4668
        {
4669
            state->SelectAll();
4670
            state->SelectedAllMouseLock = true;
4671
        }
4672
        else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
4673
        {
4674
            stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
4675
            const int multiclick_count = (io.MouseClickedCount[0] - 2);
4676
            if ((multiclick_count % 2) == 0)
4677
            {
4678
                // Double-click: Select word
4679
                // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant:
4680
                // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
4681
                const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n';
4682
                if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)
4683
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
4684
                //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4685
                if (!STB_TEXT_HAS_SELECTION(state->Stb))
4686
                    ImStb::stb_textedit_prep_selection_at_cursor(state->Stb);
4687
                state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);
4688
                state->Stb->select_end = state->Stb->cursor;
4689
                ImStb::stb_textedit_clamp(state, state->Stb);
4690
            }
4691
            else
4692
            {
4693
                // Triple-click: Select line
4694
                const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n';
4695
                state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
4696
                state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
4697
                state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
4698
                if (!is_eol && is_multiline)
4699
                {
4700
                    ImSwap(state->Stb->select_start, state->Stb->select_end);
4701
                    state->Stb->cursor = state->Stb->select_end;
4702
                }
4703
                state->CursorFollow = false;
4704
            }
4705
            state->CursorAnimReset();
4706
        }
4707
        else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
4708
        {
4709
            if (hovered)
4710
            {
4711
                if (io.KeyShift)
4712
                    stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
4713
                else
4714
                    stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
4715
                state->CursorAnimReset();
4716
            }
4717
        }
4718
        else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
4719
        {
4720
            stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
4721
            state->CursorAnimReset();
4722
            state->CursorFollow = true;
4723
        }
4724
        if (state->SelectedAllMouseLock && !io.MouseDown[0])
4725
            state->SelectedAllMouseLock = false;
4726
4727
        // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
4728
        // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
4729
        if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
4730
        {
4731
            if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
4732
            {
4733
                unsigned int c = '\t'; // Insert TAB
4734
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
4735
                    state->OnCharPressed(c);
4736
            }
4737
            // FIXME: Implement Shift+Tab
4738
            /*
4739
            if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))
4740
            {
4741
            }
4742
            */
4743
        }
4744
4745
        // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
4746
        // 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.
4747
        const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
4748
        if (io.InputQueueCharacters.Size > 0)
4749
        {
4750
            if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
4751
                for (int n = 0; n < io.InputQueueCharacters.Size; n++)
4752
                {
4753
                    // Insert character if they pass filtering
4754
                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];
4755
                    if (c == '\t') // Skip Tab, see above.
4756
                        continue;
4757
                    if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
4758
                        state->OnCharPressed(c);
4759
                }
4760
4761
            // Consume characters
4762
            io.InputQueueCharacters.resize(0);
4763
        }
4764
    }
4765
4766
    // Process other shortcuts/key-presses
4767
    bool revert_edit = false;
4768
    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
4769
    {
4770
        IM_ASSERT(state != NULL);
4771
4772
        const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
4773
        state->Stb->row_count_per_page = row_count_per_page;
4774
4775
        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
4776
        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
4777
        const bool is_startend_key_down = is_osx && io.KeyCtrl && !io.KeySuper && !io.KeyAlt;  // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
4778
4779
        // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText)
4780
        // Otherwise we could simply assume that we own the keys as we are active.
4781
        const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
4782
        const bool is_cut   = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_X, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Delete, f_repeat, id)) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
4783
        const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());
4784
        const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
4785
        const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
4786
        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || (is_osx && Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id))) && !is_readonly && is_undoable;
4787
        const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
4788
4789
        // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
4790
        const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
4791
        const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
4792
        const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
4793
        const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));
4794
4795
        // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.
4796
        // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line.
4797
        if (IsKeyPressed(ImGuiKey_LeftArrow))                        { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
4798
        else if (IsKeyPressed(ImGuiKey_RightArrow))                  { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
4799
        else if (IsKeyPressed(ImGuiKey_UpArrow) && is_multiline)     { if (io.KeyCtrl) SetScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
4800
        else if (IsKeyPressed(ImGuiKey_DownArrow) && is_multiline)   { if (io.KeyCtrl) SetScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
4801
        else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
4802
        else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
4803
        else if (IsKeyPressed(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
4804
        else if (IsKeyPressed(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
4805
        else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
4806
        {
4807
            if (!state->HasSelection())
4808
            {
4809
                // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace)
4810
                if (is_wordmove_key_down)
4811
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
4812
            }
4813
            state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
4814
        }
4815
        else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
4816
        {
4817
            if (!state->HasSelection())
4818
            {
4819
                if (is_wordmove_key_down)
4820
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
4821
                else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
4822
                    state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
4823
            }
4824
            state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
4825
        }
4826
        else if (is_enter_pressed || is_gamepad_validate)
4827
        {
4828
            // Determine if we turn Enter into a \n character
4829
            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
4830
            if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
4831
            {
4832
                validated = true;
4833
                if (io.ConfigInputTextEnterKeepActive && !is_multiline)
4834
                    state->SelectAll(); // No need to scroll
4835
                else
4836
                    clear_active_id = true;
4837
            }
4838
            else if (!is_readonly)
4839
            {
4840
                unsigned int c = '\n'; // Insert new line
4841
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
4842
                    state->OnCharPressed(c);
4843
            }
4844
        }
4845
        else if (is_cancel)
4846
        {
4847
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
4848
            {
4849
                if (buf[0] != 0)
4850
                {
4851
                    revert_edit = true;
4852
                }
4853
                else
4854
                {
4855
                    render_cursor = render_selection = false;
4856
                    clear_active_id = true;
4857
                }
4858
            }
4859
            else
4860
            {
4861
                clear_active_id = revert_edit = true;
4862
                render_cursor = render_selection = false;
4863
            }
4864
        }
4865
        else if (is_undo || is_redo)
4866
        {
4867
            state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
4868
            state->ClearSelection();
4869
        }
4870
        else if (is_select_all)
4871
        {
4872
            state->SelectAll();
4873
            state->CursorFollow = true;
4874
        }
4875
        else if (is_cut || is_copy)
4876
        {
4877
            // Cut, Copy
4878
            if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
4879
            {
4880
                const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
4881
                const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;
4882
4883
                char backup = state->TextA.Data[ie];
4884
                state->TextA.Data[ie] = 0; // A bit of a hack since SetClipboardText only takes null terminated strings
4885
                SetClipboardText(state->TextA.Data + ib);
4886
                state->TextA.Data[ie] = backup;
4887
            }
4888
            if (is_cut)
4889
            {
4890
                if (!state->HasSelection())
4891
                    state->SelectAll();
4892
                state->CursorFollow = true;
4893
                stb_textedit_cut(state, state->Stb);
4894
            }
4895
        }
4896
        else if (is_paste)
4897
        {
4898
            if (const char* clipboard = GetClipboardText())
4899
            {
4900
                // Filter pasted buffer
4901
                const int clipboard_len = (int)strlen(clipboard);
4902
                char* clipboard_filtered = (char*)IM_ALLOC(clipboard_len + 1);
4903
                int clipboard_filtered_len = 0;
4904
                for (const char* s = clipboard; *s != 0; )
4905
                {
4906
                    unsigned int c;
4907
                    int len = ImTextCharFromUtf8(&c, s, NULL);
4908
                    s += len;
4909
                    if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
4910
                        continue;
4911
                    memcpy(clipboard_filtered + clipboard_filtered_len, s - len, len);
4912
                    clipboard_filtered_len += len;
4913
                }
4914
                clipboard_filtered[clipboard_filtered_len] = 0;
4915
                if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
4916
                {
4917
                    stb_textedit_paste(state, state->Stb, clipboard_filtered, clipboard_filtered_len);
4918
                    state->CursorFollow = true;
4919
                }
4920
                MemFree(clipboard_filtered);
4921
            }
4922
        }
4923
4924
        // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
4925
        render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4926
    }
4927
4928
    // Process callbacks and apply result back to user's buffer.
4929
    const char* apply_new_text = NULL;
4930
    int apply_new_text_length = 0;
4931
    if (g.ActiveId == id)
4932
    {
4933
        IM_ASSERT(state != NULL);
4934
        if (revert_edit && !is_readonly)
4935
        {
4936
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
4937
            {
4938
                // Clear input
4939
                IM_ASSERT(buf[0] != 0);
4940
                apply_new_text = "";
4941
                apply_new_text_length = 0;
4942
                value_changed = true;
4943
                IMSTB_TEXTEDIT_CHARTYPE empty_string;
4944
                stb_textedit_replace(state, state->Stb, &empty_string, 0);
4945
            }
4946
            else if (strcmp(buf, state->TextToRevertTo.Data) != 0)
4947
            {
4948
                apply_new_text = state->TextToRevertTo.Data;
4949
                apply_new_text_length = state->TextToRevertTo.Size - 1;
4950
4951
                // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
4952
                // Push records into the undo stack so we can CTRL+Z the revert operation itself
4953
                value_changed = true;
4954
                stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1);
4955
            }
4956
        }
4957
4958
        // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer
4959
        // before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
4960
        // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
4961
        // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage
4962
        // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object
4963
        // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
4964
        const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
4965
        if (apply_edit_back_to_user_buffer)
4966
        {
4967
            // Apply new value immediately - copy modified buffer back
4968
            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
4969
            // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
4970
            // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
4971
4972
            // User callback
4973
            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
4974
            {
4975
                IM_ASSERT(callback != NULL);
4976
4977
                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
4978
                ImGuiInputTextFlags event_flag = 0;
4979
                ImGuiKey event_key = ImGuiKey_None;
4980
                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
4981
                {
4982
                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
4983
                    event_key = ImGuiKey_Tab;
4984
                }
4985
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
4986
                {
4987
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
4988
                    event_key = ImGuiKey_UpArrow;
4989
                }
4990
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
4991
                {
4992
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
4993
                    event_key = ImGuiKey_DownArrow;
4994
                }
4995
                else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
4996
                {
4997
                    event_flag = ImGuiInputTextFlags_CallbackEdit;
4998
                }
4999
                else if (flags & ImGuiInputTextFlags_CallbackAlways)
5000
                {
5001
                    event_flag = ImGuiInputTextFlags_CallbackAlways;
5002
                }
5003
5004
                if (event_flag)
5005
                {
5006
                    ImGuiInputTextCallbackData callback_data;
5007
                    callback_data.Ctx = &g;
5008
                    callback_data.EventFlag = event_flag;
5009
                    callback_data.Flags = flags;
5010
                    callback_data.UserData = callback_user_data;
5011
5012
                    // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
5013
                    state->CallbackTextBackup.resize(state->TextLen + 1);
5014
                    memcpy(state->CallbackTextBackup.Data, state->TextA.Data, state->TextLen + 1);
5015
5016
                    char* callback_buf = is_readonly ? buf : state->TextA.Data;
5017
                    callback_data.EventKey = event_key;
5018
                    callback_data.Buf = callback_buf;
5019
                    callback_data.BufTextLen = state->TextLen;
5020
                    callback_data.BufSize = state->BufCapacity;
5021
                    callback_data.BufDirty = false;
5022
5023
                    const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor;
5024
                    const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start;
5025
                    const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end;
5026
5027
                    // Call user code
5028
                    callback(&callback_data);
5029
5030
                    // Read back what user may have modified
5031
                    callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
5032
                    IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields
5033
                    IM_ASSERT(callback_data.BufSize == state->BufCapacity);
5034
                    IM_ASSERT(callback_data.Flags == flags);
5035
                    const bool buf_dirty = callback_data.BufDirty;
5036
                    if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty)            { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; }
5037
                    if (callback_data.SelectionStart != utf8_selection_start || buf_dirty)  { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; }
5038
                    if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty)      { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; }
5039
                    if (buf_dirty)
5040
                    {
5041
                        // Callback may update buffer and thus set buf_dirty even in read-only mode.
5042
                        IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
5043
                        InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
5044
                        state->TextLen = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
5045
                        state->CursorAnimReset();
5046
                    }
5047
                }
5048
            }
5049
5050
            // Will copy result string if modified
5051
            if (!is_readonly && strcmp(state->TextA.Data, buf) != 0)
5052
            {
5053
                apply_new_text = state->TextA.Data;
5054
                apply_new_text_length = state->TextLen;
5055
                value_changed = true;
5056
            }
5057
        }
5058
    }
5059
5060
    // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
5061
    if (g.InputTextDeactivatedState.ID == id)
5062
    {
5063
        if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
5064
        {
5065
            apply_new_text = g.InputTextDeactivatedState.TextA.Data;
5066
            apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
5067
            value_changed = true;
5068
            //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
5069
        }
5070
        g.InputTextDeactivatedState.ID = 0;
5071
    }
5072
5073
    // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
5074
    if (apply_new_text != NULL)
5075
    {
5076
        //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
5077
        //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
5078
        //// without any storage on user's side.
5079
        IM_ASSERT(apply_new_text_length >= 0);
5080
        if (is_resizable)
5081
        {
5082
            ImGuiInputTextCallbackData callback_data;
5083
            callback_data.Ctx = &g;
5084
            callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
5085
            callback_data.Flags = flags;
5086
            callback_data.Buf = buf;
5087
            callback_data.BufTextLen = apply_new_text_length;
5088
            callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
5089
            callback_data.UserData = callback_user_data;
5090
            callback(&callback_data);
5091
            buf = callback_data.Buf;
5092
            buf_size = callback_data.BufSize;
5093
            apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
5094
            IM_ASSERT(apply_new_text_length <= buf_size);
5095
        }
5096
        //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
5097
5098
        // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
5099
        ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
5100
    }
5101
5102
    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
5103
    // Otherwise request text input ahead for next frame.
5104
    if (g.ActiveId == id && clear_active_id)
5105
        ClearActiveID();
5106
    else if (g.ActiveId == id)
5107
        g.WantTextInputNextFrame = 1;
5108
5109
    // Render frame
5110
    if (!is_multiline)
5111
    {
5112
        RenderNavCursor(frame_bb, id);
5113
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
5114
    }
5115
5116
    const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + inner_size.x, frame_bb.Min.y + inner_size.y); // Not using frame_bb.Max because we have adjusted size
5117
    ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
5118
    ImVec2 text_size(0.0f, 0.0f);
5119
5120
    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
5121
    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
5122
    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
5123
    const int buf_display_max_length = 2 * 1024 * 1024;
5124
    const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
5125
    const char* buf_display_end = NULL; // We have specialized paths below for setting the length
5126
    if (is_displaying_hint)
5127
    {
5128
        buf_display = hint;
5129
        buf_display_end = hint + strlen(hint);
5130
    }
5131
5132
    // Render text. We currently only render selection when the widget is active or while scrolling.
5133
    // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive.
5134
    if (render_cursor || render_selection)
5135
    {
5136
        IM_ASSERT(state != NULL);
5137
        if (!is_displaying_hint)
5138
            buf_display_end = buf_display + state->TextLen;
5139
5140
        // Render text (with cursor and selection)
5141
        // This is going to be messy. We need to:
5142
        // - Display the text (this alone can be more easily clipped)
5143
        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
5144
        // - Measure text height (for scrollbar)
5145
        // 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)
5146
        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
5147
        const char* text_begin = state->TextA.Data;
5148
        const char* text_end = text_begin + state->TextLen;
5149
        ImVec2 cursor_offset, select_start_offset;
5150
5151
        {
5152
            // Find lines numbers straddling cursor and selection min position
5153
            int cursor_line_no = render_cursor ? -1 : -1000;
5154
            int selmin_line_no = render_selection ? -1 : -1000;
5155
            const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL;
5156
            const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL;
5157
5158
            // Count lines and find line number for cursor and selection ends
5159
            int line_count = 1;
5160
            if (is_multiline)
5161
            {
5162
                for (const char* s = text_begin; (s = (const char*)memchr(s, '\n', (size_t)(text_end - s))) != NULL; s++)
5163
                {
5164
                    if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; }
5165
                    if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; }
5166
                    line_count++;
5167
                }
5168
            }
5169
            if (cursor_line_no == -1)
5170
                cursor_line_no = line_count;
5171
            if (selmin_line_no == -1)
5172
                selmin_line_no = line_count;
5173
5174
            // Calculate 2d position by finding the beginning of the line and measuring distance
5175
            cursor_offset.x = InputTextCalcTextSize(&g, ImStrbol(cursor_ptr, text_begin), cursor_ptr).x;
5176
            cursor_offset.y = cursor_line_no * g.FontSize;
5177
            if (selmin_line_no >= 0)
5178
            {
5179
                select_start_offset.x = InputTextCalcTextSize(&g, ImStrbol(selmin_ptr, text_begin), selmin_ptr).x;
5180
                select_start_offset.y = selmin_line_no * g.FontSize;
5181
            }
5182
5183
            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
5184
            if (is_multiline)
5185
                text_size = ImVec2(inner_size.x, line_count * g.FontSize);
5186
        }
5187
5188
        // Scroll
5189
        if (render_cursor && state->CursorFollow)
5190
        {
5191
            // Horizontal scroll in chunks of quarter width
5192
            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
5193
            {
5194
                const float scroll_increment_x = inner_size.x * 0.25f;
5195
                const float visible_width = inner_size.x - style.FramePadding.x;
5196
                if (cursor_offset.x < state->Scroll.x)
5197
                    state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
5198
                else if (cursor_offset.x - visible_width >= state->Scroll.x)
5199
                    state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
5200
            }
5201
            else
5202
            {
5203
                state->Scroll.y = 0.0f;
5204
            }
5205
5206
            // Vertical scroll
5207
            if (is_multiline)
5208
            {
5209
                // Test if cursor is vertically visible
5210
                if (cursor_offset.y - g.FontSize < scroll_y)
5211
                    scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
5212
                else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
5213
                    scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
5214
                const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
5215
                scroll_y = ImClamp(scroll_y, 0.0f, scroll_max_y);
5216
                draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
5217
                draw_window->Scroll.y = scroll_y;
5218
            }
5219
5220
            state->CursorFollow = false;
5221
        }
5222
5223
        // Draw selection
5224
        const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f);
5225
        if (render_selection)
5226
        {
5227
            const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end);
5228
            const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end);
5229
5230
            ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests.
5231
            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.
5232
            float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
5233
            ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll;
5234
            for (const char* p = text_selected_begin; p < text_selected_end; )
5235
            {
5236
                if (rect_pos.y > clip_rect.w + g.FontSize)
5237
                    break;
5238
                if (rect_pos.y < clip_rect.y)
5239
                {
5240
                    p = (const char*)memchr((void*)p, '\n', text_selected_end - p);
5241
                    p = p ? p + 1 : text_selected_end;
5242
                }
5243
                else
5244
                {
5245
                    ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true);
5246
                    if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
5247
                    ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
5248
                    rect.ClipWith(clip_rect);
5249
                    if (rect.Overlaps(clip_rect))
5250
                        draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
5251
                    rect_pos.x = draw_pos.x - draw_scroll.x;
5252
                }
5253
                rect_pos.y += g.FontSize;
5254
            }
5255
        }
5256
5257
        // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash.
5258
        // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it.
5259
        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
5260
        {
5261
            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
5262
            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
5263
        }
5264
5265
        // Draw blinking cursor
5266
        if (render_cursor)
5267
        {
5268
            state->CursorAnim += io.DeltaTime;
5269
            bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
5270
            ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
5271
            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);
5272
            if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
5273
                draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
5274
5275
            // 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.)
5276
            if (!is_readonly)
5277
            {
5278
                g.PlatformImeData.WantVisible = true;
5279
                g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
5280
                g.PlatformImeData.InputLineHeight = g.FontSize;
5281
                g.PlatformImeViewport = window->Viewport->ID;
5282
            }
5283
        }
5284
    }
5285
    else
5286
    {
5287
        // Render text only (no selection, no cursor)
5288
        if (is_multiline)
5289
            text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
5290
        else if (!is_displaying_hint && g.ActiveId == id)
5291
            buf_display_end = buf_display + state->TextLen;
5292
        else if (!is_displaying_hint)
5293
            buf_display_end = buf_display + strlen(buf_display);
5294
5295
        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
5296
        {
5297
            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
5298
            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, col, buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
5299
        }
5300
    }
5301
5302
    if (is_password && !is_displaying_hint)
5303
        PopFont();
5304
5305
    if (is_multiline)
5306
    {
5307
        // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
5308
        Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
5309
        g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
5310
        EndChild();
5311
        item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
5312
5313
        // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active...
5314
        // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
5315
        EndGroup();
5316
        if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y))
5317
        {
5318
            g.LastItemData.ID = id;
5319
            g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
5320
            g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
5321
        }
5322
    }
5323
5324
    // Log as text
5325
    if (g.LogEnabled && (!is_password || is_displaying_hint))
5326
    {
5327
        LogSetNextTextDecoration("{", "}");
5328
        LogRenderedText(&draw_pos, buf_display, buf_display_end);
5329
    }
5330
5331
    if (label_size.x > 0)
5332
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
5333
5334
    if (value_changed)
5335
        MarkItemEdited(id);
5336
5337
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
5338
    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
5339
        return validated;
5340
    else
5341
        return value_changed;
5342
}
5343
5344
void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
5345
{
5346
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5347
    ImGuiContext& g = *GImGui;
5348
    ImStb::STB_TexteditState* stb_state = state->Stb;
5349
    ImStb::StbUndoState* undo_state = &stb_state->undostate;
5350
    Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
5351
    DebugLocateItemOnHover(state->ID);
5352
    Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end);
5353
    Text("BufCapacityA: %d", state->BufCapacity);
5354
    Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity);
5355
    Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
5356
    Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);
5357
    if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state
5358
    {
5359
        PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
5360
        for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
5361
        {
5362
            ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
5363
            const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
5364
            if (undo_rec_type == ' ')
5365
                BeginDisabled();
5366
            const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0;
5367
            const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage;
5368
            Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"",
5369
                undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf_preview_len, buf_preview_str);
5370
            if (undo_rec_type == ' ')
5371
                EndDisabled();
5372
        }
5373
        PopStyleVar();
5374
    }
5375
    EndChild();
5376
#else
5377
    IM_UNUSED(state);
5378
#endif
5379
}
5380
5381
//-------------------------------------------------------------------------
5382
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
5383
//-------------------------------------------------------------------------
5384
// - ColorEdit3()
5385
// - ColorEdit4()
5386
// - ColorPicker3()
5387
// - RenderColorRectWithAlphaCheckerboard() [Internal]
5388
// - ColorPicker4()
5389
// - ColorButton()
5390
// - SetColorEditOptions()
5391
// - ColorTooltip() [Internal]
5392
// - ColorEditOptionsPopup() [Internal]
5393
// - ColorPickerOptionsPopup() [Internal]
5394
//-------------------------------------------------------------------------
5395
5396
bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
5397
{
5398
    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
5399
}
5400
5401
static void ColorEditRestoreH(const float* col, float* H)
5402
{
5403
    ImGuiContext& g = *GImGui;
5404
    IM_ASSERT(g.ColorEditCurrentID != 0);
5405
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5406
        return;
5407
    *H = g.ColorEditSavedHue;
5408
}
5409
5410
// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
5411
// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.
5412
static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
5413
{
5414
    ImGuiContext& g = *GImGui;
5415
    IM_ASSERT(g.ColorEditCurrentID != 0);
5416
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5417
        return;
5418
5419
    // When S == 0, H is undefined.
5420
    // When H == 1 it wraps around to 0.
5421
    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
5422
        *H = g.ColorEditSavedHue;
5423
5424
    // When V == 0, S is undefined.
5425
    if (*V == 0.0f)
5426
        *S = g.ColorEditSavedSat;
5427
}
5428
5429
// Edit colors components (each component in 0.0f..1.0f range).
5430
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5431
// With typical options: Left-click on color 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.
5432
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
5433
{
5434
    ImGuiWindow* window = GetCurrentWindow();
5435
    if (window->SkipItems)
5436
        return false;
5437
5438
    ImGuiContext& g = *GImGui;
5439
    const ImGuiStyle& style = g.Style;
5440
    const float square_sz = GetFrameHeight();
5441
    const char* label_display_end = FindRenderedTextEnd(label);
5442
    float w_full = CalcItemWidth();
5443
    g.NextItemData.ClearFlags();
5444
5445
    BeginGroup();
5446
    PushID(label);
5447
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
5448
    if (set_current_color_edit_id)
5449
        g.ColorEditCurrentID = window->IDStack.back();
5450
5451
    // If we're not showing any slider there's no point in doing any HSV conversions
5452
    const ImGuiColorEditFlags flags_untouched = flags;
5453
    if (flags & ImGuiColorEditFlags_NoInputs)
5454
        flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
5455
5456
    // Context menu: display and modify options (before defaults are applied)
5457
    if (!(flags & ImGuiColorEditFlags_NoOptions))
5458
        ColorEditOptionsPopup(col, flags);
5459
5460
    // Read stored options
5461
    if (!(flags & ImGuiColorEditFlags_DisplayMask_))
5462
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
5463
    if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
5464
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
5465
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
5466
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
5467
    if (!(flags & ImGuiColorEditFlags_InputMask_))
5468
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
5469
    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
5470
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
5471
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
5472
5473
    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
5474
    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
5475
    const int components = alpha ? 4 : 3;
5476
    const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
5477
    const float w_inputs = ImMax(w_full - w_button, 1.0f);
5478
    w_full = w_inputs + w_button;
5479
5480
    // Convert to the formats we need
5481
    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
5482
    if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
5483
        ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
5484
    else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
5485
    {
5486
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
5487
        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
5488
        ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
5489
    }
5490
    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]) };
5491
5492
    bool value_changed = false;
5493
    bool value_changed_as_float = false;
5494
5495
    const ImVec2 pos = window->DC.CursorPos;
5496
    const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
5497
    window->DC.CursorPos.x = pos.x + inputs_offset_x;
5498
5499
    if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5500
    {
5501
        // RGB/HSV 0..255 Sliders
5502
        const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
5503
5504
        const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
5505
        static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
5506
        static const char* fmt_table_int[3][4] =
5507
        {
5508
            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
5509
            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
5510
            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
5511
        };
5512
        static const char* fmt_table_float[3][4] =
5513
        {
5514
            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
5515
            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
5516
            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
5517
        };
5518
        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
5519
5520
        float prev_split = 0.0f;
5521
        for (int n = 0; n < components; n++)
5522
        {
5523
            if (n > 0)
5524
                SameLine(0, style.ItemInnerSpacing.x);
5525
            float next_split = IM_TRUNC(w_items * (n + 1) / components);
5526
            SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
5527
            prev_split = next_split;
5528
5529
            // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
5530
            if (flags & ImGuiColorEditFlags_Float)
5531
            {
5532
                value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
5533
                value_changed_as_float |= value_changed;
5534
            }
5535
            else
5536
            {
5537
                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
5538
            }
5539
            if (!(flags & ImGuiColorEditFlags_NoOptions))
5540
                OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5541
        }
5542
    }
5543
    else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5544
    {
5545
        // RGB Hexadecimal Input
5546
        char buf[64];
5547
        if (alpha)
5548
            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));
5549
        else
5550
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
5551
        SetNextItemWidth(w_inputs);
5552
        if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
5553
        {
5554
            value_changed = true;
5555
            char* p = buf;
5556
            while (*p == '#' || ImCharIsBlankA(*p))
5557
                p++;
5558
            i[0] = i[1] = i[2] = 0;
5559
            i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
5560
            int r;
5561
            if (alpha)
5562
                r = 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)
5563
            else
5564
                r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
5565
            IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
5566
        }
5567
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5568
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5569
    }
5570
5571
    ImGuiWindow* picker_active_window = NULL;
5572
    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
5573
    {
5574
        const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
5575
        window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
5576
5577
        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
5578
        if (ColorButton("##ColorButton", col_v4, flags))
5579
        {
5580
            if (!(flags & ImGuiColorEditFlags_NoPicker))
5581
            {
5582
                // Store current color and open a picker
5583
                g.ColorPickerRef = col_v4;
5584
                OpenPopup("picker");
5585
                SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
5586
            }
5587
        }
5588
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5589
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5590
5591
        if (BeginPopup("picker"))
5592
        {
5593
            if (g.CurrentWindow->BeginCount == 1)
5594
            {
5595
                picker_active_window = g.CurrentWindow;
5596
                if (label != label_display_end)
5597
                {
5598
                    TextEx(label, label_display_end);
5599
                    Spacing();
5600
                }
5601
                ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
5602
                ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
5603
                SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
5604
                value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
5605
            }
5606
            EndPopup();
5607
        }
5608
    }
5609
5610
    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
5611
    {
5612
        // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
5613
        // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.
5614
        SameLine(0.0f, style.ItemInnerSpacing.x);
5615
        window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
5616
        TextEx(label, label_display_end);
5617
    }
5618
5619
    // Convert back
5620
    if (value_changed && picker_active_window == NULL)
5621
    {
5622
        if (!value_changed_as_float)
5623
            for (int n = 0; n < 4; n++)
5624
                f[n] = i[n] / 255.0f;
5625
        if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
5626
        {
5627
            g.ColorEditSavedHue = f[0];
5628
            g.ColorEditSavedSat = f[1];
5629
            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
5630
            g.ColorEditSavedID = g.ColorEditCurrentID;
5631
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
5632
        }
5633
        if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
5634
            ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
5635
5636
        col[0] = f[0];
5637
        col[1] = f[1];
5638
        col[2] = f[2];
5639
        if (alpha)
5640
            col[3] = f[3];
5641
    }
5642
5643
    if (set_current_color_edit_id)
5644
        g.ColorEditCurrentID = 0;
5645
    PopID();
5646
    EndGroup();
5647
5648
    // Drag and Drop Target
5649
    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
5650
    if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
5651
    {
5652
        bool accepted_drag_drop = false;
5653
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
5654
        {
5655
            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
5656
            value_changed = accepted_drag_drop = true;
5657
        }
5658
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
5659
        {
5660
            memcpy((float*)col, payload->Data, sizeof(float) * components);
5661
            value_changed = accepted_drag_drop = true;
5662
        }
5663
5664
        // Drag-drop payloads are always RGB
5665
        if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
5666
            ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
5667
        EndDragDropTarget();
5668
    }
5669
5670
    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
5671
    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
5672
        g.LastItemData.ID = g.ActiveId;
5673
5674
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
5675
        MarkItemEdited(g.LastItemData.ID);
5676
5677
    return value_changed;
5678
}
5679
5680
bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
5681
{
5682
    float col4[4] = { col[0], col[1], col[2], 1.0f };
5683
    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
5684
        return false;
5685
    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
5686
    return true;
5687
}
5688
5689
// Helper for ColorPicker4()
5690
static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
5691
{
5692
    ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
5693
    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(0,0,0,alpha8));
5694
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
5695
    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(0,0,0,alpha8));
5696
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
5697
}
5698
5699
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5700
// (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
5701
// 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..)
5702
// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
5703
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
5704
{
5705
    ImGuiContext& g = *GImGui;
5706
    ImGuiWindow* window = GetCurrentWindow();
5707
    if (window->SkipItems)
5708
        return false;
5709
5710
    ImDrawList* draw_list = window->DrawList;
5711
    ImGuiStyle& style = g.Style;
5712
    ImGuiIO& io = g.IO;
5713
5714
    const float width = CalcItemWidth();
5715
    const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
5716
    g.NextItemData.ClearFlags();
5717
5718
    PushID(label);
5719
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
5720
    if (set_current_color_edit_id)
5721
        g.ColorEditCurrentID = window->IDStack.back();
5722
    BeginGroup();
5723
5724
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5725
        flags |= ImGuiColorEditFlags_NoSmallPreview;
5726
5727
    // Context menu: display and store options.
5728
    if (!(flags & ImGuiColorEditFlags_NoOptions))
5729
        ColorPickerOptionsPopup(col, flags);
5730
5731
    // Read stored options
5732
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
5733
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
5734
    if (!(flags & ImGuiColorEditFlags_InputMask_))
5735
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
5736
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
5737
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
5738
    if (!(flags & ImGuiColorEditFlags_NoOptions))
5739
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
5740
5741
    // Setup
5742
    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
5743
    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
5744
    ImVec2 picker_pos = window->DC.CursorPos;
5745
    float square_sz = GetFrameHeight();
5746
    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
5747
    float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
5748
    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
5749
    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
5750
    float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
5751
5752
    float backup_initial_col[4];
5753
    memcpy(backup_initial_col, col, components * sizeof(float));
5754
5755
    float wheel_thickness = sv_picker_size * 0.08f;
5756
    float wheel_r_outer = sv_picker_size * 0.50f;
5757
    float wheel_r_inner = wheel_r_outer - wheel_thickness;
5758
    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
5759
5760
    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
5761
    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
5762
    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
5763
    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
5764
    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
5765
5766
    float H = col[0], S = col[1], V = col[2];
5767
    float R = col[0], G = col[1], B = col[2];
5768
    if (flags & ImGuiColorEditFlags_InputRGB)
5769
    {
5770
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
5771
        ColorConvertRGBtoHSV(R, G, B, H, S, V);
5772
        ColorEditRestoreHS(col, &H, &S, &V);
5773
    }
5774
    else if (flags & ImGuiColorEditFlags_InputHSV)
5775
    {
5776
        ColorConvertHSVtoRGB(H, S, V, R, G, B);
5777
    }
5778
5779
    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
5780
5781
    PushItemFlag(ImGuiItemFlags_NoNav, true);
5782
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
5783
    {
5784
        // Hue wheel + SV triangle logic
5785
        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
5786
        if (IsItemActive() && !is_readonly)
5787
        {
5788
            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
5789
            ImVec2 current_off = g.IO.MousePos - wheel_center;
5790
            float initial_dist2 = ImLengthSqr(initial_off);
5791
            if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
5792
            {
5793
                // Interactive with Hue wheel
5794
                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
5795
                if (H < 0.0f)
5796
                    H += 1.0f;
5797
                value_changed = value_changed_h = true;
5798
            }
5799
            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
5800
            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
5801
            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
5802
            {
5803
                // Interacting with SV triangle
5804
                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
5805
                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
5806
                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
5807
                float uu, vv, ww;
5808
                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
5809
                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
5810
                S = ImClamp(uu / V, 0.0001f, 1.0f);
5811
                value_changed = value_changed_sv = true;
5812
            }
5813
        }
5814
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5815
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5816
    }
5817
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
5818
    {
5819
        // SV rectangle logic
5820
        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
5821
        if (IsItemActive() && !is_readonly)
5822
        {
5823
            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
5824
            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5825
            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
5826
            value_changed = value_changed_sv = true;
5827
        }
5828
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5829
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5830
5831
        // Hue bar logic
5832
        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
5833
        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
5834
        if (IsItemActive() && !is_readonly)
5835
        {
5836
            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5837
            value_changed = value_changed_h = true;
5838
        }
5839
    }
5840
5841
    // Alpha bar logic
5842
    if (alpha_bar)
5843
    {
5844
        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
5845
        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
5846
        if (IsItemActive())
5847
        {
5848
            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
5849
            value_changed = true;
5850
        }
5851
    }
5852
    PopItemFlag(); // ImGuiItemFlags_NoNav
5853
5854
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5855
    {
5856
        SameLine(0, style.ItemInnerSpacing.x);
5857
        BeginGroup();
5858
    }
5859
5860
    if (!(flags & ImGuiColorEditFlags_NoLabel))
5861
    {
5862
        const char* label_display_end = FindRenderedTextEnd(label);
5863
        if (label != label_display_end)
5864
        {
5865
            if ((flags & ImGuiColorEditFlags_NoSidePreview))
5866
                SameLine(0, style.ItemInnerSpacing.x);
5867
            TextEx(label, label_display_end);
5868
        }
5869
    }
5870
5871
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
5872
    {
5873
        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
5874
        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
5875
        if ((flags & ImGuiColorEditFlags_NoLabel))
5876
            Text("Current");
5877
5878
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
5879
        ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
5880
        if (ref_col != NULL)
5881
        {
5882
            Text("Original");
5883
            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
5884
            if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
5885
            {
5886
                memcpy(col, ref_col, components * sizeof(float));
5887
                value_changed = true;
5888
            }
5889
        }
5890
        PopItemFlag();
5891
        EndGroup();
5892
    }
5893
5894
    // Convert back color to RGB
5895
    if (value_changed_h || value_changed_sv)
5896
    {
5897
        if (flags & ImGuiColorEditFlags_InputRGB)
5898
        {
5899
            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
5900
            g.ColorEditSavedHue = H;
5901
            g.ColorEditSavedSat = S;
5902
            g.ColorEditSavedID = g.ColorEditCurrentID;
5903
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
5904
        }
5905
        else if (flags & ImGuiColorEditFlags_InputHSV)
5906
        {
5907
            col[0] = H;
5908
            col[1] = S;
5909
            col[2] = V;
5910
        }
5911
    }
5912
5913
    // R,G,B and H,S,V slider color editor
5914
    bool value_changed_fix_hue_wrap = false;
5915
    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
5916
    {
5917
        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
5918
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
5919
        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
5920
        if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5921
            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
5922
            {
5923
                // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
5924
                // 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)
5925
                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
5926
                value_changed = true;
5927
            }
5928
        if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5929
            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
5930
        if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
5931
            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
5932
        PopItemWidth();
5933
    }
5934
5935
    // Try to cancel hue wrap (after ColorEdit4 call), if any
5936
    if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
5937
    {
5938
        float new_H, new_S, new_V;
5939
        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
5940
        if (new_H <= 0 && H > 0)
5941
        {
5942
            if (new_V <= 0 && V != new_V)
5943
                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
5944
            else if (new_S <= 0)
5945
                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
5946
        }
5947
    }
5948
5949
    if (value_changed)
5950
    {
5951
        if (flags & ImGuiColorEditFlags_InputRGB)
5952
        {
5953
            R = col[0];
5954
            G = col[1];
5955
            B = col[2];
5956
            ColorConvertRGBtoHSV(R, G, B, H, S, V);
5957
            ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.
5958
        }
5959
        else if (flags & ImGuiColorEditFlags_InputHSV)
5960
        {
5961
            H = col[0];
5962
            S = col[1];
5963
            V = col[2];
5964
            ColorConvertHSVtoRGB(H, S, V, R, G, B);
5965
        }
5966
    }
5967
5968
    const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
5969
    const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
5970
    const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
5971
    const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
5972
    const ImU32 col_hues[6 + 1] = { IM_COL32(255,0,0,style_alpha8), IM_COL32(255,255,0,style_alpha8), IM_COL32(0,255,0,style_alpha8), IM_COL32(0,255,255,style_alpha8), IM_COL32(0,0,255,style_alpha8), IM_COL32(255,0,255,style_alpha8), IM_COL32(255,0,0,style_alpha8) };
5973
5974
    ImVec4 hue_color_f(1, 1, 1, style.Alpha); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
5975
    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
5976
    ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
5977
5978
    ImVec2 sv_cursor_pos;
5979
5980
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
5981
    {
5982
        // Render Hue Wheel
5983
        const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
5984
        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
5985
        for (int n = 0; n < 6; n++)
5986
        {
5987
            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
5988
            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
5989
            const int vert_start_idx = draw_list->VtxBuffer.Size;
5990
            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
5991
            draw_list->PathStroke(col_white, 0, wheel_thickness);
5992
            const int vert_end_idx = draw_list->VtxBuffer.Size;
5993
5994
            // Paint colors over existing vertices
5995
            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
5996
            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
5997
            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
5998
        }
5999
6000
        // Render Cursor + preview on Hue Wheel
6001
        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
6002
        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
6003
        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);
6004
        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
6005
        int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.
6006
        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
6007
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
6008
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
6009
6010
        // Render SV triangle (rotated according to hue)
6011
        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
6012
        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
6013
        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
6014
        ImVec2 uv_white = GetFontTexUvWhitePixel();
6015
        draw_list->PrimReserve(3, 3);
6016
        draw_list->PrimVtx(tra, uv_white, hue_color32);
6017
        draw_list->PrimVtx(trb, uv_white, col_black);
6018
        draw_list->PrimVtx(trc, uv_white, col_white);
6019
        draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
6020
        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
6021
    }
6022
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
6023
    {
6024
        // Render SV Square
6025
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
6026
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
6027
        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
6028
        sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S)     * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
6029
        sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
6030
6031
        // Render Hue Bar
6032
        for (int i = 0; i < 6; ++i)
6033
            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)), col_hues[i], col_hues[i], col_hues[i + 1], col_hues[i + 1]);
6034
        float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
6035
        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
6036
        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, style.Alpha);
6037
    }
6038
6039
    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
6040
    float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
6041
    int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.
6042
    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);
6043
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
6044
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
6045
6046
    // Render alpha bar
6047
    if (alpha_bar)
6048
    {
6049
        float alpha = ImSaturate(col[3]);
6050
        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
6051
        RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
6052
        draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, user_col32_striped_of_alpha, user_col32_striped_of_alpha, user_col32_striped_of_alpha & ~IM_COL32_A_MASK, user_col32_striped_of_alpha & ~IM_COL32_A_MASK);
6053
        float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
6054
        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
6055
        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, style.Alpha);
6056
    }
6057
6058
    EndGroup();
6059
6060
    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
6061
        value_changed = false;
6062
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
6063
        MarkItemEdited(g.LastItemData.ID);
6064
6065
    if (set_current_color_edit_id)
6066
        g.ColorEditCurrentID = 0;
6067
    PopID();
6068
6069
    return value_changed;
6070
}
6071
6072
// A little color square. Return true when clicked.
6073
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
6074
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
6075
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
6076
bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
6077
{
6078
    ImGuiWindow* window = GetCurrentWindow();
6079
    if (window->SkipItems)
6080
        return false;
6081
6082
    ImGuiContext& g = *GImGui;
6083
    const ImGuiID id = window->GetID(desc_id);
6084
    const float default_size = GetFrameHeight();
6085
    const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
6086
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
6087
    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
6088
    if (!ItemAdd(bb, id))
6089
        return false;
6090
6091
    bool hovered, held;
6092
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
6093
6094
    if (flags & ImGuiColorEditFlags_NoAlpha)
6095
        flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
6096
6097
    ImVec4 col_rgb = col;
6098
    if (flags & ImGuiColorEditFlags_InputHSV)
6099
        ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
6100
6101
    ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
6102
    float grid_step = ImMin(size.x, size.y) / 2.99f;
6103
    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
6104
    ImRect bb_inner = bb;
6105
    float off = 0.0f;
6106
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
6107
    {
6108
        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.
6109
        bb_inner.Expand(off);
6110
    }
6111
    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
6112
    {
6113
        float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
6114
        RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawFlags_RoundCornersRight);
6115
        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
6116
    }
6117
    else
6118
    {
6119
        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
6120
        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col_rgb : col_rgb_without_alpha;
6121
        if (col_source.w < 1.0f)
6122
            RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
6123
        else
6124
            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
6125
    }
6126
    RenderNavCursor(bb, id);
6127
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
6128
    {
6129
        if (g.Style.FrameBorderSize > 0.0f)
6130
            RenderFrameBorder(bb.Min, bb.Max, rounding);
6131
        else
6132
            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
6133
    }
6134
6135
    // Drag and Drop Source
6136
    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
6137
    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
6138
    {
6139
        if (flags & ImGuiColorEditFlags_NoAlpha)
6140
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
6141
        else
6142
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
6143
        ColorButton(desc_id, col, flags);
6144
        SameLine();
6145
        TextEx("Color");
6146
        EndDragDropSource();
6147
    }
6148
6149
    // Tooltip
6150
    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
6151
        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
6152
6153
    return pressed;
6154
}
6155
6156
// Initialize/override default color options
6157
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
6158
{
6159
    ImGuiContext& g = *GImGui;
6160
    if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
6161
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
6162
    if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
6163
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
6164
    if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
6165
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
6166
    if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
6167
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
6168
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
6169
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
6170
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
6171
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
6172
    g.ColorEditOptions = flags;
6173
}
6174
6175
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
6176
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
6177
{
6178
    ImGuiContext& g = *GImGui;
6179
6180
    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
6181
        return;
6182
    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
6183
    if (text_end > text)
6184
    {
6185
        TextEx(text, text_end);
6186
        Separator();
6187
    }
6188
6189
    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
6190
    ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
6191
    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]);
6192
    ColorButton("##preview", cf, (flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
6193
    SameLine();
6194
    if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
6195
    {
6196
        if (flags & ImGuiColorEditFlags_NoAlpha)
6197
            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]);
6198
        else
6199
            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]);
6200
    }
6201
    else if (flags & ImGuiColorEditFlags_InputHSV)
6202
    {
6203
        if (flags & ImGuiColorEditFlags_NoAlpha)
6204
            Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
6205
        else
6206
            Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
6207
    }
6208
    EndTooltip();
6209
}
6210
6211
void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
6212
{
6213
    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
6214
    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
6215
    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
6216
        return;
6217
6218
    ImGuiContext& g = *GImGui;
6219
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
6220
    ImGuiColorEditFlags opts = g.ColorEditOptions;
6221
    if (allow_opt_inputs)
6222
    {
6223
        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
6224
        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
6225
        if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
6226
    }
6227
    if (allow_opt_datatype)
6228
    {
6229
        if (allow_opt_inputs) Separator();
6230
        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
6231
        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
6232
    }
6233
6234
    if (allow_opt_inputs || allow_opt_datatype)
6235
        Separator();
6236
    if (Button("Copy as..", ImVec2(-1, 0)))
6237
        OpenPopup("Copy");
6238
    if (BeginPopup("Copy"))
6239
    {
6240
        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]);
6241
        char buf[64];
6242
        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
6243
        if (Selectable(buf))
6244
            SetClipboardText(buf);
6245
        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
6246
        if (Selectable(buf))
6247
            SetClipboardText(buf);
6248
        ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
6249
        if (Selectable(buf))
6250
            SetClipboardText(buf);
6251
        if (!(flags & ImGuiColorEditFlags_NoAlpha))
6252
        {
6253
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
6254
            if (Selectable(buf))
6255
                SetClipboardText(buf);
6256
        }
6257
        EndPopup();
6258
    }
6259
6260
    g.ColorEditOptions = opts;
6261
    PopItemFlag();
6262
    EndPopup();
6263
}
6264
6265
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
6266
{
6267
    bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
6268
    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
6269
    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
6270
        return;
6271
6272
    ImGuiContext& g = *GImGui;
6273
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
6274
    if (allow_opt_picker)
6275
    {
6276
        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
6277
        PushItemWidth(picker_size.x);
6278
        for (int picker_type = 0; picker_type < 2; picker_type++)
6279
        {
6280
            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
6281
            if (picker_type > 0) Separator();
6282
            PushID(picker_type);
6283
            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
6284
            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
6285
            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
6286
            ImVec2 backup_pos = GetCursorScreenPos();
6287
            if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
6288
                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
6289
            SetCursorScreenPos(backup_pos);
6290
            ImVec4 previewing_ref_col;
6291
            memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
6292
            ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
6293
            PopID();
6294
        }
6295
        PopItemWidth();
6296
    }
6297
    if (allow_opt_alpha_bar)
6298
    {
6299
        if (allow_opt_picker) Separator();
6300
        CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
6301
    }
6302
    PopItemFlag();
6303
    EndPopup();
6304
}
6305
6306
//-------------------------------------------------------------------------
6307
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
6308
//-------------------------------------------------------------------------
6309
// - TreeNode()
6310
// - TreeNodeV()
6311
// - TreeNodeEx()
6312
// - TreeNodeExV()
6313
// - TreeNodeBehavior() [Internal]
6314
// - TreePush()
6315
// - TreePop()
6316
// - GetTreeNodeToLabelSpacing()
6317
// - SetNextItemOpen()
6318
// - CollapsingHeader()
6319
//-------------------------------------------------------------------------
6320
6321
bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
6322
{
6323
    va_list args;
6324
    va_start(args, fmt);
6325
    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
6326
    va_end(args);
6327
    return is_open;
6328
}
6329
6330
bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
6331
{
6332
    va_list args;
6333
    va_start(args, fmt);
6334
    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
6335
    va_end(args);
6336
    return is_open;
6337
}
6338
6339
bool ImGui::TreeNode(const char* label)
6340
{
6341
    ImGuiWindow* window = GetCurrentWindow();
6342
    if (window->SkipItems)
6343
        return false;
6344
    ImGuiID id = window->GetID(label);
6345
    return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);
6346
}
6347
6348
bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
6349
{
6350
    return TreeNodeExV(str_id, 0, fmt, args);
6351
}
6352
6353
bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
6354
{
6355
    return TreeNodeExV(ptr_id, 0, fmt, args);
6356
}
6357
6358
bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
6359
{
6360
    ImGuiWindow* window = GetCurrentWindow();
6361
    if (window->SkipItems)
6362
        return false;
6363
    ImGuiID id = window->GetID(label);
6364
    return TreeNodeBehavior(id, flags, label, NULL);
6365
}
6366
6367
bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6368
{
6369
    va_list args;
6370
    va_start(args, fmt);
6371
    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
6372
    va_end(args);
6373
    return is_open;
6374
}
6375
6376
bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6377
{
6378
    va_list args;
6379
    va_start(args, fmt);
6380
    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
6381
    va_end(args);
6382
    return is_open;
6383
}
6384
6385
bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6386
{
6387
    ImGuiWindow* window = GetCurrentWindow();
6388
    if (window->SkipItems)
6389
        return false;
6390
6391
    ImGuiID id = window->GetID(str_id);
6392
    const char* label, *label_end;
6393
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6394
    return TreeNodeBehavior(id, flags, label, label_end);
6395
}
6396
6397
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6398
{
6399
    ImGuiWindow* window = GetCurrentWindow();
6400
    if (window->SkipItems)
6401
        return false;
6402
6403
    ImGuiID id = window->GetID(ptr_id);
6404
    const char* label, *label_end;
6405
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6406
    return TreeNodeBehavior(id, flags, label, label_end);
6407
}
6408
6409
bool ImGui::TreeNodeGetOpen(ImGuiID storage_id)
6410
{
6411
    ImGuiContext& g = *GImGui;
6412
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
6413
    return storage->GetInt(storage_id, 0) != 0;
6414
}
6415
6416
void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open)
6417
{
6418
    ImGuiContext& g = *GImGui;
6419
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
6420
    storage->SetInt(storage_id, open ? 1 : 0);
6421
}
6422
6423
bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
6424
{
6425
    if (flags & ImGuiTreeNodeFlags_Leaf)
6426
        return true;
6427
6428
    // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function
6429
    ImGuiContext& g = *GImGui;
6430
    ImGuiWindow* window = g.CurrentWindow;
6431
    ImGuiStorage* storage = window->DC.StateStorage;
6432
6433
    bool is_open;
6434
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen)
6435
    {
6436
        if (g.NextItemData.OpenCond & ImGuiCond_Always)
6437
        {
6438
            is_open = g.NextItemData.OpenVal;
6439
            TreeNodeSetOpen(storage_id, is_open);
6440
        }
6441
        else
6442
        {
6443
            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
6444
            const int stored_value = storage->GetInt(storage_id, -1);
6445
            if (stored_value == -1)
6446
            {
6447
                is_open = g.NextItemData.OpenVal;
6448
                TreeNodeSetOpen(storage_id, is_open);
6449
            }
6450
            else
6451
            {
6452
                is_open = stored_value != 0;
6453
            }
6454
        }
6455
    }
6456
    else
6457
    {
6458
        is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
6459
    }
6460
6461
    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
6462
    // NB- If we are above max depth we still allow manually opened nodes to be logged.
6463
    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
6464
        is_open = true;
6465
6466
    return is_open;
6467
}
6468
6469
// Store ImGuiTreeNodeStackData for just submitted node.
6470
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
6471
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags)
6472
{
6473
    ImGuiContext& g = *GImGui;
6474
    ImGuiWindow* window = g.CurrentWindow;
6475
6476
    g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
6477
    ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back();
6478
    tree_node_data->ID = g.LastItemData.ID;
6479
    tree_node_data->TreeFlags = flags;
6480
    tree_node_data->ItemFlags = g.LastItemData.ItemFlags;
6481
    tree_node_data->NavRect = g.LastItemData.NavRect;
6482
    window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
6483
}
6484
6485
// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.
6486
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
6487
{
6488
    ImGuiWindow* window = GetCurrentWindow();
6489
    if (window->SkipItems)
6490
        return false;
6491
6492
    ImGuiContext& g = *GImGui;
6493
    const ImGuiStyle& style = g.Style;
6494
    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
6495
    const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
6496
6497
    if (!label_end)
6498
        label_end = FindRenderedTextEnd(label);
6499
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
6500
6501
    const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);   // Collapsing arrow width + Spacing
6502
    const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);            // Latch before ItemSize changes it
6503
    const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow
6504
6505
    // We vertically grow up to current line height up the typical widget height.
6506
    const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
6507
    const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
6508
    ImRect frame_bb;
6509
    frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
6510
    frame_bb.Min.y = window->DC.CursorPos.y;
6511
    frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;
6512
    frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
6513
    if (display_frame)
6514
    {
6515
        const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits
6516
        frame_bb.Min.x -= outer_extend;
6517
        frame_bb.Max.x += outer_extend;
6518
    }
6519
6520
    ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
6521
    ItemSize(ImVec2(text_width, frame_height), padding.y);
6522
6523
    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
6524
    ImRect interact_bb = frame_bb;
6525
    if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
6526
        interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f);
6527
6528
    // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
6529
    ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;
6530
    bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);
6531
6532
    bool is_visible;
6533
    if (span_all_columns)
6534
    {
6535
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
6536
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6537
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6538
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6539
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6540
        is_visible = ItemAdd(interact_bb, id);
6541
        window->ClipRect.Min.x = backup_clip_rect_min_x;
6542
        window->ClipRect.Max.x = backup_clip_rect_max_x;
6543
    }
6544
    else
6545
    {
6546
        is_visible = ItemAdd(interact_bb, id);
6547
    }
6548
    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
6549
    g.LastItemData.DisplayRect = frame_bb;
6550
6551
    // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled:
6552
    // Store data for the current depth to allow returning to this node from any child item.
6553
    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
6554
    // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle.
6555
    bool store_tree_node_stack_data = false;
6556
    if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6557
    {
6558
        if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive)
6559
            if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6560
                store_tree_node_stack_data = true;
6561
    }
6562
6563
    const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
6564
    if (!is_visible)
6565
    {
6566
        if (store_tree_node_stack_data && is_open)
6567
            TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
6568
        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6569
            TreePushOverrideID(id);
6570
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
6571
        return is_open;
6572
    }
6573
6574
    if (span_all_columns)
6575
    {
6576
        TablePushBackgroundChannel();
6577
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
6578
        g.LastItemData.ClipRect = window->ClipRect;
6579
    }
6580
6581
    ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
6582
    if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap))
6583
        button_flags |= ImGuiButtonFlags_AllowOverlap;
6584
    if (!is_leaf)
6585
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
6586
6587
    // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
6588
    // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
6589
    // When clicking on the rest of the tree node we always disallow keyboard modifiers.
6590
    const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
6591
    const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
6592
    const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
6593
6594
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
6595
    if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default
6596
        flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow;
6597
6598
    // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
6599
    // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
6600
    // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
6601
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
6602
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
6603
    // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
6604
    // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
6605
    // It is rather standard that arrow click react on Down rather than Up.
6606
    // We set ImGuiButtonFlags_PressedOnClickRelease on OpenOnDoubleClick because we want the item to be active on the initial MouseDown in order for drag and drop to work.
6607
    if (is_mouse_x_over_arrow)
6608
        button_flags |= ImGuiButtonFlags_PressedOnClick;
6609
    else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
6610
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
6611
    else
6612
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
6613
6614
    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
6615
    const bool was_selected = selected;
6616
6617
    // Multi-selection support (header)
6618
    if (is_multi_select)
6619
    {
6620
        // Handle multi-select + alter button flags for it
6621
        MultiSelectItemHeader(id, &selected, &button_flags);
6622
        if (is_mouse_x_over_arrow)
6623
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
6624
    }
6625
    else
6626
    {
6627
        if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
6628
            button_flags |= ImGuiButtonFlags_NoKeyModsAllowed;
6629
    }
6630
6631
    bool hovered, held;
6632
    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
6633
    bool toggled = false;
6634
    if (!is_leaf)
6635
    {
6636
        if (pressed && g.DragDropHoldJustPressedId != id)
6637
        {
6638
            if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select))
6639
                toggled = true; // Single click
6640
            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
6641
                toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
6642
            if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
6643
                toggled = true; // Double click
6644
        }
6645
        else if (pressed && g.DragDropHoldJustPressedId == id)
6646
        {
6647
            IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
6648
            if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
6649
                toggled = true;
6650
            else
6651
                pressed = false; // Cancel press so it doesn't trigger selection.
6652
        }
6653
6654
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
6655
        {
6656
            toggled = true;
6657
            NavClearPreferredPosForAxis(ImGuiAxis_X);
6658
            NavMoveRequestCancel();
6659
        }
6660
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
6661
        {
6662
            toggled = true;
6663
            NavClearPreferredPosForAxis(ImGuiAxis_X);
6664
            NavMoveRequestCancel();
6665
        }
6666
6667
        if (toggled)
6668
        {
6669
            is_open = !is_open;
6670
            window->DC.StateStorage->SetInt(storage_id, is_open);
6671
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
6672
        }
6673
    }
6674
6675
    // Multi-selection support (footer)
6676
    if (is_multi_select)
6677
    {
6678
        bool pressed_copy = pressed && !toggled;
6679
        MultiSelectItemFooter(id, &selected, &pressed_copy);
6680
        if (pressed)
6681
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
6682
    }
6683
6684
    if (selected != was_selected)
6685
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
6686
6687
    // Render
6688
    {
6689
        const ImU32 text_col = GetColorU32(ImGuiCol_Text);
6690
        ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;
6691
        if (is_multi_select)
6692
            nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
6693
        if (display_frame)
6694
        {
6695
            // Framed type
6696
            const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6697
            RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
6698
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
6699
            if (flags & ImGuiTreeNodeFlags_Bullet)
6700
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
6701
            else if (!is_leaf)
6702
                RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 1.0f);
6703
            else // Leaf without bullet, left-adjusted text
6704
                text_pos.x -= text_offset_x - padding.x;
6705
            if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
6706
                frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
6707
            if (g.LogEnabled)
6708
                LogSetNextTextDecoration("###", "###");
6709
        }
6710
        else
6711
        {
6712
            // Unframed typed for tree nodes
6713
            if (hovered || selected)
6714
            {
6715
                const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
6716
                RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
6717
            }
6718
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
6719
            if (flags & ImGuiTreeNodeFlags_Bullet)
6720
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
6721
            else if (!is_leaf)
6722
                RenderArrow(window->DrawList, ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.15f), text_col, is_open ? ((flags & ImGuiTreeNodeFlags_UpsideDownArrow) ? ImGuiDir_Up : ImGuiDir_Down) : ImGuiDir_Right, 0.70f);
6723
            if (g.LogEnabled)
6724
                LogSetNextTextDecoration(">", NULL);
6725
        }
6726
6727
        if (span_all_columns)
6728
            TablePopBackgroundChannel();
6729
6730
        // Label
6731
        if (display_frame)
6732
            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
6733
        else
6734
            RenderText(text_pos, label, label_end, false);
6735
    }
6736
6737
    if (store_tree_node_stack_data && is_open)
6738
        TreeNodeStoreStackData(flags); // Call before TreePushOverrideID()
6739
    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6740
        TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
6741
6742
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
6743
    return is_open;
6744
}
6745
6746
void ImGui::TreePush(const char* str_id)
6747
{
6748
    ImGuiWindow* window = GetCurrentWindow();
6749
    Indent();
6750
    window->DC.TreeDepth++;
6751
    PushID(str_id);
6752
}
6753
6754
void ImGui::TreePush(const void* ptr_id)
6755
{
6756
    ImGuiWindow* window = GetCurrentWindow();
6757
    Indent();
6758
    window->DC.TreeDepth++;
6759
    PushID(ptr_id);
6760
}
6761
6762
void ImGui::TreePushOverrideID(ImGuiID id)
6763
{
6764
    ImGuiContext& g = *GImGui;
6765
    ImGuiWindow* window = g.CurrentWindow;
6766
    Indent();
6767
    window->DC.TreeDepth++;
6768
    PushOverrideID(id);
6769
}
6770
6771
void ImGui::TreePop()
6772
{
6773
    ImGuiContext& g = *GImGui;
6774
    ImGuiWindow* window = g.CurrentWindow;
6775
    Unindent();
6776
6777
    window->DC.TreeDepth--;
6778
    ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
6779
6780
    if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request
6781
    {
6782
        ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back();
6783
        IM_ASSERT(data->ID == window->IDStack.back());
6784
        if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)
6785
        {
6786
            // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled)
6787
            if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6788
                NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
6789
        }
6790
        g.TreeNodeStack.pop_back();
6791
        window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
6792
    }
6793
6794
    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.
6795
    PopID();
6796
}
6797
6798
// Horizontal distance preceding label when using TreeNode() or Bullet()
6799
float ImGui::GetTreeNodeToLabelSpacing()
6800
{
6801
    ImGuiContext& g = *GImGui;
6802
    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
6803
}
6804
6805
// Set next TreeNode/CollapsingHeader open state.
6806
void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
6807
{
6808
    ImGuiContext& g = *GImGui;
6809
    if (g.CurrentWindow->SkipItems)
6810
        return;
6811
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen;
6812
    g.NextItemData.OpenVal = is_open;
6813
    g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);
6814
}
6815
6816
// Set next TreeNode/CollapsingHeader storage id.
6817
void ImGui::SetNextItemStorageID(ImGuiID storage_id)
6818
{
6819
    ImGuiContext& g = *GImGui;
6820
    if (g.CurrentWindow->SkipItems)
6821
        return;
6822
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID;
6823
    g.NextItemData.StorageId = storage_id;
6824
}
6825
6826
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
6827
// 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().
6828
bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
6829
{
6830
    ImGuiWindow* window = GetCurrentWindow();
6831
    if (window->SkipItems)
6832
        return false;
6833
    ImGuiID id = window->GetID(label);
6834
    return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
6835
}
6836
6837
// p_visible == NULL                        : regular collapsing header
6838
// p_visible != NULL && *p_visible == true  : show a small close button on the corner of the header, clicking the button will set *p_visible = false
6839
// p_visible != NULL && *p_visible == false : do not show the header at all
6840
// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
6841
bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
6842
{
6843
    ImGuiWindow* window = GetCurrentWindow();
6844
    if (window->SkipItems)
6845
        return false;
6846
6847
    if (p_visible && !*p_visible)
6848
        return false;
6849
6850
    ImGuiID id = window->GetID(label);
6851
    flags |= ImGuiTreeNodeFlags_CollapsingHeader;
6852
    if (p_visible)
6853
        flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
6854
    bool is_open = TreeNodeBehavior(id, flags, label);
6855
    if (p_visible != NULL)
6856
    {
6857
        // Create a small overlapping close button
6858
        // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
6859
        // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
6860
        ImGuiContext& g = *GImGui;
6861
        ImGuiLastItemData last_item_backup = g.LastItemData;
6862
        float button_size = g.FontSize;
6863
        float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
6864
        float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
6865
        ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
6866
        if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
6867
            *p_visible = false;
6868
        g.LastItemData = last_item_backup;
6869
    }
6870
6871
    return is_open;
6872
}
6873
6874
//-------------------------------------------------------------------------
6875
// [SECTION] Widgets: Selectable
6876
//-------------------------------------------------------------------------
6877
// - Selectable()
6878
//-------------------------------------------------------------------------
6879
6880
// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
6881
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
6882
// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
6883
// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
6884
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
6885
{
6886
    ImGuiWindow* window = GetCurrentWindow();
6887
    if (window->SkipItems)
6888
        return false;
6889
6890
    ImGuiContext& g = *GImGui;
6891
    const ImGuiStyle& style = g.Style;
6892
6893
    // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
6894
    ImGuiID id = window->GetID(label);
6895
    ImVec2 label_size = CalcTextSize(label, NULL, true);
6896
    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
6897
    ImVec2 pos = window->DC.CursorPos;
6898
    pos.y += window->DC.CurrLineTextBaseOffset;
6899
    ItemSize(size, 0.0f);
6900
6901
    // Fill horizontal space
6902
    // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
6903
    const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
6904
    const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
6905
    const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
6906
    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
6907
        size.x = ImMax(label_size.x, max_x - min_x);
6908
6909
    // Text stays at the submission position, but bounding box may be extended on both sides
6910
    const ImVec2 text_min = pos;
6911
    const ImVec2 text_max(min_x + size.x, pos.y + size.y);
6912
6913
    // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
6914
    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.
6915
    ImRect bb(min_x, pos.y, text_max.x, text_max.y);
6916
    if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
6917
    {
6918
        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
6919
        const float spacing_y = style.ItemSpacing.y;
6920
        const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
6921
        const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
6922
        bb.Min.x -= spacing_L;
6923
        bb.Min.y -= spacing_U;
6924
        bb.Max.x += (spacing_x - spacing_L);
6925
        bb.Max.y += (spacing_y - spacing_U);
6926
    }
6927
    //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
6928
6929
    const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
6930
    const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;
6931
    bool is_visible;
6932
    if (span_all_columns)
6933
    {
6934
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
6935
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6936
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6937
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6938
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6939
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
6940
        window->ClipRect.Min.x = backup_clip_rect_min_x;
6941
        window->ClipRect.Max.x = backup_clip_rect_max_x;
6942
    }
6943
    else
6944
    {
6945
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
6946
    }
6947
6948
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
6949
    if (!is_visible)
6950
        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(bb)) // Extra layer of "no logic clip" for box-select support (would be more overhead to add to ItemAdd)
6951
            return false;
6952
6953
    const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
6954
    if (disabled_item && !disabled_global) // Only testing this as an optimization
6955
        BeginDisabled();
6956
6957
    // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
6958
    // which would be advantageous since most selectable are not selected.
6959
    if (span_all_columns)
6960
    {
6961
        if (g.CurrentTable)
6962
            TablePushBackgroundChannel();
6963
        else if (window->DC.CurrentColumns)
6964
            PushColumnsBackground();
6965
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
6966
        g.LastItemData.ClipRect = window->ClipRect;
6967
    }
6968
6969
    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
6970
    ImGuiButtonFlags button_flags = 0;
6971
    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
6972
    if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
6973
    if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
6974
    if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
6975
    if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
6976
    if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
6977
6978
    // Multi-selection support (header)
6979
    const bool was_selected = selected;
6980
    if (is_multi_select)
6981
    {
6982
        // Handle multi-select + alter button flags for it
6983
        MultiSelectItemHeader(id, &selected, &button_flags);
6984
    }
6985
6986
    bool hovered, held;
6987
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
6988
6989
    // Multi-selection support (footer)
6990
    if (is_multi_select)
6991
    {
6992
        MultiSelectItemFooter(id, &selected, &pressed);
6993
    }
6994
    else
6995
    {
6996
        // Auto-select when moved into
6997
        // - This will be more fully fleshed in the range-select branch
6998
        // - This is not exposed as it won't nicely work with some user side handling of shift/control
6999
        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
7000
        //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
7001
        //   - (2) usage will fail with clipped items
7002
        //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
7003
        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
7004
            if (g.NavJustMovedToId == id)
7005
                selected = pressed = true;
7006
    }
7007
7008
    // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad
7009
    if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
7010
    {
7011
        if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
7012
        {
7013
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
7014
            if (g.IO.ConfigNavCursorVisibleAuto)
7015
                g.NavCursorVisible = false;
7016
        }
7017
    }
7018
    if (pressed)
7019
        MarkItemEdited(id);
7020
7021
    if (selected != was_selected)
7022
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
7023
7024
    // Render
7025
    if (is_visible)
7026
    {
7027
        const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);
7028
        if (highlighted || selected)
7029
        {
7030
            // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106)
7031
            ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
7032
            RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
7033
        }
7034
        if (g.NavId == id)
7035
        {
7036
            ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding;
7037
            if (is_multi_select)
7038
                nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
7039
            RenderNavCursor(bb, id, nav_render_cursor_flags);
7040
        }
7041
    }
7042
7043
    if (span_all_columns)
7044
    {
7045
        if (g.CurrentTable)
7046
            TablePopBackgroundChannel();
7047
        else if (window->DC.CurrentColumns)
7048
            PopColumnsBackground();
7049
    }
7050
7051
    if (is_visible)
7052
        RenderTextClipped(text_min, text_max, label, NULL, &label_size, style.SelectableTextAlign, &bb);
7053
7054
    // Automatically close popups
7055
    if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
7056
        CloseCurrentPopup();
7057
7058
    if (disabled_item && !disabled_global)
7059
        EndDisabled();
7060
7061
    // Selectable() always returns a pressed state!
7062
    // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve
7063
    // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect().
7064
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
7065
    return pressed; //-V1020
7066
}
7067
7068
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
7069
{
7070
    if (Selectable(label, *p_selected, flags, size_arg))
7071
    {
7072
        *p_selected = !*p_selected;
7073
        return true;
7074
    }
7075
    return false;
7076
}
7077
7078
7079
//-------------------------------------------------------------------------
7080
// [SECTION] Widgets: Typing-Select support
7081
//-------------------------------------------------------------------------
7082
7083
// [Experimental] Currently not exposed in public API.
7084
// Consume character inputs and return search request, if any.
7085
// This would typically only be called on the focused window or location you want to grab inputs for, e.g.
7086
//   if (ImGui::IsWindowFocused(...))
7087
//       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
7088
//           focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);
7089
// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).
7090
ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
7091
{
7092
    ImGuiContext& g = *GImGui;
7093
    ImGuiTypingSelectState* data = &g.TypingSelectState;
7094
    ImGuiTypingSelectRequest* out_request = &data->Request;
7095
7096
    // Clear buffer
7097
    const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.
7098
    const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times
7099
    if (data->SearchBuffer[0] != 0)
7100
    {
7101
        bool clear_buffer = false;
7102
        clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
7103
        clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
7104
        clear_buffer |= g.NavAnyRequest;
7105
        clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
7106
        clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
7107
        clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
7108
        //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
7109
        if (clear_buffer)
7110
            data->Clear();
7111
    }
7112
7113
    // Append to buffer
7114
    const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
7115
    int buffer_len = (int)strlen(data->SearchBuffer);
7116
    bool select_request = false;
7117
    for (ImWchar w : g.IO.InputQueueCharacters)
7118
    {
7119
        const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
7120
        if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks
7121
            continue;
7122
        char w_buf[5];
7123
        ImTextCharToUtf8(w_buf, (unsigned int)w);
7124
        if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
7125
        {
7126
            select_request = true; // Same character: don't need to append to buffer.
7127
            continue;
7128
        }
7129
        if (data->SingleCharModeLock)
7130
        {
7131
            data->Clear(); // Different character: clear
7132
            buffer_len = 0;
7133
        }
7134
        memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
7135
        buffer_len += w_len;
7136
        select_request = true;
7137
    }
7138
    g.IO.InputQueueCharacters.resize(0);
7139
7140
    // Handle backspace
7141
    if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))
7142
    {
7143
        char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
7144
        *p = 0;
7145
        buffer_len = (int)(p - data->SearchBuffer);
7146
    }
7147
7148
    // Return request if any
7149
    if (buffer_len == 0)
7150
        return NULL;
7151
    if (select_request)
7152
    {
7153
        data->FocusScope = g.NavFocusScopeId;
7154
        data->LastRequestFrame = g.FrameCount;
7155
        data->LastRequestTime = (float)g.Time;
7156
    }
7157
    out_request->Flags = flags;
7158
    out_request->SearchBufferLen = buffer_len;
7159
    out_request->SearchBuffer = data->SearchBuffer;
7160
    out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
7161
    out_request->SingleCharMode = false;
7162
    out_request->SingleCharSize = 0;
7163
7164
    // Calculate if buffer contains the same character repeated.
7165
    // - This can be used to implement a special search mode on first character.
7166
    // - Performed on UTF-8 codepoint for correctness.
7167
    // - SingleCharMode is always set for first input character, because it usually leads to a "next".
7168
    if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
7169
    {
7170
        const char* buf_begin = out_request->SearchBuffer;
7171
        const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
7172
        const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
7173
        const char* p = buf_begin + c0_len;
7174
        for (; p < buf_end; p += c0_len)
7175
            if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
7176
                break;
7177
        const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
7178
        out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
7179
        out_request->SingleCharSize = (ImS8)c0_len;
7180
        data->SingleCharModeLock |= (single_char_count >= TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK); // From now on we stop search matching to lock to single char mode.
7181
    }
7182
7183
    return out_request;
7184
}
7185
7186
static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
7187
{
7188
    int match_len = 0;
7189
    while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
7190
        match_len++;
7191
    return match_len;
7192
}
7193
7194
// Default handler for finding a result for typing-select. You may implement your own.
7195
// You might want to display a tooltip to visualize the current request SearchBuffer
7196
// When SingleCharMode is set:
7197
// - it is better to NOT display a tooltip of other on-screen display indicator.
7198
// - the index of the currently focused item is required.
7199
//   if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.
7200
int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
7201
{
7202
    if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.
7203
        return -1;
7204
    int idx = -1;
7205
    if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
7206
        idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
7207
    else
7208
        idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
7209
    if (idx != -1)
7210
        SetNavCursorVisibleAfterMove();
7211
    return idx;
7212
}
7213
7214
// Special handling when a single character is repeated: perform search on a single letter and goes to next.
7215
int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
7216
{
7217
    // FIXME: Assume selection user data is index. Would be extremely practical.
7218
    //if (nav_item_idx == -1)
7219
    //    nav_item_idx = (int)g.NavLastValidSelectionUserData;
7220
7221
    int first_match_idx = -1;
7222
    bool return_next_match = false;
7223
    for (int idx = 0; idx < items_count; idx++)
7224
    {
7225
        const char* item_name = get_item_name_func(user_data, idx);
7226
        if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
7227
            continue;
7228
        if (return_next_match)                           // Return next matching item after current item.
7229
            return idx;
7230
        if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.
7231
            return idx;
7232
        if (first_match_idx == -1)                       // Record first match for wrapping.
7233
            first_match_idx = idx;
7234
        if (nav_item_idx == idx)                         // Record that we encountering nav_item so we can return next match.
7235
            return_next_match = true;
7236
    }
7237
    return first_match_idx; // First result
7238
}
7239
7240
int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)
7241
{
7242
    int longest_match_idx = -1;
7243
    int longest_match_len = 0;
7244
    for (int idx = 0; idx < items_count; idx++)
7245
    {
7246
        const char* item_name = get_item_name_func(user_data, idx);
7247
        const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
7248
        if (match_len <= longest_match_len)
7249
            continue;
7250
        longest_match_idx = idx;
7251
        longest_match_len = match_len;
7252
        if (match_len == req->SearchBufferLen)
7253
            break;
7254
    }
7255
    return longest_match_idx;
7256
}
7257
7258
void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
7259
{
7260
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
7261
    Text("SearchBuffer = \"%s\"", data->SearchBuffer);
7262
    Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
7263
    Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
7264
#else
7265
    IM_UNUSED(data);
7266
#endif
7267
}
7268
7269
//-------------------------------------------------------------------------
7270
// [SECTION] Widgets: Box-Select support
7271
// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.
7272
//-------------------------------------------------------------------------
7273
// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()
7274
//-------------------------------------------------------------------------
7275
// - BoxSelectPreStartDrag() [Internal]
7276
// - BoxSelectActivateDrag() [Internal]
7277
// - BoxSelectDeactivateDrag() [Internal]
7278
// - BoxSelectScrollWithMouseDrag() [Internal]
7279
// - BeginBoxSelect() [Internal]
7280
// - EndBoxSelect() [Internal]
7281
//-------------------------------------------------------------------------
7282
7283
// Call on the initial click.
7284
static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
7285
{
7286
    ImGuiContext& g = *GImGui;
7287
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7288
    bs->ID = id;
7289
    bs->IsStarting = true; // Consider starting box-select.
7290
    bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);
7291
    bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;
7292
    bs->KeyMods = g.IO.KeyMods;
7293
    bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
7294
    bs->ScrollAccum = ImVec2(0.0f, 0.0f);
7295
}
7296
7297
static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)
7298
{
7299
    ImGuiContext& g = *GImGui;
7300
    IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID);
7301
    bs->IsActive = true;
7302
    bs->Window = window;
7303
    bs->IsStarting = false;
7304
    ImGui::SetActiveID(bs->ID, window);
7305
    ImGui::SetActiveIdUsingAllKeyboardKeys();
7306
    if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
7307
        bs->RequestClear = true;
7308
}
7309
7310
static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)
7311
{
7312
    ImGuiContext& g = *GImGui;
7313
    bs->IsActive = bs->IsStarting = false;
7314
    if (g.ActiveId == bs->ID)
7315
    {
7316
        IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID);
7317
        ImGui::ClearActiveID();
7318
    }
7319
    bs->ID = 0;
7320
}
7321
7322
static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)
7323
{
7324
    ImGuiContext& g = *GImGui;
7325
    IM_ASSERT(bs->Window == window);
7326
    for (int n = 0; n < 2; n++) // each axis
7327
    {
7328
        const float mouse_pos = g.IO.MousePos[n];
7329
        const float dist = (mouse_pos > inner_r.Max[n]) ? mouse_pos - inner_r.Max[n] : (mouse_pos < inner_r.Min[n]) ? mouse_pos - inner_r.Min[n] : 0.0f;
7330
        const float scroll_curr = window->Scroll[n];
7331
        if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))
7332
            continue;
7333
7334
        const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
7335
        const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;
7336
        bs->ScrollAccum[n] += scroll_step;
7337
7338
        // Accumulate into a stored value so we can handle high-framerate
7339
        const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);
7340
        if (scroll_step_i == 0.0f)
7341
            continue;
7342
        if (n == 0)
7343
            ImGui::SetScrollX(window, scroll_curr + scroll_step_i);
7344
        else
7345
            ImGui::SetScrollY(window, scroll_curr + scroll_step_i);
7346
        bs->ScrollAccum[n] -= scroll_step_i;
7347
    }
7348
}
7349
7350
bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
7351
{
7352
    ImGuiContext& g = *GImGui;
7353
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7354
    KeepAliveID(box_select_id);
7355
    if (bs->ID != box_select_id)
7356
        return false;
7357
7358
    // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
7359
    bs->UnclipMode = false;
7360
    bs->RequestClear = false;
7361
    if (bs->IsStarting && IsMouseDragPastThreshold(0))
7362
        BoxSelectActivateDrag(bs, window);
7363
    else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
7364
        BoxSelectDeactivateDrag(bs);
7365
    if (!bs->IsActive)
7366
        return false;
7367
7368
    // Current frame absolute prev/current rectangles are used to toggle selection.
7369
    // They are derived from positions relative to scrolling space.
7370
    ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
7371
    ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
7372
    ImVec2 curr_end_pos_abs = g.IO.MousePos;
7373
    if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow
7374
        curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
7375
    bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
7376
    bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
7377
    bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
7378
    bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
7379
7380
    // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
7381
    // Storing an extra rect used by widgets supporting box-select.
7382
    if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
7383
        if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
7384
        {
7385
            bs->UnclipMode = true;
7386
            bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis.
7387
            bs->UnclipRect.Add(bs->BoxSelectRectCurr);
7388
        }
7389
7390
    //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
7391
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
7392
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
7393
    return true;
7394
}
7395
7396
void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)
7397
{
7398
    ImGuiContext& g = *GImGui;
7399
    ImGuiWindow* window = g.CurrentWindow;
7400
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7401
    IM_ASSERT(bs->IsActive);
7402
    bs->UnclipMode = false;
7403
7404
    // Render selection rectangle
7405
    bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
7406
    ImRect box_select_r = bs->BoxSelectRectCurr;
7407
    box_select_r.ClipWith(scope_rect);
7408
    window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
7409
    window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling
7410
7411
    // Scroll
7412
    const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
7413
    if (enable_scroll)
7414
    {
7415
        ImRect scroll_r = scope_rect;
7416
        scroll_r.Expand(-g.FontSize);
7417
        //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
7418
        if (!scroll_r.Contains(g.IO.MousePos))
7419
            BoxSelectScrollWithMouseDrag(bs, window, scroll_r);
7420
    }
7421
}
7422
7423
//-------------------------------------------------------------------------
7424
// [SECTION] Widgets: Multi-Select support
7425
//-------------------------------------------------------------------------
7426
// - DebugLogMultiSelectRequests() [Internal]
7427
// - CalcScopeRect() [Internal]
7428
// - BeginMultiSelect()
7429
// - EndMultiSelect()
7430
// - SetNextItemSelectionUserData()
7431
// - MultiSelectItemHeader() [Internal]
7432
// - MultiSelectItemFooter() [Internal]
7433
// - DebugNodeMultiSelectState() [Internal]
7434
//-------------------------------------------------------------------------
7435
7436
static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
7437
{
7438
    ImGuiContext& g = *GImGui;
7439
    IM_UNUSED(function);
7440
    for (const ImGuiSelectionRequest& req : io->Requests)
7441
    {
7442
        if (req.Type == ImGuiSelectionRequestType_SetAll)    IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear");
7443
        if (req.Type == ImGuiSelectionRequestType_SetRange)  IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetRange %" IM_PRId64 "..%" IM_PRId64 " (0x%" IM_PRIX64 "..0x%" IM_PRIX64 ") = %d (dir %d)\n", function, req.RangeFirstItem, req.RangeLastItem, req.RangeFirstItem, req.RangeLastItem, req.Selected, req.RangeDirection);
7444
    }
7445
}
7446
7447
static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
7448
{
7449
    ImGuiContext& g = *GImGui;
7450
    if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
7451
    {
7452
        // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
7453
        return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
7454
    }
7455
    else
7456
    {
7457
        // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)
7458
        ImRect scope_rect = window->InnerClipRect;
7459
        if (g.CurrentTable != NULL)
7460
            scope_rect = g.CurrentTable->HostClipRect;
7461
7462
        // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
7463
        scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
7464
        return scope_rect;
7465
    }
7466
}
7467
7468
// Return ImGuiMultiSelectIO structure.
7469
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
7470
// Passing 'selection_size' and 'items_count' parameters is currently optional.
7471
// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0,
7472
//    allowing a first press to clear selection THEN the second press to leave child window and return to parent.
7473
// - 'items_count' is stored in ImGuiMultiSelectIO which makes it a convenient way to pass the information to your ApplyRequest() handler (but you may pass it differently).
7474
// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1.
7475
//   - If you can easily tell if your selection is empty or not, you may pass 0/1, or you may enable ImGuiMultiSelectFlags_ClearOnEscape flag dynamically.
7476
ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)
7477
{
7478
    ImGuiContext& g = *GImGui;
7479
    ImGuiWindow* window = g.CurrentWindow;
7480
7481
    if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)
7482
        g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());
7483
    ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];
7484
    IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.
7485
    g.CurrentMultiSelect = ms;
7486
    if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
7487
        flags |= ImGuiMultiSelectFlags_ScopeWindow;
7488
    if (flags & ImGuiMultiSelectFlags_SingleSelect)
7489
        flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);
7490
    if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
7491
        flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;
7492
7493
    // FIXME: BeginFocusScope()
7494
    const ImGuiID id = window->IDStack.back();
7495
    ms->Clear();
7496
    ms->FocusScopeId = id;
7497
    ms->Flags = flags;
7498
    ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);
7499
    ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
7500
    ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;
7501
    PushFocusScope(ms->FocusScopeId);
7502
    if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.
7503
        window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
7504
7505
    // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.
7506
    ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;
7507
    if (flags & ImGuiMultiSelectFlags_NoRangeSelect)
7508
        ms->KeyMods &= ~ImGuiMod_Shift;
7509
7510
    // Bind storage
7511
    ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);
7512
    storage->ID = id;
7513
    storage->LastFrameActive = g.FrameCount;
7514
    storage->LastSelectionSize = selection_size;
7515
    storage->Window = window;
7516
    ms->Storage = storage;
7517
7518
    // Output to user
7519
    ms->IO.Requests.resize(0);
7520
    ms->IO.RangeSrcItem = storage->RangeSrcItem;
7521
    ms->IO.NavIdItem = storage->NavIdItem;
7522
    ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
7523
    ms->IO.ItemsCount = items_count;
7524
7525
    // Clear when using Navigation to move within the scope
7526
    // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
7527
    bool request_clear = false;
7528
    bool request_select_all = false;
7529
    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
7530
    {
7531
        if (ms->KeyMods & ImGuiMod_Shift)
7532
            ms->IsKeyboardSetRange = true;
7533
        if (ms->IsKeyboardSetRange)
7534
            IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
7535
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
7536
            request_clear = true;
7537
    }
7538
    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
7539
    {
7540
        // Also clear on leaving scope (may be optional?)
7541
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
7542
            request_clear = true;
7543
    }
7544
7545
    // Box-select handling: update active state.
7546
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7547
    if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7548
    {
7549
        ms->BoxSelectId = GetID("##BoxSelect");
7550
        if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))
7551
            request_clear |= bs->RequestClear;
7552
    }
7553
7554
    if (ms->IsFocused)
7555
    {
7556
        // Shortcut: Clear selection (Escape)
7557
        // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window.
7558
        // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.
7559
        if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
7560
        {
7561
            if (selection_size != 0 || bs->IsActive)
7562
                if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))
7563
                {
7564
                    request_clear = true;
7565
                    if (bs->IsActive)
7566
                        BoxSelectDeactivateDrag(bs);
7567
                }
7568
        }
7569
7570
        // Shortcut: Select all (CTRL+A)
7571
        if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
7572
            if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
7573
                request_select_all = true;
7574
    }
7575
7576
    if (request_clear || request_select_all)
7577
    {
7578
        MultiSelectAddSetAll(ms, request_select_all);
7579
        if (!request_select_all)
7580
            storage->LastSelectionSize = 0;
7581
    }
7582
    ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
7583
    ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
7584
7585
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
7586
        DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
7587
7588
    return &ms->IO;
7589
}
7590
7591
// Return updated ImGuiMultiSelectIO structure.
7592
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
7593
ImGuiMultiSelectIO* ImGui::EndMultiSelect()
7594
{
7595
    ImGuiContext& g = *GImGui;
7596
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
7597
    ImGuiMultiSelectState* storage = ms->Storage;
7598
    ImGuiWindow* window = g.CurrentWindow;
7599
    IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!");
7600
    IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
7601
    IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);
7602
7603
    ImRect scope_rect = CalcScopeRect(ms, window);
7604
    if (ms->IsFocused)
7605
    {
7606
        // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
7607
        if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure)
7608
        {
7609
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
7610
            storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
7611
        }
7612
        if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)
7613
        {
7614
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n");
7615
            storage->NavIdItem = ImGuiSelectionUserData_Invalid;
7616
            storage->NavIdSelected = -1;
7617
        }
7618
7619
        if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))
7620
            EndBoxSelect(scope_rect, ms->Flags);
7621
    }
7622
7623
    if (ms->IsEndIO == false)
7624
        ms->IO.Requests.resize(0);
7625
7626
    // Clear selection when clicking void?
7627
    // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
7628
    // The InnerRect test is necessary for non-child/decorated windows.
7629
    bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);
7630
    if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
7631
        scope_hovered &= scope_rect.Contains(g.IO.MousePos);
7632
    if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
7633
    {
7634
        if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7635
        {
7636
            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
7637
            {
7638
                BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
7639
                FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
7640
                SetHoveredID(ms->BoxSelectId);
7641
                if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
7642
                    SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select.
7643
            }
7644
        }
7645
7646
        if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
7647
            if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
7648
                MultiSelectAddSetAll(ms, false);
7649
    }
7650
7651
    // Courtesy nav wrapping helper flag
7652
    if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
7653
    {
7654
        IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
7655
        ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
7656
    }
7657
7658
    // Unwind
7659
    window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
7660
    PopFocusScope();
7661
7662
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
7663
        DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO);
7664
7665
    ms->FocusScopeId = 0;
7666
    ms->Flags = ImGuiMultiSelectFlags_None;
7667
    g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;
7668
7669
    return &ms->IO;
7670
}
7671
7672
void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
7673
{
7674
    // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
7675
    // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
7676
    ImGuiContext& g = *GImGui;
7677
    g.NextItemData.SelectionUserData = selection_user_data;
7678
    g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;
7679
7680
    if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
7681
    {
7682
        // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
7683
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
7684
        if (ms->IO.RangeSrcItem == selection_user_data)
7685
            ms->RangeSrcPassedBy = true;
7686
    }
7687
    else
7688
    {
7689
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
7690
    }
7691
}
7692
7693
// In charge of:
7694
// - Applying SetAll for submitted items.
7695
// - Applying SetRange for submitted items and record end points.
7696
// - Altering button behavior flags to facilitate use with drag and drop.
7697
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)
7698
{
7699
    ImGuiContext& g = *GImGui;
7700
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
7701
7702
    bool selected = *p_selected;
7703
    if (ms->IsFocused)
7704
    {
7705
        ImGuiMultiSelectState* storage = ms->Storage;
7706
        ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
7707
        IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
7708
7709
        // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().
7710
        // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
7711
        // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()
7712
        if (ms->LoopRequestSetAll != -1)
7713
            selected = (ms->LoopRequestSetAll == 1);
7714
7715
        // When using SHIFT+Nav: because it can incur scrolling we cannot afford a frame of lag with the selection highlight (otherwise scrolling would happen before selection)
7716
        // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function)
7717
        if (ms->IsKeyboardSetRange)
7718
        {
7719
            IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);
7720
            const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.
7721
            if (is_range_dst)
7722
                ms->RangeDstPassedBy = true;
7723
            if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst
7724
            {
7725
                storage->RangeSrcItem = item_data;
7726
                storage->RangeSelected = selected ? 1 : 0;
7727
            }
7728
            const bool is_range_src = storage->RangeSrcItem == item_data;
7729
            if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)
7730
            {
7731
                // Apply range-select value to visible items
7732
                IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);
7733
                selected = (storage->RangeSelected != 0);
7734
            }
7735
            else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
7736
            {
7737
                // Clear other items
7738
                selected = false;
7739
            }
7740
        }
7741
        *p_selected = selected;
7742
    }
7743
7744
    // Alter button behavior flags
7745
    // To handle drag and drop of multiple items we need to avoid clearing selection on click.
7746
    // Enabling this test makes actions using CTRL+SHIFT delay their effect on MouseUp which is annoying, but it allows drag and drop of multiple items.
7747
    if (p_button_flags != NULL)
7748
    {
7749
        ImGuiButtonFlags button_flags = *p_button_flags;
7750
        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
7751
        if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
7752
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
7753
        else
7754
            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
7755
        *p_button_flags = button_flags;
7756
    }
7757
}
7758
7759
// In charge of:
7760
// - Auto-select on navigation.
7761
// - Box-select toggle handling.
7762
// - Right-click handling.
7763
// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.
7764
// - Record current selection state for RangeSrc
7765
// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite.
7766
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
7767
{
7768
    ImGuiContext& g = *GImGui;
7769
    ImGuiWindow* window = g.CurrentWindow;
7770
7771
    bool selected = *p_selected;
7772
    bool pressed = *p_pressed;
7773
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
7774
    ImGuiMultiSelectState* storage = ms->Storage;
7775
    if (pressed)
7776
        ms->IsFocused = true;
7777
7778
    bool hovered = false;
7779
    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)
7780
        hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
7781
    if (!ms->IsFocused && !hovered)
7782
        return;
7783
7784
    ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
7785
7786
    ImGuiMultiSelectFlags flags = ms->Flags;
7787
    const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;
7788
    bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;
7789
    bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;
7790
7791
    bool apply_to_range_src = false;
7792
7793
    if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
7794
        apply_to_range_src = true;
7795
    if (ms->IsEndIO == false)
7796
    {
7797
        ms->IO.Requests.resize(0);
7798
        ms->IsEndIO = true;
7799
    }
7800
7801
    // Auto-select as you navigate a list
7802
    if (g.NavJustMovedToId == id)
7803
    {
7804
        if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
7805
        {
7806
            if (is_ctrl && is_shift)
7807
                pressed = true;
7808
            else if (!is_ctrl)
7809
                selected = pressed = true;
7810
        }
7811
        else
7812
        {
7813
            // With NoAutoSelect, using Shift+keyboard performs a write/copy
7814
            if (is_shift)
7815
                pressed = true;
7816
            else if (!is_ctrl)
7817
                apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this
7818
        }
7819
    }
7820
7821
    if (apply_to_range_src)
7822
    {
7823
        storage->RangeSrcItem = item_data;
7824
        storage->RangeSelected = selected; // Will be updated at the end of this function anyway.
7825
    }
7826
7827
    // Box-select toggle handling
7828
    if (ms->BoxSelectId != 0)
7829
        if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
7830
        {
7831
            const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
7832
            const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);
7833
            if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
7834
            {
7835
                if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
7836
                {
7837
                    pressed = true; // First item act as a pressed: code below will emit selection request and set NavId (whatever we emit here will be overridden anyway)
7838
                    bs->IsStartedSetNavIdOnce = false;
7839
                }
7840
                else
7841
                {
7842
                    selected = !selected;
7843
                    MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
7844
                }
7845
                storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
7846
            }
7847
        }
7848
7849
    // Right-click handling.
7850
    // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816
7851
    if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
7852
    {
7853
        if (g.ActiveId != 0 && g.ActiveId != id)
7854
            ClearActiveID();
7855
        SetFocusID(id, window);
7856
        if (!pressed && !selected)
7857
        {
7858
            pressed = true;
7859
            is_ctrl = is_shift = false;
7860
        }
7861
    }
7862
7863
    // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected.
7864
    // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something
7865
    // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional?
7866
    const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);
7867
7868
    // Alter selection
7869
    if (pressed && (!enter_pressed || !selected))
7870
    {
7871
        // Box-select
7872
        ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
7873
        if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7874
            if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
7875
                BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
7876
7877
        //----------------------------------------------------------------------------------------
7878
        // ACTION                      | Begin  | Pressed/Activated  | End
7879
        //----------------------------------------------------------------------------------------
7880
        // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1
7881
        // Keys Navigated: Ctrl        | n/a    | n/a
7882
        // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
7883
        // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst
7884
        // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1
7885
        // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1
7886
        // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1
7887
        //----------------------------------------------------------------------------------------
7888
        // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1
7889
        // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1
7890
        // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
7891
        // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst
7892
        //----------------------------------------------------------------------------------------
7893
7894
        if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
7895
        {
7896
            bool request_clear = false;
7897
            if (is_singleselect)
7898
                request_clear = true;
7899
            else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
7900
                request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;
7901
            else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
7902
                request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
7903
            if (request_clear)
7904
                MultiSelectAddSetAll(ms, false);
7905
        }
7906
7907
        int range_direction;
7908
        bool range_selected;
7909
        if (is_shift && !is_singleselect)
7910
        {
7911
            //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
7912
            if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
7913
                storage->RangeSrcItem = item_data;
7914
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
7915
            {
7916
                // Shift+Arrow always select
7917
                // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)
7918
                range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
7919
            }
7920
            else
7921
            {
7922
                // Shift+Arrow copy source selection state
7923
                // Shift+Click always copy from target selection state
7924
                if (ms->IsKeyboardSetRange)
7925
                    range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
7926
                else
7927
                    range_selected = !selected;
7928
            }
7929
            range_direction = ms->RangeSrcPassedBy ? +1 : -1;
7930
        }
7931
        else
7932
        {
7933
            // Ctrl inverts selection, otherwise always select
7934
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
7935
                selected = is_ctrl ? !selected : true;
7936
            else
7937
                selected = !selected;
7938
            storage->RangeSrcItem = item_data;
7939
            range_selected = selected;
7940
            range_direction = +1;
7941
        }
7942
        MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);
7943
    }
7944
7945
    // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)
7946
    if (storage->RangeSrcItem == item_data)
7947
        storage->RangeSelected = selected ? 1 : 0;
7948
7949
    // Update/store the selection state of focused item
7950
    if (g.NavId == id)
7951
    {
7952
        storage->NavIdItem = item_data;
7953
        storage->NavIdSelected = selected ? 1 : 0;
7954
    }
7955
    if (storage->NavIdItem == item_data)
7956
        ms->NavIdPassedBy = true;
7957
    ms->LastSubmittedItem = item_data;
7958
7959
    *p_selected = selected;
7960
    *p_pressed = pressed;
7961
}
7962
7963
void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
7964
{
7965
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };
7966
    ms->IO.Requests.resize(0);      // Can always clear previous requests
7967
    ms->IO.Requests.push_back(req); // Add new request
7968
}
7969
7970
void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
7971
{
7972
    // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
7973
    if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
7974
    {
7975
        ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
7976
        if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)
7977
        {
7978
            prev->RangeLastItem = last_item;
7979
            return;
7980
        }
7981
    }
7982
7983
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };
7984
    ms->IO.Requests.push_back(req); // Add new request
7985
}
7986
7987
void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
7988
{
7989
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
7990
    const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
7991
    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
7992
    bool open = TreeNode((void*)(intptr_t)storage->ID, "MultiSelect 0x%08X in '%s'%s", storage->ID, storage->Window ? storage->Window->Name : "N/A", is_active ? "" : " *Inactive*");
7993
    if (!is_active) { PopStyleColor(); }
7994
    if (!open)
7995
        return;
7996
    Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);
7997
    Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);
7998
    Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user
7999
    TreePop();
8000
#else
8001
    IM_UNUSED(storage);
8002
#endif
8003
}
8004
8005
//-------------------------------------------------------------------------
8006
// [SECTION] Widgets: Multi-Select helpers
8007
//-------------------------------------------------------------------------
8008
// - ImGuiSelectionBasicStorage
8009
// - ImGuiSelectionExternalStorage
8010
//-------------------------------------------------------------------------
8011
8012
ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()
8013
{
8014
    Size = 0;
8015
    PreserveOrder = false;
8016
    UserData = NULL;
8017
    AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };
8018
    _SelectionOrder = 1; // Always >0
8019
}
8020
8021
void ImGuiSelectionBasicStorage::Clear()
8022
{
8023
    Size = 0;
8024
    _SelectionOrder = 1; // Always >0
8025
    _Storage.Data.resize(0);
8026
}
8027
8028
void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)
8029
{
8030
    ImSwap(Size, r.Size);
8031
    ImSwap(_SelectionOrder, r._SelectionOrder);
8032
    _Storage.Data.swap(r._Storage.Data);
8033
}
8034
8035
bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const
8036
{
8037
    return _Storage.GetInt(id, 0) != 0;
8038
}
8039
8040
static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)
8041
{
8042
    int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;
8043
    int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;
8044
    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
8045
}
8046
8047
// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.
8048
// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector<ImGuiID> instead of ImGuiStorage...)
8049
bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)
8050
{
8051
    ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;
8052
    ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;
8053
    if (PreserveOrder && it == NULL && it_end != NULL)
8054
        ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()
8055
    if (it == NULL)
8056
        it = _Storage.Data.Data;
8057
    IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
8058
    if (it != it_end)
8059
        while (it->val_i == 0 && it < it_end)
8060
            it++;
8061
    const bool has_more = (it != it_end);
8062
    *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
8063
    *out_id = has_more ? it->key : 0;
8064
    if (PreserveOrder && !has_more)
8065
        _Storage.BuildSortByKey();
8066
    return has_more;
8067
}
8068
8069
void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
8070
{
8071
    int* p_int = _Storage.GetIntRef(id, 0);
8072
    if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }
8073
    else if (!selected && *p_int != 0) { *p_int = 0; Size--; }
8074
}
8075
8076
// Optimized for batch edits (with same value of 'selected')
8077
static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order)
8078
{
8079
    ImGuiStorage* storage = &selection->_Storage;
8080
    ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
8081
    const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);
8082
    if (selected == (is_contained && it->val_i != 0))
8083
        return;
8084
    if (selected && !is_contained)
8085
        storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()
8086
    else if (is_contained)
8087
        it->val_i = selected ? selection_order : 0; // Modify in-place.
8088
    selection->Size += selected ? +1 : -1;
8089
}
8090
8091
static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
8092
{
8093
    ImGuiStorage* storage = &selection->_Storage;
8094
    if (selected && selection->Size != size_before_amends)
8095
        storage->BuildSortByKey(); // When done selecting: sort everything
8096
}
8097
8098
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
8099
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
8100
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
8101
//   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
8102
//   - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform
8103
//     a lookup in order to have some way to iterate/interpolate between two items.
8104
// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices
8105
//   and constructing a view index <> object id/ptr data structure anyway.
8106
// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC.
8107
// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
8108
// The most simple implementation (using indices everywhere) would look like:
8109
//   for (ImGuiSelectionRequest& req : ms_io->Requests)
8110
//   {
8111
//      if (req.Type == ImGuiSelectionRequestType_SetAll)    { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } }
8112
//      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } }
8113
//   }
8114
void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
8115
{
8116
    // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.
8117
    // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().
8118
    // Other scheme may handle SetAll differently.
8119
    IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
8120
    IM_ASSERT(AdapterIndexToStorageId != NULL);
8121
8122
    // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)
8123
    // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
8124
    // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.
8125
    // - A more optimal version wouldn't even use ImGuiStorage but directly a ImVector<ImGuiID> to reduce bandwidth, but this is a reasonable trade off to reuse code.
8126
    // - There are many ways this could be better optimized. The worse case scenario being: using BoxSelect2d in a grid, box-select scrolling down while wiggling
8127
    //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.)
8128
    // FIXME-OPT: For each block of consecutive SetRange request:
8129
    // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.
8130
    // - rewrite sorted storage a single time.
8131
    for (ImGuiSelectionRequest& req : ms_io->Requests)
8132
    {
8133
        if (req.Type == ImGuiSelectionRequestType_SetAll)
8134
        {
8135
            Clear();
8136
            if (req.Selected)
8137
            {
8138
                _Storage.Data.reserve(ms_io->ItemsCount);
8139
                const int size_before_amends = _Storage.Data.Size;
8140
                for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)
8141
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);
8142
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
8143
            }
8144
        }
8145
        else if (req.Type == ImGuiSelectionRequestType_SetRange)
8146
        {
8147
            const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;
8148
            //ImGuiContext& g = *GImGui; IMGUI_DEBUG_LOG_SELECTION("Req %d/%d: set %d to %d\n", ms_io->Requests.index_from_ptr(&req), ms_io->Requests.Size, selection_changes, req.Selected);
8149
            if (selection_changes == 1 || (selection_changes < Size / 100))
8150
            {
8151
                // Multiple sorted insertion + copy likely to be faster.
8152
                // Technically we could do a single copy with a little more work (sort sequential SetRange requests)
8153
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
8154
                    SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
8155
            }
8156
            else
8157
            {
8158
                // Append insertion + single sort likely be faster.
8159
                // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1
8160
                const int size_before_amends = _Storage.Data.Size;
8161
                int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);
8162
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)
8163
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);
8164
                if (req.Selected)
8165
                    _SelectionOrder += selection_changes;
8166
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
8167
            }
8168
        }
8169
    }
8170
}
8171
8172
//-------------------------------------------------------------------------
8173
8174
ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()
8175
{
8176
    UserData = NULL;
8177
    AdapterSetItemSelected = NULL;
8178
}
8179
8180
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
8181
// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage
8182
// This makes no assumption about underlying storage.
8183
void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
8184
{
8185
    IM_ASSERT(AdapterSetItemSelected);
8186
    for (ImGuiSelectionRequest& req : ms_io->Requests)
8187
    {
8188
        if (req.Type == ImGuiSelectionRequestType_SetAll)
8189
            for (int idx = 0; idx < ms_io->ItemsCount; idx++)
8190
                AdapterSetItemSelected(this, idx, req.Selected);
8191
        if (req.Type == ImGuiSelectionRequestType_SetRange)
8192
            for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
8193
                AdapterSetItemSelected(this, idx, req.Selected);
8194
    }
8195
}
8196
8197
//-------------------------------------------------------------------------
8198
// [SECTION] Widgets: ListBox
8199
//-------------------------------------------------------------------------
8200
// - BeginListBox()
8201
// - EndListBox()
8202
// - ListBox()
8203
//-------------------------------------------------------------------------
8204
8205
// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
8206
// This handle some subtleties with capturing info from the label, but for 99% uses it could essentially be rewritten as:
8207
//    if (ImGui::BeginChild("...", ImVec2(ImGui::CalcItemWidth(), ImGui::GetTextLineHeight() * 7.5f), ImGuiChildFlags_FrameStyle))
8208
//        { .... }
8209
//    ImGui::EndChild();
8210
//    ImGui::SameLine();
8211
//    ImGui::AlignTextToFramePadding();
8212
//    ImGui::Text("Label");
8213
// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
8214
// Tip: If your vertical size is calculated from an item count (e.g. 10 * item_height) consider adding a fractional part to facilitate seeing scrolling boundaries (e.g. 10.25 * item_height).
8215
bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
8216
{
8217
    ImGuiContext& g = *GImGui;
8218
    ImGuiWindow* window = GetCurrentWindow();
8219
    if (window->SkipItems)
8220
        return false;
8221
8222
    const ImGuiStyle& style = g.Style;
8223
    const ImGuiID id = GetID(label);
8224
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
8225
8226
    // Size default to hold ~7.25 items.
8227
    // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
8228
    ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
8229
    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
8230
    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
8231
    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
8232
    g.NextItemData.ClearFlags();
8233
8234
    if (!IsRectVisible(bb.Min, bb.Max))
8235
    {
8236
        ItemSize(bb.GetSize(), style.FramePadding.y);
8237
        ItemAdd(bb, 0, &frame_bb);
8238
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8239
        return false;
8240
    }
8241
8242
    // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.
8243
    BeginGroup();
8244
    if (label_size.x > 0.0f)
8245
    {
8246
        ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
8247
        RenderText(label_pos, label);
8248
        window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
8249
        AlignTextToFramePadding();
8250
    }
8251
8252
    BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
8253
    return true;
8254
}
8255
8256
void ImGui::EndListBox()
8257
{
8258
    ImGuiContext& g = *GImGui;
8259
    ImGuiWindow* window = g.CurrentWindow;
8260
    IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
8261
    IM_UNUSED(window);
8262
8263
    EndChild();
8264
    EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
8265
}
8266
8267
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
8268
{
8269
    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
8270
    return value_changed;
8271
}
8272
8273
// This is merely a helper around BeginListBox(), EndListBox().
8274
// Considering using those directly to submit custom data or store selection differently.
8275
bool ImGui::ListBox(const char* label, int* current_item, const char* (*getter)(void* user_data, int idx), void* user_data, int items_count, int height_in_items)
8276
{
8277
    ImGuiContext& g = *GImGui;
8278
8279
    // Calculate size from "height_in_items"
8280
    if (height_in_items < 0)
8281
        height_in_items = ImMin(items_count, 7);
8282
    float height_in_items_f = height_in_items + 0.25f;
8283
    ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
8284
8285
    if (!BeginListBox(label, size))
8286
        return false;
8287
8288
    // Assume all items have even height (= 1 line of text). If you need items of different height,
8289
    // you can create a custom version of ListBox() in your code without using the clipper.
8290
    bool value_changed = false;
8291
    ImGuiListClipper clipper;
8292
    clipper.Begin(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.
8293
    clipper.IncludeItemByIndex(*current_item);
8294
    while (clipper.Step())
8295
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
8296
        {
8297
            const char* item_text = getter(user_data, i);
8298
            if (item_text == NULL)
8299
                item_text = "*Unknown item*";
8300
8301
            PushID(i);
8302
            const bool item_selected = (i == *current_item);
8303
            if (Selectable(item_text, item_selected))
8304
            {
8305
                *current_item = i;
8306
                value_changed = true;
8307
            }
8308
            if (item_selected)
8309
                SetItemDefaultFocus();
8310
            PopID();
8311
        }
8312
    EndListBox();
8313
8314
    if (value_changed)
8315
        MarkItemEdited(g.LastItemData.ID);
8316
8317
    return value_changed;
8318
}
8319
8320
//-------------------------------------------------------------------------
8321
// [SECTION] Widgets: PlotLines, PlotHistogram
8322
//-------------------------------------------------------------------------
8323
// - PlotEx() [Internal]
8324
// - PlotLines()
8325
// - PlotHistogram()
8326
//-------------------------------------------------------------------------
8327
// Plot/Graph widgets are not very good.
8328
// Consider writing your own, or using a third-party one, see:
8329
// - ImPlot https://github.com/epezent/implot
8330
// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
8331
//-------------------------------------------------------------------------
8332
8333
int 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, const ImVec2& size_arg)
8334
{
8335
    ImGuiContext& g = *GImGui;
8336
    ImGuiWindow* window = GetCurrentWindow();
8337
    if (window->SkipItems)
8338
        return -1;
8339
8340
    const ImGuiStyle& style = g.Style;
8341
    const ImGuiID id = window->GetID(label);
8342
8343
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
8344
    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
8345
8346
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
8347
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
8348
    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));
8349
    ItemSize(total_bb, style.FramePadding.y);
8350
    if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav))
8351
        return -1;
8352
    bool hovered;
8353
    ButtonBehavior(frame_bb, id, &hovered, NULL);
8354
8355
    // Determine scale from values if not specified
8356
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
8357
    {
8358
        float v_min = FLT_MAX;
8359
        float v_max = -FLT_MAX;
8360
        for (int i = 0; i < values_count; i++)
8361
        {
8362
            const float v = values_getter(data, i);
8363
            if (v != v) // Ignore NaN values
8364
                continue;
8365
            v_min = ImMin(v_min, v);
8366
            v_max = ImMax(v_max, v);
8367
        }
8368
        if (scale_min == FLT_MAX)
8369
            scale_min = v_min;
8370
        if (scale_max == FLT_MAX)
8371
            scale_max = v_max;
8372
    }
8373
8374
    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
8375
8376
    const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
8377
    int idx_hovered = -1;
8378
    if (values_count >= values_count_min)
8379
    {
8380
        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
8381
        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
8382
8383
        // Tooltip on hover
8384
        if (hovered && inner_bb.Contains(g.IO.MousePos))
8385
        {
8386
            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
8387
            const int v_idx = (int)(t * item_count);
8388
            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
8389
8390
            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
8391
            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
8392
            if (plot_type == ImGuiPlotType_Lines)
8393
                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
8394
            else if (plot_type == ImGuiPlotType_Histogram)
8395
                SetTooltip("%d: %8.4g", v_idx, v0);
8396
            idx_hovered = v_idx;
8397
        }
8398
8399
        const float t_step = 1.0f / (float)res_w;
8400
        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
8401
8402
        float v0 = values_getter(data, (0 + values_offset) % values_count);
8403
        float t0 = 0.0f;
8404
        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
8405
        float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f);   // Where does the zero line stands
8406
8407
        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
8408
        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
8409
8410
        for (int n = 0; n < res_w; n++)
8411
        {
8412
            const float t1 = t0 + t_step;
8413
            const int v1_idx = (int)(t0 * item_count + 0.5f);
8414
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
8415
            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
8416
            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
8417
8418
            // 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.
8419
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
8420
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
8421
            if (plot_type == ImGuiPlotType_Lines)
8422
            {
8423
                window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
8424
            }
8425
            else if (plot_type == ImGuiPlotType_Histogram)
8426
            {
8427
                if (pos1.x >= pos0.x + 2.0f)
8428
                    pos1.x -= 1.0f;
8429
                window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
8430
            }
8431
8432
            t0 = t1;
8433
            tp0 = tp1;
8434
        }
8435
    }
8436
8437
    // Text overlay
8438
    if (overlay_text)
8439
        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));
8440
8441
    if (label_size.x > 0.0f)
8442
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
8443
8444
    // Return hovered index or -1 if none are hovered.
8445
    // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx().
8446
    return idx_hovered;
8447
}
8448
8449
struct ImGuiPlotArrayGetterData
8450
{
8451
    const float* Values;
8452
    int Stride;
8453
8454
    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
8455
};
8456
8457
static float Plot_ArrayGetter(void* data, int idx)
8458
{
8459
    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
8460
    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
8461
    return v;
8462
}
8463
8464
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)
8465
{
8466
    ImGuiPlotArrayGetterData data(values, stride);
8467
    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
8468
}
8469
8470
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)
8471
{
8472
    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
8473
}
8474
8475
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)
8476
{
8477
    ImGuiPlotArrayGetterData data(values, stride);
8478
    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
8479
}
8480
8481
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)
8482
{
8483
    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
8484
}
8485
8486
//-------------------------------------------------------------------------
8487
// [SECTION] Widgets: Value helpers
8488
// Those is not very useful, legacy API.
8489
//-------------------------------------------------------------------------
8490
// - Value()
8491
//-------------------------------------------------------------------------
8492
8493
void ImGui::Value(const char* prefix, bool b)
8494
{
8495
    Text("%s: %s", prefix, (b ? "true" : "false"));
8496
}
8497
8498
void ImGui::Value(const char* prefix, int v)
8499
{
8500
    Text("%s: %d", prefix, v);
8501
}
8502
8503
void ImGui::Value(const char* prefix, unsigned int v)
8504
{
8505
    Text("%s: %d", prefix, v);
8506
}
8507
8508
void ImGui::Value(const char* prefix, float v, const char* float_format)
8509
{
8510
    if (float_format)
8511
    {
8512
        char fmt[64];
8513
        ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
8514
        Text(fmt, prefix, v);
8515
    }
8516
    else
8517
    {
8518
        Text("%s: %.3f", prefix, v);
8519
    }
8520
}
8521
8522
//-------------------------------------------------------------------------
8523
// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
8524
//-------------------------------------------------------------------------
8525
// - ImGuiMenuColumns [Internal]
8526
// - BeginMenuBar()
8527
// - EndMenuBar()
8528
// - BeginMainMenuBar()
8529
// - EndMainMenuBar()
8530
// - BeginMenu()
8531
// - EndMenu()
8532
// - MenuItemEx() [Internal]
8533
// - MenuItem()
8534
//-------------------------------------------------------------------------
8535
8536
// Helpers for internal use
8537
void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
8538
{
8539
    if (window_reappearing)
8540
        memset(Widths, 0, sizeof(Widths));
8541
    Spacing = (ImU16)spacing;
8542
    CalcNextTotalWidth(true);
8543
    memset(Widths, 0, sizeof(Widths));
8544
    TotalWidth = NextTotalWidth;
8545
    NextTotalWidth = 0;
8546
}
8547
8548
void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
8549
{
8550
    ImU16 offset = 0;
8551
    bool want_spacing = false;
8552
    for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
8553
    {
8554
        ImU16 width = Widths[i];
8555
        if (want_spacing && width > 0)
8556
            offset += Spacing;
8557
        want_spacing |= (width > 0);
8558
        if (update_offsets)
8559
        {
8560
            if (i == 1) { OffsetLabel = offset; }
8561
            if (i == 2) { OffsetShortcut = offset; }
8562
            if (i == 3) { OffsetMark = offset; }
8563
        }
8564
        offset += width;
8565
    }
8566
    NextTotalWidth = offset;
8567
}
8568
8569
float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
8570
{
8571
    Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
8572
    Widths[1] = ImMax(Widths[1], (ImU16)w_label);
8573
    Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
8574
    Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
8575
    CalcNextTotalWidth(false);
8576
    return (float)ImMax(TotalWidth, NextTotalWidth);
8577
}
8578
8579
// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
8580
// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
8581
// Ideally we also want this to be responsible for claiming space out of the main window scrolling rectangle, in which case ImGuiWindowFlags_MenuBar will become unnecessary.
8582
// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
8583
bool ImGui::BeginMenuBar()
8584
{
8585
    ImGuiWindow* window = GetCurrentWindow();
8586
    if (window->SkipItems)
8587
        return false;
8588
    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
8589
        return false;
8590
8591
    IM_ASSERT(!window->DC.MenuBarAppending);
8592
    BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
8593
    PushID("##menubar");
8594
8595
    // 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.
8596
    // 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.
8597
    ImRect bar_rect = window->MenuBarRect();
8598
    ImRect clip_rect(ImFloor(bar_rect.Min.x + window->WindowBorderSize), ImFloor(bar_rect.Min.y + window->WindowBorderSize), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, window->WindowBorderSize))), ImFloor(bar_rect.Max.y));
8599
    clip_rect.ClipWith(window->OuterRectClipped);
8600
    PushClipRect(clip_rect.Min, clip_rect.Max, false);
8601
8602
    // We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
8603
    window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
8604
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
8605
    window->DC.IsSameLine = false;
8606
    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
8607
    window->DC.MenuBarAppending = true;
8608
    AlignTextToFramePadding();
8609
    return true;
8610
}
8611
8612
void ImGui::EndMenuBar()
8613
{
8614
    ImGuiWindow* window = GetCurrentWindow();
8615
    if (window->SkipItems)
8616
        return;
8617
    ImGuiContext& g = *GImGui;
8618
8619
    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
8620
    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
8621
    {
8622
        // Try to find out if the request is for one of our child menu
8623
        ImGuiWindow* nav_earliest_child = g.NavWindow;
8624
        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
8625
            nav_earliest_child = nav_earliest_child->ParentWindow;
8626
        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
8627
        {
8628
            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
8629
            // 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 bothering)
8630
            const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
8631
            IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
8632
            FocusWindow(window);
8633
            SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
8634
            // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto?
8635
            if (g.NavCursorVisible)
8636
            {
8637
                g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again
8638
                g.NavCursorHideFrames = 2;
8639
            }
8640
            g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;
8641
            NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
8642
        }
8643
    }
8644
8645
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
8646
    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
8647
    IM_ASSERT(window->DC.MenuBarAppending);
8648
    PopClipRect();
8649
    PopID();
8650
    window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->Pos.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
8651
8652
    // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.
8653
    ImGuiGroupData& group_data = g.GroupStack.back();
8654
    group_data.EmitItem = false;
8655
    ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
8656
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.
8657
    EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
8658
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
8659
    window->DC.IsSameLine = false;
8660
    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
8661
    window->DC.MenuBarAppending = false;
8662
    window->DC.CursorMaxPos = restore_cursor_max_pos;
8663
}
8664
8665
// Important: calling order matters!
8666
// FIXME: Somehow overlapping with docking tech.
8667
// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
8668
bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
8669
{
8670
    IM_ASSERT(dir != ImGuiDir_None);
8671
8672
    ImGuiWindow* bar_window = FindWindowByName(name);
8673
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
8674
    if (bar_window == NULL || bar_window->BeginCount == 0)
8675
    {
8676
        // Calculate and set window size/position
8677
        ImRect avail_rect = viewport->GetBuildWorkRect();
8678
        ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
8679
        ImVec2 pos = avail_rect.Min;
8680
        if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
8681
            pos[axis] = avail_rect.Max[axis] - axis_size;
8682
        ImVec2 size = avail_rect.GetSize();
8683
        size[axis] = axis_size;
8684
        SetNextWindowPos(pos);
8685
        SetNextWindowSize(size);
8686
8687
        // Report our size into work area (for next frame) using actual window size
8688
        if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
8689
            viewport->BuildWorkInsetMin[axis] += axis_size;
8690
        else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
8691
            viewport->BuildWorkInsetMax[axis] += axis_size;
8692
    }
8693
8694
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
8695
    SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
8696
    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
8697
    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
8698
    bool is_open = Begin(name, NULL, window_flags);
8699
    PopStyleVar(2);
8700
8701
    return is_open;
8702
}
8703
8704
bool ImGui::BeginMainMenuBar()
8705
{
8706
    ImGuiContext& g = *GImGui;
8707
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
8708
8709
    // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
8710
    SetCurrentViewport(NULL, viewport);
8711
8712
    // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
8713
    // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
8714
    // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
8715
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
8716
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
8717
    float height = GetFrameHeight();
8718
    bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
8719
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
8720
8721
    if (is_open)
8722
        BeginMenuBar();
8723
    else
8724
        End();
8725
    return is_open;
8726
}
8727
8728
void ImGui::EndMainMenuBar()
8729
{
8730
    EndMenuBar();
8731
8732
    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
8733
    // FIXME: With this strategy we won't be able to restore a NULL focus.
8734
    ImGuiContext& g = *GImGui;
8735
    if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest)
8736
        FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
8737
8738
    End();
8739
}
8740
8741
static bool IsRootOfOpenMenuSet()
8742
{
8743
    ImGuiContext& g = *GImGui;
8744
    ImGuiWindow* window = g.CurrentWindow;
8745
    if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
8746
        return false;
8747
8748
    // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
8749
    // (e.g. inside menu bar vs loose menu items) based on parent ID.
8750
    // This would however prevent the use of e.g. PushID() user code submitting menus.
8751
    // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
8752
    // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
8753
    // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
8754
    // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
8755
    // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer.
8756
    // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still
8757
    // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart
8758
    // it likely won't be a problem anyone runs into.
8759
    const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
8760
    if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
8761
        return false;
8762
    return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false);
8763
}
8764
8765
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
8766
{
8767
    ImGuiWindow* window = GetCurrentWindow();
8768
    if (window->SkipItems)
8769
        return false;
8770
8771
    ImGuiContext& g = *GImGui;
8772
    const ImGuiStyle& style = g.Style;
8773
    const ImGuiID id = window->GetID(label);
8774
    bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
8775
8776
    // 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)
8777
    // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation.
8778
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
8779
    if (window->Flags & ImGuiWindowFlags_ChildMenu)
8780
        window_flags |= ImGuiWindowFlags_ChildWindow;
8781
8782
    // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
8783
    // We are relying on a O(N) search - so O(N log N) over the frame - which seems like the most efficient for the expected small amount of BeginMenu() calls per frame.
8784
    // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
8785
    if (g.MenusIdSubmittedThisFrame.contains(id))
8786
    {
8787
        if (menu_is_open)
8788
            menu_is_open = BeginPopupEx(id, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
8789
        else
8790
            g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
8791
        return menu_is_open;
8792
    }
8793
8794
    // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
8795
    g.MenusIdSubmittedThisFrame.push_back(id);
8796
8797
    ImVec2 label_size = CalcTextSize(label, NULL, true);
8798
8799
    // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
8800
    // This is only done for items for the menu set and not the full parent window.
8801
    const bool menuset_is_open = IsRootOfOpenMenuSet();
8802
    if (menuset_is_open)
8803
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
8804
8805
    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
8806
    // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
8807
    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
8808
    ImVec2 popup_pos, pos = window->DC.CursorPos;
8809
    PushID(label);
8810
    if (!enabled)
8811
        BeginDisabled();
8812
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
8813
    bool pressed;
8814
8815
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
8816
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
8817
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
8818
    {
8819
        // Menu inside an horizontal menu bar
8820
        // Selectable extend their highlight by half ItemSpacing in each direction.
8821
        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
8822
        popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);
8823
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
8824
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
8825
        float w = label_size.x;
8826
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
8827
        pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
8828
        LogSetNextTextDecoration("[", "]");
8829
        RenderText(text_pos, label);
8830
        PopStyleVar();
8831
        window->DC.CursorPos.x += IM_TRUNC(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().
8832
    }
8833
    else
8834
    {
8835
        // Menu inside a regular/vertical menu
8836
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
8837
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
8838
        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
8839
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
8840
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
8841
        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
8842
        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
8843
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
8844
        pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
8845
        LogSetNextTextDecoration("", ">");
8846
        RenderText(text_pos, label);
8847
        if (icon_w > 0.0f)
8848
            RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
8849
        RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
8850
    }
8851
    if (!enabled)
8852
        EndDisabled();
8853
8854
    const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
8855
    if (menuset_is_open)
8856
        PopItemFlag();
8857
8858
    bool want_open = false;
8859
    bool want_open_nav_init = false;
8860
    bool want_close = false;
8861
    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
8862
    {
8863
        // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
8864
        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
8865
        bool moving_toward_child_menu = false;
8866
        ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
8867
        ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
8868
        if (g.HoveredWindow == window && child_menu_window != NULL)
8869
        {
8870
            const float ref_unit = g.FontSize; // FIXME-DPI
8871
            const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
8872
            const ImRect next_window_rect = child_menu_window->Rect();
8873
            ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
8874
            ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
8875
            ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
8876
            const float pad_farmost_h = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // Add a bit of extra slack.
8877
            ta.x += child_dir * -0.5f;
8878
            tb.x += child_dir * ref_unit;
8879
            tc.x += child_dir * ref_unit;
8880
            tb.y = ta.y + ImMax((tb.y - pad_farmost_h) - ta.y, -ref_unit * 8.0f); // Triangle has maximum height to limit the slope and the bias toward large sub-menus
8881
            tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
8882
            moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
8883
            //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
8884
        }
8885
8886
        // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)
8887
        // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.
8888
        // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)
8889
        if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0)
8890
            want_close = true;
8891
8892
        // Open
8893
        // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
8894
        if (!menu_is_open && pressed) // Click/activate to open
8895
            want_open = true;
8896
        else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
8897
            want_open = true;
8898
        else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)
8899
            want_open = true;
8900
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
8901
        {
8902
            want_open = want_open_nav_init = true;
8903
            NavMoveRequestCancel();
8904
            SetNavCursorVisibleAfterMove();
8905
        }
8906
    }
8907
    else
8908
    {
8909
        // Menu bar
8910
        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
8911
        {
8912
            want_close = true;
8913
            want_open = menu_is_open = false;
8914
        }
8915
        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
8916
        {
8917
            want_open = true;
8918
        }
8919
        else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
8920
        {
8921
            want_open = true;
8922
            NavMoveRequestCancel();
8923
        }
8924
    }
8925
8926
    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.. }'
8927
        want_close = true;
8928
    if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
8929
        ClosePopupToLevel(g.BeginPopupStack.Size, true);
8930
8931
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
8932
    PopID();
8933
8934
    if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
8935
    {
8936
        // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame.
8937
        OpenPopup(label);
8938
    }
8939
    else if (want_open)
8940
    {
8941
        menu_is_open = true;
8942
        OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
8943
    }
8944
8945
    if (menu_is_open)
8946
    {
8947
        ImGuiLastItemData last_item_in_parent = g.LastItemData;
8948
        SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
8949
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
8950
        menu_is_open = BeginPopupEx(id, window_flags);                  // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
8951
        PopStyleVar();
8952
        if (menu_is_open)
8953
        {
8954
            // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
8955
            // Perform an init request in the case the popup was already open (via a previous mouse hover)
8956
            if (want_open && want_open_nav_init && !g.NavInitRequest)
8957
            {
8958
                FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
8959
                NavInitWindow(g.CurrentWindow, false);
8960
            }
8961
8962
            // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
8963
            // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
8964
            g.LastItemData = last_item_in_parent;
8965
            if (g.HoveredWindow == window)
8966
                g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
8967
        }
8968
    }
8969
    else
8970
    {
8971
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8972
    }
8973
8974
    return menu_is_open;
8975
}
8976
8977
bool ImGui::BeginMenu(const char* label, bool enabled)
8978
{
8979
    return BeginMenuEx(label, NULL, enabled);
8980
}
8981
8982
void ImGui::EndMenu()
8983
{
8984
    // Nav: When a left move request our menu failed, close ourselves.
8985
    ImGuiContext& g = *GImGui;
8986
    ImGuiWindow* window = g.CurrentWindow;
8987
    IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginMenu()/EndMenu() calls
8988
    ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.
8989
    if (window->BeginCount == window->BeginCountPreviousFrame)
8990
        if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
8991
            if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
8992
            {
8993
                ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
8994
                NavMoveRequestCancel();
8995
            }
8996
8997
    EndPopup();
8998
}
8999
9000
bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
9001
{
9002
    ImGuiWindow* window = GetCurrentWindow();
9003
    if (window->SkipItems)
9004
        return false;
9005
9006
    ImGuiContext& g = *GImGui;
9007
    ImGuiStyle& style = g.Style;
9008
    ImVec2 pos = window->DC.CursorPos;
9009
    ImVec2 label_size = CalcTextSize(label, NULL, true);
9010
9011
    // See BeginMenuEx() for comments about this.
9012
    const bool menuset_is_open = IsRootOfOpenMenuSet();
9013
    if (menuset_is_open)
9014
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
9015
9016
    // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
9017
    // but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
9018
    bool pressed;
9019
    PushID(label);
9020
    if (!enabled)
9021
        BeginDisabled();
9022
9023
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
9024
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
9025
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
9026
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
9027
    {
9028
        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
9029
        // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
9030
        float w = label_size.x;
9031
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
9032
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
9033
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
9034
        pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
9035
        PopStyleVar();
9036
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
9037
            RenderText(text_pos, label);
9038
        window->DC.CursorPos.x += IM_TRUNC(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().
9039
    }
9040
    else
9041
    {
9042
        // Menu item inside a vertical menu
9043
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
9044
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.
9045
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
9046
        float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
9047
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
9048
        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
9049
        float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
9050
        pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
9051
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
9052
        {
9053
            RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
9054
            if (icon_w > 0.0f)
9055
                RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
9056
            if (shortcut_w > 0.0f)
9057
            {
9058
                PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
9059
                LogSetNextTextDecoration("(", ")");
9060
                RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
9061
                PopStyleColor();
9062
            }
9063
            if (selected)
9064
                RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
9065
        }
9066
    }
9067
    IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
9068
    if (!enabled)
9069
        EndDisabled();
9070
    PopID();
9071
    if (menuset_is_open)
9072
        PopItemFlag();
9073
9074
    return pressed;
9075
}
9076
9077
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
9078
{
9079
    return MenuItemEx(label, NULL, shortcut, selected, enabled);
9080
}
9081
9082
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
9083
{
9084
    if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
9085
    {
9086
        if (p_selected)
9087
            *p_selected = !*p_selected;
9088
        return true;
9089
    }
9090
    return false;
9091
}
9092
9093
//-------------------------------------------------------------------------
9094
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
9095
//-------------------------------------------------------------------------
9096
// - BeginTabBar()
9097
// - BeginTabBarEx() [Internal]
9098
// - EndTabBar()
9099
// - TabBarLayout() [Internal]
9100
// - TabBarCalcTabID() [Internal]
9101
// - TabBarCalcMaxTabWidth() [Internal]
9102
// - TabBarFindTabById() [Internal]
9103
// - TabBarFindTabByOrder() [Internal]
9104
// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal]
9105
// - TabBarGetCurrentTab() [Internal]
9106
// - TabBarGetTabName() [Internal]
9107
// - TabBarAddTab() [Internal]
9108
// - TabBarRemoveTab() [Internal]
9109
// - TabBarCloseTab() [Internal]
9110
// - TabBarScrollClamp() [Internal]
9111
// - TabBarScrollToTab() [Internal]
9112
// - TabBarQueueFocus() [Internal]
9113
// - TabBarQueueReorder() [Internal]
9114
// - TabBarProcessReorderFromMousePos() [Internal]
9115
// - TabBarProcessReorder() [Internal]
9116
// - TabBarScrollingButtons() [Internal]
9117
// - TabBarTabListPopupButton() [Internal]
9118
//-------------------------------------------------------------------------
9119
9120
struct ImGuiTabBarSection
9121
{
9122
    int                 TabCount;               // Number of tabs in this section.
9123
    float               Width;                  // Sum of width of tabs in this section (after shrinking down)
9124
    float               Spacing;                // Horizontal spacing at the end of the section.
9125
9126
    ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
9127
};
9128
9129
namespace ImGui
9130
{
9131
    static void             TabBarLayout(ImGuiTabBar* tab_bar);
9132
    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
9133
    static float            TabBarCalcMaxTabWidth();
9134
    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
9135
    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
9136
    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
9137
    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
9138
}
9139
9140
ImGuiTabBar::ImGuiTabBar()
9141
{
9142
    memset(this, 0, sizeof(*this));
9143
    CurrFrameVisible = PrevFrameVisible = -1;
9144
    LastTabItemIdx = -1;
9145
}
9146
9147
static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
9148
{
9149
    return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
9150
}
9151
9152
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
9153
{
9154
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
9155
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
9156
    const int a_section = TabItemGetSectionIdx(a);
9157
    const int b_section = TabItemGetSectionIdx(b);
9158
    if (a_section != b_section)
9159
        return a_section - b_section;
9160
    return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
9161
}
9162
9163
static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
9164
{
9165
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
9166
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
9167
    return (int)(a->BeginOrder - b->BeginOrder);
9168
}
9169
9170
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
9171
{
9172
    ImGuiContext& g = *GImGui;
9173
    return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
9174
}
9175
9176
static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
9177
{
9178
    ImGuiContext& g = *GImGui;
9179
    if (g.TabBars.Contains(tab_bar))
9180
        return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
9181
    return ImGuiPtrOrIndex(tab_bar);
9182
}
9183
9184
bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
9185
{
9186
    ImGuiContext& g = *GImGui;
9187
    ImGuiWindow* window = g.CurrentWindow;
9188
    if (window->SkipItems)
9189
        return false;
9190
9191
    ImGuiID id = window->GetID(str_id);
9192
    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
9193
    ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->WorkRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2);
9194
    tab_bar->ID = id;
9195
    tab_bar->SeparatorMinX = tab_bar->BarRect.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
9196
    tab_bar->SeparatorMaxX = tab_bar->BarRect.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
9197
    //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))
9198
    flags |= ImGuiTabBarFlags_IsFocused;
9199
    return BeginTabBarEx(tab_bar, tab_bar_bb, flags);
9200
}
9201
9202
bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
9203
{
9204
    ImGuiContext& g = *GImGui;
9205
    ImGuiWindow* window = g.CurrentWindow;
9206
    if (window->SkipItems)
9207
        return false;
9208
9209
    IM_ASSERT(tab_bar->ID != 0);
9210
    if ((flags & ImGuiTabBarFlags_DockNode) == 0)
9211
        PushOverrideID(tab_bar->ID);
9212
9213
    // Add to stack
9214
    g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
9215
    g.CurrentTabBar = tab_bar;
9216
    tab_bar->Window = window;
9217
9218
    // Append with multiple BeginTabBar()/EndTabBar() pairs.
9219
    tab_bar->BackupCursorPos = window->DC.CursorPos;
9220
    if (tab_bar->CurrFrameVisible == g.FrameCount)
9221
    {
9222
        window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
9223
        tab_bar->BeginCount++;
9224
        return true;
9225
    }
9226
9227
    // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
9228
    if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
9229
        if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
9230
            ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
9231
    tab_bar->TabsAddedNew = false;
9232
9233
    // Flags
9234
    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
9235
        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
9236
9237
    tab_bar->Flags = flags;
9238
    tab_bar->BarRect = tab_bar_bb;
9239
    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
9240
    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
9241
    tab_bar->CurrFrameVisible = g.FrameCount;
9242
    tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
9243
    tab_bar->CurrTabsContentsHeight = 0.0f;
9244
    tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
9245
    tab_bar->FramePadding = g.Style.FramePadding;
9246
    tab_bar->TabsActiveCount = 0;
9247
    tab_bar->LastTabItemIdx = -1;
9248
    tab_bar->BeginCount = 1;
9249
9250
    // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
9251
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
9252
9253
    // Draw separator
9254
    // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)
9255
    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);
9256
    if (g.Style.TabBarBorderSize > 0.0f)
9257
    {
9258
        const float y = tab_bar->BarRect.Max.y;
9259
        window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);
9260
    }
9261
    return true;
9262
}
9263
9264
void    ImGui::EndTabBar()
9265
{
9266
    ImGuiContext& g = *GImGui;
9267
    ImGuiWindow* window = g.CurrentWindow;
9268
    if (window->SkipItems)
9269
        return;
9270
9271
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
9272
    if (tab_bar == NULL)
9273
    {
9274
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
9275
        return;
9276
    }
9277
9278
    // Fallback in case no TabItem have been submitted
9279
    if (tab_bar->WantLayout)
9280
        TabBarLayout(tab_bar);
9281
9282
    // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
9283
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
9284
    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
9285
    {
9286
        tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
9287
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
9288
    }
9289
    else
9290
    {
9291
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
9292
    }
9293
    if (tab_bar->BeginCount > 1)
9294
        window->DC.CursorPos = tab_bar->BackupCursorPos;
9295
9296
    tab_bar->LastTabItemIdx = -1;
9297
    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
9298
        PopID();
9299
9300
    g.CurrentTabBarStack.pop_back();
9301
    g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
9302
}
9303
9304
// Scrolling happens only in the central section (leading/trailing sections are not scrolling)
9305
static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
9306
{
9307
    return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
9308
}
9309
9310
// This is called only once a frame before by the first call to ItemTab()
9311
// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
9312
static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
9313
{
9314
    ImGuiContext& g = *GImGui;
9315
    tab_bar->WantLayout = false;
9316
9317
    // Garbage collect by compacting list
9318
    // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
9319
    int tab_dst_n = 0;
9320
    bool need_sort_by_section = false;
9321
    ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
9322
    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
9323
    {
9324
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
9325
        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
9326
        {
9327
            // Remove tab
9328
            if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
9329
            if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
9330
            if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
9331
            continue;
9332
        }
9333
        if (tab_dst_n != tab_src_n)
9334
            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
9335
9336
        tab = &tab_bar->Tabs[tab_dst_n];
9337
        tab->IndexDuringLayout = (ImS16)tab_dst_n;
9338
9339
        // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
9340
        int curr_tab_section_n = TabItemGetSectionIdx(tab);
9341
        if (tab_dst_n > 0)
9342
        {
9343
            ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
9344
            int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
9345
            if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
9346
                need_sort_by_section = true;
9347
            if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
9348
                need_sort_by_section = true;
9349
        }
9350
9351
        sections[curr_tab_section_n].TabCount++;
9352
        tab_dst_n++;
9353
    }
9354
    if (tab_bar->Tabs.Size != tab_dst_n)
9355
        tab_bar->Tabs.resize(tab_dst_n);
9356
9357
    if (need_sort_by_section)
9358
        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
9359
9360
    // Calculate spacing between sections
9361
    sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
9362
    sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
9363
9364
    // Setup next selected tab
9365
    ImGuiID scroll_to_tab_id = 0;
9366
    if (tab_bar->NextSelectedTabId)
9367
    {
9368
        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
9369
        tab_bar->NextSelectedTabId = 0;
9370
        scroll_to_tab_id = tab_bar->SelectedTabId;
9371
    }
9372
9373
    // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
9374
    if (tab_bar->ReorderRequestTabId != 0)
9375
    {
9376
        if (TabBarProcessReorder(tab_bar))
9377
            if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
9378
                scroll_to_tab_id = tab_bar->ReorderRequestTabId;
9379
        tab_bar->ReorderRequestTabId = 0;
9380
    }
9381
9382
    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
9383
    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
9384
    if (tab_list_popup_button)
9385
        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
9386
            scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
9387
9388
    // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
9389
    // (whereas our tabs are stored as: leading, central, trailing)
9390
    int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
9391
    g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
9392
9393
    // Compute ideal tabs widths + store them into shrink buffer
9394
    ImGuiTabItem* most_recently_selected_tab = NULL;
9395
    int curr_section_n = -1;
9396
    bool found_selected_tab_id = false;
9397
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
9398
    {
9399
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
9400
        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
9401
9402
        if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
9403
            most_recently_selected_tab = tab;
9404
        if (tab->ID == tab_bar->SelectedTabId)
9405
            found_selected_tab_id = true;
9406
        if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
9407
            scroll_to_tab_id = tab->ID;
9408
9409
        // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
9410
        // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
9411
        // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
9412
        const char* tab_name = TabBarGetTabName(tab_bar, tab);
9413
        const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
9414
        tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
9415
9416
        int section_n = TabItemGetSectionIdx(tab);
9417
        ImGuiTabBarSection* section = &sections[section_n];
9418
        section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
9419
        curr_section_n = section_n;
9420
9421
        // Store data so we can build an array sorted by width if we need to shrink tabs down
9422
        IM_MSVC_WARNING_SUPPRESS(6385);
9423
        ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
9424
        shrink_width_item->Index = tab_n;
9425
        shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
9426
        tab->Width = ImMax(tab->ContentWidth, 1.0f);
9427
    }
9428
9429
    // Compute total ideal width (used for e.g. auto-resizing a window)
9430
    tab_bar->WidthAllTabsIdeal = 0.0f;
9431
    for (int section_n = 0; section_n < 3; section_n++)
9432
        tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
9433
9434
    // Horizontal scrolling buttons
9435
    // (note that TabBarScrollButtons() will alter BarRect.Max.x)
9436
    if ((tab_bar->WidthAllTabsIdeal > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll))
9437
        if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
9438
        {
9439
            scroll_to_tab_id = scroll_and_select_tab->ID;
9440
            if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
9441
                tab_bar->SelectedTabId = scroll_to_tab_id;
9442
        }
9443
9444
    // Shrink widths if full tabs don't fit in their allocated space
9445
    float section_0_w = sections[0].Width + sections[0].Spacing;
9446
    float section_1_w = sections[1].Width + sections[1].Spacing;
9447
    float section_2_w = sections[2].Width + sections[2].Spacing;
9448
    bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
9449
    float width_excess;
9450
    if (central_section_is_visible)
9451
        width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
9452
    else
9453
        width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
9454
9455
    // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
9456
    if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
9457
    {
9458
        int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
9459
        int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
9460
        ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess);
9461
9462
        // Apply shrunk values into tabs and sections
9463
        for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
9464
        {
9465
            ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
9466
            float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
9467
            if (shrinked_width < 0.0f)
9468
                continue;
9469
9470
            shrinked_width = ImMax(1.0f, shrinked_width);
9471
            int section_n = TabItemGetSectionIdx(tab);
9472
            sections[section_n].Width -= (tab->Width - shrinked_width);
9473
            tab->Width = shrinked_width;
9474
        }
9475
    }
9476
9477
    // Layout all active tabs
9478
    int section_tab_index = 0;
9479
    float tab_offset = 0.0f;
9480
    tab_bar->WidthAllTabs = 0.0f;
9481
    for (int section_n = 0; section_n < 3; section_n++)
9482
    {
9483
        ImGuiTabBarSection* section = &sections[section_n];
9484
        if (section_n == 2)
9485
            tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
9486
9487
        for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
9488
        {
9489
            ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
9490
            tab->Offset = tab_offset;
9491
            tab->NameOffset = -1;
9492
            tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
9493
        }
9494
        tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
9495
        tab_offset += section->Spacing;
9496
        section_tab_index += section->TabCount;
9497
    }
9498
9499
    // Clear name buffers
9500
    tab_bar->TabsNames.Buf.resize(0);
9501
9502
    // If we have lost the selected tab, select the next most recently active one
9503
    if (found_selected_tab_id == false)
9504
        tab_bar->SelectedTabId = 0;
9505
    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
9506
        scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
9507
9508
    // Lock in visible tab
9509
    tab_bar->VisibleTabId = tab_bar->SelectedTabId;
9510
    tab_bar->VisibleTabWasSubmitted = false;
9511
9512
    // CTRL+TAB can override visible tab temporarily
9513
    if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
9514
        tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
9515
9516
    // Apply request requests
9517
    if (scroll_to_tab_id != 0)
9518
        TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
9519
    else if ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
9520
    {
9521
        const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
9522
        const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
9523
        if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
9524
        {
9525
            const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
9526
            tab_bar->ScrollingTargetDistToVisibility = 0.0f;
9527
            tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
9528
        }
9529
        SetKeyOwner(wheel_key, tab_bar->ID);
9530
    }
9531
9532
    // Update scrolling
9533
    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
9534
    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
9535
    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
9536
    {
9537
        // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
9538
        // Teleport if we are aiming far off the visible line
9539
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
9540
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
9541
        const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
9542
        tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
9543
    }
9544
    else
9545
    {
9546
        tab_bar->ScrollingSpeed = 0.0f;
9547
    }
9548
    tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
9549
    tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
9550
9551
    // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
9552
    ImGuiWindow* window = g.CurrentWindow;
9553
    window->DC.CursorPos = tab_bar->BarRect.Min;
9554
    ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
9555
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
9556
}
9557
9558
// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
9559
static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
9560
{
9561
    if (docked_window != NULL)
9562
    {
9563
        IM_UNUSED(tab_bar);
9564
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
9565
        ImGuiID id = docked_window->TabId;
9566
        KeepAliveID(id);
9567
        return id;
9568
    }
9569
    else
9570
    {
9571
        ImGuiWindow* window = GImGui->CurrentWindow;
9572
        return window->GetID(label);
9573
    }
9574
}
9575
9576
static float ImGui::TabBarCalcMaxTabWidth()
9577
{
9578
    ImGuiContext& g = *GImGui;
9579
    return g.FontSize * 20.0f;
9580
}
9581
9582
ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
9583
{
9584
    if (tab_id != 0)
9585
        for (int n = 0; n < tab_bar->Tabs.Size; n++)
9586
            if (tab_bar->Tabs[n].ID == tab_id)
9587
                return &tab_bar->Tabs[n];
9588
    return NULL;
9589
}
9590
9591
// Order = visible order, not submission order! (which is tab->BeginOrder)
9592
ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
9593
{
9594
    if (order < 0 || order >= tab_bar->Tabs.Size)
9595
        return NULL;
9596
    return &tab_bar->Tabs[order];
9597
}
9598
9599
// FIXME: See references to #2304 in TODO.txt
9600
ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
9601
{
9602
    ImGuiTabItem* most_recently_selected_tab = NULL;
9603
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
9604
    {
9605
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
9606
        if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
9607
            if (tab->Window && tab->Window->WasActive)
9608
                most_recently_selected_tab = tab;
9609
    }
9610
    return most_recently_selected_tab;
9611
}
9612
9613
ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
9614
{
9615
    if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
9616
        return NULL;
9617
    return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
9618
}
9619
9620
const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
9621
{
9622
    if (tab->Window)
9623
        return tab->Window->Name;
9624
    if (tab->NameOffset == -1)
9625
        return "N/A";
9626
    IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
9627
    return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
9628
}
9629
9630
// The purpose of this call is to register tab in advance so we can control their order at the time they appear.
9631
// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
9632
void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
9633
{
9634
    ImGuiContext& g = *GImGui;
9635
    IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
9636
    IM_ASSERT(g.CurrentTabBar != tab_bar);  // Can't work while the tab bar is active as our tab doesn't have an X offset yet, in theory we could/should test something like (tab_bar->CurrFrameVisible < g.FrameCount) but we'd need to solve why triggers the commented early-out assert in BeginTabBarEx() (probably dock node going from implicit to explicit in same frame)
9637
9638
    if (!window->HasCloseButton)
9639
        tab_flags |= ImGuiTabItemFlags_NoCloseButton;       // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
9640
9641
    ImGuiTabItem new_tab;
9642
    new_tab.ID = window->TabId;
9643
    new_tab.Flags = tab_flags;
9644
    new_tab.LastFrameVisible = tab_bar->CurrFrameVisible;   // Required so BeginTabBar() doesn't ditch the tab
9645
    if (new_tab.LastFrameVisible == -1)
9646
        new_tab.LastFrameVisible = g.FrameCount - 1;
9647
    new_tab.Window = window;                                // Required so tab bar layout can compute the tab width before tab submission
9648
    tab_bar->Tabs.push_back(new_tab);
9649
}
9650
9651
// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
9652
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
9653
{
9654
    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
9655
        tab_bar->Tabs.erase(tab);
9656
    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
9657
    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
9658
    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
9659
}
9660
9661
// Called on manual closure attempt
9662
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
9663
{
9664
    if (tab->Flags & ImGuiTabItemFlags_Button)
9665
        return; // A button appended with TabItemButton().
9666
9667
    if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
9668
    {
9669
        // This will remove a frame of lag for selecting another tab on closure.
9670
        // 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
9671
        tab->WantClose = true;
9672
        if (tab_bar->VisibleTabId == tab->ID)
9673
        {
9674
            tab->LastFrameVisible = -1;
9675
            tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
9676
        }
9677
    }
9678
    else
9679
    {
9680
        // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
9681
        if (tab_bar->VisibleTabId != tab->ID)
9682
            TabBarQueueFocus(tab_bar, tab);
9683
    }
9684
}
9685
9686
static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
9687
{
9688
    scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
9689
    return ImMax(scrolling, 0.0f);
9690
}
9691
9692
// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
9693
static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
9694
{
9695
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
9696
    if (tab == NULL)
9697
        return;
9698
    if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
9699
        return;
9700
9701
    ImGuiContext& g = *GImGui;
9702
    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)
9703
    int order = TabBarGetTabOrder(tab_bar, tab);
9704
9705
    // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
9706
    float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
9707
9708
    // We make all tabs positions all relative Sections[0].Width to make code simpler
9709
    float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
9710
    float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
9711
    tab_bar->ScrollingTargetDistToVisibility = 0.0f;
9712
    if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
9713
    {
9714
        // Scroll to the left
9715
        tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
9716
        tab_bar->ScrollingTarget = tab_x1;
9717
    }
9718
    else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
9719
    {
9720
        // Scroll to the right
9721
        tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
9722
        tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
9723
    }
9724
}
9725
9726
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
9727
{
9728
    tab_bar->NextSelectedTabId = tab->ID;
9729
}
9730
9731
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name)
9732
{
9733
    IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars
9734
    ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL);
9735
    tab_bar->NextSelectedTabId = tab_id;
9736
}
9737
9738
void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
9739
{
9740
    IM_ASSERT(offset != 0);
9741
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
9742
    tab_bar->ReorderRequestTabId = tab->ID;
9743
    tab_bar->ReorderRequestOffset = (ImS16)offset;
9744
}
9745
9746
void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
9747
{
9748
    ImGuiContext& g = *GImGui;
9749
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
9750
    if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
9751
        return;
9752
9753
    const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
9754
    const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
9755
9756
    // Count number of contiguous tabs we are crossing over
9757
    const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
9758
    const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
9759
    int dst_idx = src_idx;
9760
    for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
9761
    {
9762
        // Reordered tabs must share the same section
9763
        const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
9764
        if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
9765
            break;
9766
        if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
9767
            break;
9768
        dst_idx = i;
9769
9770
        // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
9771
        const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
9772
        const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
9773
        //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
9774
        if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
9775
            break;
9776
    }
9777
9778
    if (dst_idx != src_idx)
9779
        TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
9780
}
9781
9782
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
9783
{
9784
    ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
9785
    if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
9786
        return false;
9787
9788
    //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
9789
    int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
9790
    if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
9791
        return false;
9792
9793
    // Reordered tabs must share the same section
9794
    // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
9795
    ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
9796
    if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
9797
        return false;
9798
    if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
9799
        return false;
9800
9801
    ImGuiTabItem item_tmp = *tab1;
9802
    ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
9803
    ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
9804
    const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
9805
    memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
9806
    *tab2 = item_tmp;
9807
9808
    if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
9809
        MarkIniSettingsDirty();
9810
    return true;
9811
}
9812
9813
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
9814
{
9815
    ImGuiContext& g = *GImGui;
9816
    ImGuiWindow* window = g.CurrentWindow;
9817
9818
    const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
9819
    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
9820
9821
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
9822
    //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));
9823
9824
    int select_dir = 0;
9825
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
9826
    arrow_col.w *= 0.5f;
9827
9828
    PushStyleColor(ImGuiCol_Text, arrow_col);
9829
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
9830
    PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
9831
    const float backup_repeat_delay = g.IO.KeyRepeatDelay;
9832
    const float backup_repeat_rate = g.IO.KeyRepeatRate;
9833
    g.IO.KeyRepeatDelay = 0.250f;
9834
    g.IO.KeyRepeatRate = 0.200f;
9835
    float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
9836
    window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
9837
    if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
9838
        select_dir = -1;
9839
    window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
9840
    if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
9841
        select_dir = +1;
9842
    PopItemFlag();
9843
    PopStyleColor(2);
9844
    g.IO.KeyRepeatRate = backup_repeat_rate;
9845
    g.IO.KeyRepeatDelay = backup_repeat_delay;
9846
9847
    ImGuiTabItem* tab_to_scroll_to = NULL;
9848
    if (select_dir != 0)
9849
        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
9850
        {
9851
            int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
9852
            int target_order = selected_order + select_dir;
9853
9854
            // Skip tab item buttons until another tab item is found or end is reached
9855
            while (tab_to_scroll_to == NULL)
9856
            {
9857
                // If we are at the end of the list, still scroll to make our tab visible
9858
                tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
9859
9860
                // Cross through buttons
9861
                // (even if first/last item is a button, return it so we can update the scroll)
9862
                if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
9863
                {
9864
                    target_order += select_dir;
9865
                    selected_order += select_dir;
9866
                    tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
9867
                }
9868
            }
9869
        }
9870
    window->DC.CursorPos = backup_cursor_pos;
9871
    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
9872
9873
    return tab_to_scroll_to;
9874
}
9875
9876
static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
9877
{
9878
    ImGuiContext& g = *GImGui;
9879
    ImGuiWindow* window = g.CurrentWindow;
9880
9881
    // We use g.Style.FramePadding.y to match the square ArrowButton size
9882
    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
9883
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
9884
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
9885
    tab_bar->BarRect.Min.x += tab_list_popup_button_width;
9886
9887
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
9888
    arrow_col.w *= 0.5f;
9889
    PushStyleColor(ImGuiCol_Text, arrow_col);
9890
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
9891
    bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
9892
    PopStyleColor(2);
9893
9894
    ImGuiTabItem* tab_to_select = NULL;
9895
    if (open)
9896
    {
9897
        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
9898
        {
9899
            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
9900
            if (tab->Flags & ImGuiTabItemFlags_Button)
9901
                continue;
9902
9903
            const char* tab_name = TabBarGetTabName(tab_bar, tab);
9904
            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
9905
                tab_to_select = tab;
9906
        }
9907
        EndCombo();
9908
    }
9909
9910
    window->DC.CursorPos = backup_cursor_pos;
9911
    return tab_to_select;
9912
}
9913
9914
//-------------------------------------------------------------------------
9915
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
9916
//-------------------------------------------------------------------------
9917
// - BeginTabItem()
9918
// - EndTabItem()
9919
// - TabItemButton()
9920
// - TabItemEx() [Internal]
9921
// - SetTabItemClosed()
9922
// - TabItemCalcSize() [Internal]
9923
// - TabItemBackground() [Internal]
9924
// - TabItemLabelAndCloseButton() [Internal]
9925
//-------------------------------------------------------------------------
9926
9927
bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
9928
{
9929
    ImGuiContext& g = *GImGui;
9930
    ImGuiWindow* window = g.CurrentWindow;
9931
    if (window->SkipItems)
9932
        return false;
9933
9934
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
9935
    if (tab_bar == NULL)
9936
    {
9937
        IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
9938
        return false;
9939
    }
9940
    IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0);             // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
9941
9942
    bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
9943
    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
9944
    {
9945
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
9946
        PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
9947
    }
9948
    return ret;
9949
}
9950
9951
void    ImGui::EndTabItem()
9952
{
9953
    ImGuiContext& g = *GImGui;
9954
    ImGuiWindow* window = g.CurrentWindow;
9955
    if (window->SkipItems)
9956
        return;
9957
9958
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
9959
    if (tab_bar == NULL)
9960
    {
9961
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
9962
        return;
9963
    }
9964
    IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
9965
    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
9966
    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
9967
        PopID();
9968
}
9969
9970
bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
9971
{
9972
    ImGuiContext& g = *GImGui;
9973
    ImGuiWindow* window = g.CurrentWindow;
9974
    if (window->SkipItems)
9975
        return false;
9976
9977
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
9978
    if (tab_bar == NULL)
9979
    {
9980
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
9981
        return false;
9982
    }
9983
    return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
9984
}
9985
9986
bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
9987
{
9988
    // Layout whole tab bar if not already done
9989
    ImGuiContext& g = *GImGui;
9990
    if (tab_bar->WantLayout)
9991
    {
9992
        ImGuiNextItemData backup_next_item_data = g.NextItemData;
9993
        TabBarLayout(tab_bar);
9994
        g.NextItemData = backup_next_item_data;
9995
    }
9996
    ImGuiWindow* window = g.CurrentWindow;
9997
    if (window->SkipItems)
9998
        return false;
9999
10000
    const ImGuiStyle& style = g.Style;
10001
    const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
10002
10003
    // If the user called us with *p_open == false, we early out and don't render.
10004
    // We make a call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID.
10005
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
10006
    if (p_open && !*p_open)
10007
    {
10008
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
10009
        return false;
10010
    }
10011
10012
    IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
10013
    IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
10014
10015
    // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
10016
    if (flags & ImGuiTabItemFlags_NoCloseButton)
10017
        p_open = NULL;
10018
    else if (p_open == NULL)
10019
        flags |= ImGuiTabItemFlags_NoCloseButton;
10020
10021
    // Acquire tab data
10022
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
10023
    bool tab_is_new = false;
10024
    if (tab == NULL)
10025
    {
10026
        tab_bar->Tabs.push_back(ImGuiTabItem());
10027
        tab = &tab_bar->Tabs.back();
10028
        tab->ID = id;
10029
        tab_bar->TabsAddedNew = tab_is_new = true;
10030
    }
10031
    tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
10032
10033
    // Calculate tab contents size
10034
    ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
10035
    tab->RequestedWidth = -1.0f;
10036
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)
10037
        size.x = tab->RequestedWidth = g.NextItemData.Width;
10038
    if (tab_is_new)
10039
        tab->Width = ImMax(1.0f, size.x);
10040
    tab->ContentWidth = size.x;
10041
    tab->BeginOrder = tab_bar->TabsActiveCount++;
10042
10043
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
10044
    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
10045
    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
10046
    const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
10047
    const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
10048
    tab->LastFrameVisible = g.FrameCount;
10049
    tab->Flags = flags;
10050
    tab->Window = docked_window;
10051
10052
    // Append name _WITH_ the zero-terminator
10053
    // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
10054
    if (docked_window != NULL)
10055
    {
10056
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
10057
        tab->NameOffset = -1;
10058
    }
10059
    else
10060
    {
10061
        tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
10062
        tab_bar->TabsNames.append(label, label + strlen(label) + 1);
10063
    }
10064
10065
    // Update selected tab
10066
    if (!is_tab_button)
10067
    {
10068
        if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
10069
            if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
10070
                TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
10071
        if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
10072
            TabBarQueueFocus(tab_bar, tab);
10073
    }
10074
10075
    // Lock visibility
10076
    // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
10077
    bool tab_contents_visible = (tab_bar->VisibleTabId == id);
10078
    if (tab_contents_visible)
10079
        tab_bar->VisibleTabWasSubmitted = true;
10080
10081
    // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
10082
    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
10083
        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
10084
            tab_contents_visible = true;
10085
10086
    // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
10087
    // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
10088
    if (tab_appearing && (!tab_bar_appearing || tab_is_new))
10089
    {
10090
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
10091
        if (is_tab_button)
10092
            return false;
10093
        return tab_contents_visible;
10094
    }
10095
10096
    if (tab_bar->SelectedTabId == id)
10097
        tab->LastFrameSelected = g.FrameCount;
10098
10099
    // Backup current layout position
10100
    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
10101
10102
    // Layout
10103
    const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
10104
    size.x = tab->Width;
10105
    if (is_central_section)
10106
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
10107
    else
10108
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
10109
    ImVec2 pos = window->DC.CursorPos;
10110
    ImRect bb(pos, pos + size);
10111
10112
    // 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)
10113
    const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
10114
    if (want_clip_rect)
10115
        PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
10116
10117
    ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
10118
    ItemSize(bb.GetSize(), style.FramePadding.y);
10119
    window->DC.CursorMaxPos = backup_cursor_max_pos;
10120
10121
    if (!ItemAdd(bb, id))
10122
    {
10123
        if (want_clip_rect)
10124
            PopClipRect();
10125
        window->DC.CursorPos = backup_main_cursor_pos;
10126
        return tab_contents_visible;
10127
    }
10128
10129
    // Click to Select a tab
10130
    ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
10131
    if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
10132
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
10133
    bool hovered, held;
10134
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
10135
    if (pressed && !is_tab_button)
10136
        TabBarQueueFocus(tab_bar, tab);
10137
10138
    // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
10139
    // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
10140
    if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
10141
        g.ActiveIdWindow = docked_window;
10142
10143
    // Drag and drop a single floating window node moves it
10144
    ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
10145
    const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
10146
    if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
10147
    {
10148
        // Move
10149
        StartMouseMovingWindow(docked_window);
10150
    }
10151
    else if (held && !tab_appearing && IsMouseDragging(0))
10152
    {
10153
        // Drag and drop: re-order tabs
10154
        int drag_dir = 0;
10155
        float drag_distance_from_edge_x = 0.0f;
10156
        if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
10157
        {
10158
            // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
10159
            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
10160
            {
10161
                drag_dir = -1;
10162
                drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
10163
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
10164
            }
10165
            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
10166
            {
10167
                drag_dir = +1;
10168
                drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
10169
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
10170
            }
10171
        }
10172
10173
        // Extract a Dockable window out of it's tab bar
10174
        const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking);
10175
        if (can_undock)
10176
        {
10177
            // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
10178
            bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
10179
            if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
10180
            {
10181
                float threshold_base = g.FontSize;
10182
                float threshold_x = (threshold_base * 2.2f);
10183
                float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f);
10184
                //GetForegroundDrawList()->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG]
10185
10186
                float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
10187
                if (distance_from_edge_y >= threshold_y)
10188
                    undocking_tab = true;
10189
                if (drag_distance_from_edge_x > threshold_x)
10190
                    if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1))
10191
                        undocking_tab = true;
10192
            }
10193
10194
            if (undocking_tab)
10195
            {
10196
                // Undock
10197
                // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
10198
                DockContextQueueUndockWindow(&g, docked_window);
10199
                g.MovingWindow = docked_window;
10200
                SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
10201
                g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
10202
                g.ActiveIdNoClearOnFocusLoss = true;
10203
                SetActiveIdUsingAllKeyboardKeys();
10204
            }
10205
        }
10206
    }
10207
10208
#if 0
10209
    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
10210
    {
10211
        // Enlarge tab display when hovering
10212
        bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
10213
        display_draw_list = GetForegroundDrawList(window);
10214
        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
10215
    }
10216
#endif
10217
10218
    // Render tab shape
10219
    ImDrawList* display_draw_list = window->DrawList;
10220
    const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabDimmed));
10221
    TabItemBackground(display_draw_list, bb, flags, tab_col);
10222
    if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)
10223
    {
10224
        float x_offset = IM_TRUNC(0.4f * style.TabRounding);
10225
        if (x_offset < 2.0f * g.CurrentDpiScale)
10226
            x_offset = 0.0f;
10227
        float y_offset = 1.0f * g.CurrentDpiScale;
10228
        display_draw_list->AddLine(bb.GetTL() + ImVec2(x_offset, y_offset), bb.GetTR() + ImVec2(-x_offset, y_offset), GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline), style.TabBarOverlineSize);
10229
    }
10230
    RenderNavCursor(bb, id);
10231
10232
    // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
10233
    const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
10234
    if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
10235
        TabBarQueueFocus(tab_bar, tab);
10236
10237
    if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
10238
        flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
10239
10240
    // Render tab label, process close button
10241
    const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
10242
    bool just_closed;
10243
    bool text_clipped;
10244
    TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
10245
    if (just_closed && p_open != NULL)
10246
    {
10247
        *p_open = false;
10248
        TabBarCloseTab(tab_bar, tab);
10249
    }
10250
10251
    // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
10252
    // That state is copied to window->DockTabItemStatusFlags by our caller.
10253
    if (docked_window && (hovered || g.HoveredId == close_button_id))
10254
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
10255
10256
    // Restore main window position so user can draw there
10257
    if (want_clip_rect)
10258
        PopClipRect();
10259
    window->DC.CursorPos = backup_main_cursor_pos;
10260
10261
    // Tooltip
10262
    // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
10263
    // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
10264
    // FIXME: This is a mess.
10265
    // FIXME: We may want disabled tab to still display the tooltip?
10266
    if (text_clipped && g.HoveredId == id && !held)
10267
        if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
10268
            SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
10269
10270
    IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
10271
    if (is_tab_button)
10272
        return pressed;
10273
    return tab_contents_visible;
10274
}
10275
10276
// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
10277
// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
10278
// Tabs closed by the close button will automatically be flagged to avoid this issue.
10279
void    ImGui::SetTabItemClosed(const char* label)
10280
{
10281
    ImGuiContext& g = *GImGui;
10282
    bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
10283
    if (is_within_manual_tab_bar)
10284
    {
10285
        ImGuiTabBar* tab_bar = g.CurrentTabBar;
10286
        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
10287
        if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
10288
            tab->WantClose = true; // Will be processed by next call to TabBarLayout()
10289
    }
10290
    else if (ImGuiWindow* window = FindWindowByName(label))
10291
    {
10292
        if (window->DockIsActive)
10293
            if (ImGuiDockNode* node = window->DockNode)
10294
            {
10295
                ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
10296
                TabBarRemoveTab(node->TabBar, tab_id);
10297
                window->DockTabWantClose = true;
10298
            }
10299
    }
10300
}
10301
10302
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
10303
{
10304
    ImGuiContext& g = *GImGui;
10305
    ImVec2 label_size = CalcTextSize(label, NULL, true);
10306
    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
10307
    if (has_close_button_or_unsaved_marker)
10308
        size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
10309
    else
10310
        size.x += g.Style.FramePadding.x + 1.0f;
10311
    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
10312
}
10313
10314
ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
10315
{
10316
    return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
10317
}
10318
10319
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
10320
{
10321
    // 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.
10322
    ImGuiContext& g = *GImGui;
10323
    const float width = bb.GetWidth();
10324
    IM_UNUSED(flags);
10325
    IM_ASSERT(width > 0.0f);
10326
    const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
10327
    const float y1 = bb.Min.y + 1.0f;
10328
    const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
10329
    draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
10330
    draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
10331
    draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
10332
    draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
10333
    draw_list->PathFillConvex(col);
10334
    if (g.Style.TabBorderSize > 0.0f)
10335
    {
10336
        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
10337
        draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
10338
        draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
10339
        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
10340
        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
10341
    }
10342
}
10343
10344
// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
10345
// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
10346
void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImVec2 frame_padding, const char* label, ImGuiID tab_id, ImGuiID close_button_id, bool is_contents_visible, bool* out_just_closed, bool* out_text_clipped)
10347
{
10348
    ImGuiContext& g = *GImGui;
10349
    ImVec2 label_size = CalcTextSize(label, NULL, true);
10350
10351
    if (out_just_closed)
10352
        *out_just_closed = false;
10353
    if (out_text_clipped)
10354
        *out_text_clipped = false;
10355
10356
    if (bb.GetWidth() <= 1.0f)
10357
        return;
10358
10359
    // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
10360
    // But right now if you want to alter text color of tabs this is what you need to do.
10361
#if 0
10362
    const float backup_alpha = g.Style.Alpha;
10363
    if (!is_contents_visible)
10364
        g.Style.Alpha *= 0.7f;
10365
#endif
10366
10367
    // Render text label (with clipping + alpha gradient) + unsaved marker
10368
    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);
10369
    ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
10370
10371
    // Return clipped state ignoring the close button
10372
    if (out_text_clipped)
10373
    {
10374
        *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x;
10375
        //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
10376
    }
10377
10378
    const float button_sz = g.FontSize;
10379
    const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
10380
10381
    // Close Button & Unsaved Marker
10382
    // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
10383
    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
10384
    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
10385
    //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
10386
    bool close_button_pressed = false;
10387
    bool close_button_visible = false;
10388
    if (close_button_id != 0)
10389
        if (is_contents_visible || bb.GetWidth() >= ImMax(button_sz, g.Style.TabMinWidthForCloseButton))
10390
            if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id)
10391
                close_button_visible = true;
10392
    bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x);
10393
10394
    if (close_button_visible)
10395
    {
10396
        ImGuiLastItemData last_item_backup = g.LastItemData;
10397
        if (CloseButton(close_button_id, button_pos))
10398
            close_button_pressed = true;
10399
        g.LastItemData = last_item_backup;
10400
10401
        // Close with middle mouse button
10402
        if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
10403
            close_button_pressed = true;
10404
    }
10405
    else if (unsaved_marker_visible)
10406
    {
10407
        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
10408
        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
10409
    }
10410
10411
    // This is all rather complicated
10412
    // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
10413
    // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
10414
    float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f;
10415
    if (close_button_visible || unsaved_marker_visible)
10416
    {
10417
        text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f);
10418
        text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f;
10419
        ellipsis_max_x = text_pixel_clip_bb.Max.x;
10420
    }
10421
    LogSetNextTextDecoration("/", "\\");
10422
    RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size);
10423
10424
#if 0
10425
    if (!is_contents_visible)
10426
        g.Style.Alpha = backup_alpha;
10427
#endif
10428
10429
    if (out_just_closed)
10430
        *out_just_closed = close_button_pressed;
10431
}
10432
10433
10434
#endif // #ifndef IMGUI_DISABLE