OXYGENHUB
◆ main openmsx / src / 3rdparty / imgui / imgui_widgets.cc
546.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
// dear imgui, v1.92.9 WIP
2
// (widgets code)
3
4
/*
5
6
Index of this file:
7
8
// [SECTION] Forward Declarations
9
// [SECTION] Widgets: Text, etc.
10
// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
11
// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
12
// [SECTION] Widgets: ComboBox
13
// [SECTION] Data Type and Data Formatting Helpers
14
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
15
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
16
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
17
// [SECTION] Widgets: InputText, InputTextMultiline
18
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
19
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
20
// [SECTION] Widgets: Selectable
21
// [SECTION] Widgets: 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"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
74
#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.
75
#pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
76
#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.
77
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
78
#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.
79
#pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
80
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
81
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
82
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
83
#pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
84
#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
85
#elif defined(__GNUC__)
86
#pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
87
#pragma GCC diagnostic ignored "-Wfloat-equal"                      // warning: comparing floating-point with '==' or '!=' is unsafe
88
#pragma GCC diagnostic ignored "-Wformat"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'
89
#pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
90
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
91
#pragma GCC diagnostic ignored "-Wdouble-promotion"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function
92
#pragma GCC diagnostic ignored "-Wstrict-overflow"                  // warning: assuming signed overflow does not occur when simplifying division / ..when changing X +- C1 cmp C2 to X cmp C2 -+ C1
93
#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
94
#pragma GCC diagnostic ignored "-Wcast-qual"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
95
#pragma GCC diagnostic ignored "-Wconversion"                       // warning: conversion to 'xxxx' from 'xxxx' may change value
96
#pragma GCC diagnostic ignored "-Wsign-conversion"                  // warning: conversion to 'xxxx' from 'xxxx' may change the sign of the result
97
#endif
98
99
//-------------------------------------------------------------------------
100
// Data
101
//-------------------------------------------------------------------------
102
103
// Widgets
104
static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
105
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.
106
107
// Those MIN/MAX values are not define because we need to point to them
108
static const signed char    IM_S8_MIN  = -128;
109
static const signed char    IM_S8_MAX  = 127;
110
static const unsigned char  IM_U8_MIN  = 0;
111
static const unsigned char  IM_U8_MAX  = 0xFF;
112
static const signed short   IM_S16_MIN = -32768;
113
static const signed short   IM_S16_MAX = 32767;
114
static const unsigned short IM_U16_MIN = 0;
115
static const unsigned short IM_U16_MAX = 0xFFFF;
116
static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
117
static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
118
static const ImU32          IM_U32_MIN = 0;
119
static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
120
#ifdef LLONG_MIN
121
static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
122
static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
123
#else
124
static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
125
static const ImS64          IM_S64_MAX = 9223372036854775807LL;
126
#endif
127
static const ImU64          IM_U64_MIN = 0;
128
#ifdef ULLONG_MAX
129
static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
130
#else
131
static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
132
#endif
133
134
//-------------------------------------------------------------------------
135
// [SECTION] Forward Declarations
136
//-------------------------------------------------------------------------
137
138
// For InputTextEx()
139
static bool     InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
140
static ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0);
141
142
//-------------------------------------------------------------------------
143
// [SECTION] Widgets: Text, etc.
144
//-------------------------------------------------------------------------
145
// - TextEx() [Internal]
146
// - TextUnformatted()
147
// - Text()
148
// - TextV()
149
// - TextColored()
150
// - TextColoredV()
151
// - TextDisabled()
152
// - TextDisabledV()
153
// - TextWrapped()
154
// - TextWrappedV()
155
// - LabelText()
156
// - LabelTextV()
157
// - BulletText()
158
// - BulletTextV()
159
//-------------------------------------------------------------------------
160
161
void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
162
{
163
    ImGuiWindow* window = GetCurrentWindow();
164
    if (window->SkipItems)
165
        return;
166
    ImGuiContext& g = *GImGui;
167
168
    // Accept null ranges
169
    if (text == text_end)
170
        text = text_end = "";
171
172
    // Calculate length
173
    const char* text_begin = text;
174
    if (text_end == NULL)
175
        text_end = text + ImStrlen(text); // FIXME-OPT
176
177
    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
178
    const float wrap_pos_x = window->DC.TextWrapPos;
179
    const bool wrap_enabled = (wrap_pos_x >= 0.0f);
180
    if (text_end - text <= 2000 || wrap_enabled)
181
    {
182
        // Common case
183
        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
184
        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
185
186
        ImRect bb(text_pos, text_pos + text_size);
187
        ItemSize(text_size, 0.0f);
188
        if (!ItemAdd(bb, 0))
189
            return;
190
191
        // Render (we don't hide text after ## in this end-user function)
192
        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
193
    }
194
    else
195
    {
196
        // Long text!
197
        // Perform manual coarse clipping to optimize for long multi-line text
198
        // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
199
        // - 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.
200
        // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
201
        const char* line = text;
202
        const float line_height = GetTextLineHeight();
203
        ImVec2 text_size(0, 0);
204
205
        // Lines to skip (can't skip when logging text)
206
        ImVec2 pos = text_pos;
207
        if (!g.LogEnabled)
208
        {
209
            int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
210
            if (lines_skippable > 0)
211
            {
212
                int lines_skipped = 0;
213
                while (line < text_end && lines_skipped < lines_skippable)
214
                {
215
                    const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
216
                    if (!line_end)
217
                        line_end = text_end;
218
                    if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
219
                        text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
220
                    line = line_end + 1;
221
                    lines_skipped++;
222
                }
223
                pos.y += lines_skipped * line_height;
224
            }
225
        }
226
227
        // Lines to render
228
        if (line < text_end)
229
        {
230
            ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
231
            while (line < text_end)
232
            {
233
                if (IsClippedEx(line_rect, 0))
234
                    break;
235
236
                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
237
                if (!line_end)
238
                    line_end = text_end;
239
                text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
240
                RenderText(pos, line, line_end, false);
241
                line = line_end + 1;
242
                line_rect.Min.y += line_height;
243
                line_rect.Max.y += line_height;
244
                pos.y += line_height;
245
            }
246
247
            // Count remaining lines
248
            int lines_skipped = 0;
249
            while (line < text_end)
250
            {
251
                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
252
                if (!line_end)
253
                    line_end = text_end;
254
                if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
255
                    text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
256
                line = line_end + 1;
257
                lines_skipped++;
258
            }
259
            pos.y += lines_skipped * line_height;
260
        }
261
        text_size.y = (pos - text_pos).y;
262
263
        ImRect bb(text_pos, text_pos + text_size);
264
        ItemSize(text_size, 0.0f);
265
        ItemAdd(bb, 0);
266
    }
267
}
268
269
// Note that all functions taking format strings in the API may be passed ("%s", text) or ("%.*s", text_len, text),
270
// which will automatically bypass the formatter.
271
void ImGui::TextUnformatted(const char* text, const char* text_end)
272
{
273
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
274
}
275
276
void ImGui::Text(const char* fmt, ...)
277
{
278
    va_list args;
279
    va_start(args, fmt);
280
    TextV(fmt, args);
281
    va_end(args);
282
}
283
284
void ImGui::TextV(const char* fmt, va_list args)
285
{
286
    ImGuiWindow* window = GetCurrentWindow();
287
    if (window->SkipItems)
288
        return;
289
290
    const char* text, *text_end;
291
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
292
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
293
}
294
295
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
296
{
297
    va_list args;
298
    va_start(args, fmt);
299
    TextColoredV(col, fmt, args);
300
    va_end(args);
301
}
302
303
void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
304
{
305
    PushStyleColor(ImGuiCol_Text, col);
306
    TextV(fmt, args);
307
    PopStyleColor();
308
}
309
310
void ImGui::TextDisabled(const char* fmt, ...)
311
{
312
    va_list args;
313
    va_start(args, fmt);
314
    TextDisabledV(fmt, args);
315
    va_end(args);
316
}
317
318
void ImGui::TextDisabledV(const char* fmt, va_list args)
319
{
320
    ImGuiContext& g = *GImGui;
321
    PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
322
    TextV(fmt, args);
323
    PopStyleColor();
324
}
325
326
void ImGui::TextWrapped(const char* fmt, ...)
327
{
328
    va_list args;
329
    va_start(args, fmt);
330
    TextWrappedV(fmt, args);
331
    va_end(args);
332
}
333
334
void ImGui::TextWrappedV(const char* fmt, va_list args)
335
{
336
    ImGuiContext& g = *GImGui;
337
    const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
338
    if (need_backup)
339
        PushTextWrapPos(0.0f);
340
    TextV(fmt, args);
341
    if (need_backup)
342
        PopTextWrapPos();
343
}
344
345
void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...)
346
{
347
    va_list args;
348
    va_start(args, fmt);
349
    TextAlignedV(align_x, size_x, fmt, args);
350
    va_end(args);
351
}
352
353
// align_x: 0.0f = left, 0.5f = center, 1.0f = right.
354
// size_x : 0.0f = shortcut for GetContentRegionAvail().x
355
// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024)
356
void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args)
357
{
358
    ImGuiWindow* window = GetCurrentWindow();
359
    if (window->SkipItems)
360
        return;
361
362
    const char* text, *text_end;
363
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
364
    const ImVec2 text_size = CalcTextSize(text, text_end);
365
    size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x;
366
367
    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
368
    ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y);
369
    ImVec2 size(ImMin(size_x, text_size.x), text_size.y);
370
    window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x);
371
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x);
372
    if (align_x > 0.0f && text_size.x < size_x)
373
        pos.x += ImTrunc((size_x - text_size.x) * align_x);
374
    RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size);
375
376
    const ImVec2 backup_max_pos = window->DC.CursorMaxPos;
377
    ItemSize(size);
378
    ItemAdd(ImRect(pos, pos + size), 0);
379
    window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up.
380
381
    if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip))
382
        SetTooltip("%.*s", (int)(text_end - text), text);
383
}
384
385
void ImGui::LabelText(const char* label, const char* fmt, ...)
386
{
387
    va_list args;
388
    va_start(args, fmt);
389
    LabelTextV(label, fmt, args);
390
    va_end(args);
391
}
392
393
// Add a label+text combo aligned to other label+value widgets
394
void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
395
{
396
    ImGuiWindow* window = GetCurrentWindow();
397
    if (window->SkipItems)
398
        return;
399
400
    ImGuiContext& g = *GImGui;
401
    const ImGuiStyle& style = g.Style;
402
    const float w = CalcItemWidth();
403
404
    const char* value_text_begin, *value_text_end;
405
    ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
406
    const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
407
    const char* label_end = FindRenderedTextEnd(label);
408
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
409
410
    const ImVec2 pos = window->DC.CursorPos;
411
    const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
412
    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));
413
    ItemSize(total_bb, style.FramePadding.y);
414
    if (!ItemAdd(total_bb, 0))
415
        return;
416
417
    // Render
418
    RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
419
    if (label_size.x > 0.0f)
420
        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label, label_end, false);
421
}
422
423
void ImGui::BulletText(const char* fmt, ...)
424
{
425
    va_list args;
426
    va_start(args, fmt);
427
    BulletTextV(fmt, args);
428
    va_end(args);
429
}
430
431
// Text with a little bullet aligned to the typical tree node.
432
void ImGui::BulletTextV(const char* fmt, va_list args)
433
{
434
    ImGuiWindow* window = GetCurrentWindow();
435
    if (window->SkipItems)
436
        return;
437
438
    ImGuiContext& g = *GImGui;
439
    const ImGuiStyle& style = g.Style;
440
441
    const char* text_begin, *text_end;
442
    ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
443
    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
444
    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
445
    ImVec2 pos = window->DC.CursorPos;
446
    pos.y += window->DC.CurrLineTextBaseOffset;
447
    ItemSize(total_size, 0.0f);
448
    const ImRect bb(pos, pos + total_size);
449
    if (!ItemAdd(bb, 0))
450
        return;
451
452
    // Render
453
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
454
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
455
    RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
456
}
457
458
//-------------------------------------------------------------------------
459
// [SECTION] Widgets: Main
460
//-------------------------------------------------------------------------
461
// - ButtonBehavior() [Internal]
462
// - Button()
463
// - SmallButton()
464
// - InvisibleButton()
465
// - ArrowButton()
466
// - CloseButton() [Internal]
467
// - CollapseButton() [Internal]
468
// - GetWindowScrollbarID() [Internal]
469
// - GetWindowScrollbarRect() [Internal]
470
// - Scrollbar() [Internal]
471
// - ScrollbarEx() [Internal]
472
// - Image()
473
// - ImageButton()
474
// - Checkbox()
475
// - CheckboxFlagsT() [Internal]
476
// - CheckboxFlags()
477
// - RadioButton()
478
// - ProgressBar()
479
// - Bullet()
480
// - Hyperlink()
481
//-------------------------------------------------------------------------
482
483
// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
484
// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
485
// this code is a little complex.
486
// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
487
// See the series of events below and the corresponding state reported by dear imgui:
488
//------------------------------------------------------------------------------------------------------------------------------------------------
489
// with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
490
//   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
491
//   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
492
//   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
493
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
494
//   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
495
//   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
496
//   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
497
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
498
//   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
499
//------------------------------------------------------------------------------------------------------------------------------------------------
500
// with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
501
//   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
502
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
503
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
504
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
505
//------------------------------------------------------------------------------------------------------------------------------------------------
506
// with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
507
//   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
508
//   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
509
//   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
510
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
511
//------------------------------------------------------------------------------------------------------------------------------------------------
512
// with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
513
//   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
514
//   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
515
//   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
516
//   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
517
//   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
518
//   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
519
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
520
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
521
//------------------------------------------------------------------------------------------------------------------------------------------------
522
// Note that some combinations are supported,
523
// - PressedOnDragDropHold can generally be associated with any flag.
524
// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
525
//------------------------------------------------------------------------------------------------------------------------------------------------
526
// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set:
527
//                                         Repeat+                  Repeat+           Repeat+             Repeat+
528
//                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
529
//-------------------------------------------------------------------------------------------------------------------------------------------------
530
//   Frame N+0 (mouse button is down)       -                        true              -                   true
531
//   ...                                    -                        -                 -                   -
532
//   Frame N + RepeatDelay                  true                     true              -                   true
533
//   ...                                    -                        -                 -                   -
534
//   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
535
//-------------------------------------------------------------------------------------------------------------------------------------------------
536
537
// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
538
//   And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
539
//   For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
540
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
541
//   One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior()
542
//   with same ID and different MouseButton (see #8030). You can fix it by:
543
//       (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
544
//    or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
545
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
546
{
547
    ImGuiContext& g = *GImGui;
548
    ImGuiWindow* window = GetCurrentWindow();
549
550
    // Default behavior inherited from item flags
551
    // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
552
    ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
553
    if (flags & ImGuiButtonFlags_AllowOverlap)
554
        item_flags |= ImGuiItemFlags_AllowOverlap;
555
    if (item_flags & ImGuiItemFlags_NoFocus)
556
        flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus;
557
558
    // Default only reacts to left mouse button
559
    if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
560
        flags |= ImGuiButtonFlags_MouseButtonLeft;
561
562
    // Default behavior requires click + release inside bounding box
563
    if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
564
        flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_;
565
566
    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
567
    const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
568
    if (flatten_hovered_children)
569
        g.HoveredWindow = window;
570
571
#ifdef IMGUI_ENABLE_TEST_ENGINE
572
    // Alternate registration spot, for when caller didn't use ItemAdd()
573
    if (g.LastItemData.ID != id)
574
        IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
575
#endif
576
577
    bool pressed = false;
578
    bool hovered = ItemHoverable(bb, id, item_flags);
579
580
    // Special mode for Drag and Drop used by openables (tree nodes, tabs etc.)
581
    // where holding the button pressed for a long time while drag a payload item triggers the button.
582
    if (g.DragDropActive)
583
    {
584
        if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
585
        {
586
            hovered = true;
587
            SetHoveredID(id);
588
            if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
589
            {
590
                pressed = true;
591
                g.DragDropHoldJustPressedId = id;
592
                FocusWindow(window);
593
            }
594
        }
595
        if (g.DragDropAcceptIdPrev == id && (g.DragDropAcceptFlagsPrev & ImGuiDragDropFlags_AcceptDrawAsHovered))
596
            hovered = true;
597
    }
598
599
    if (flatten_hovered_children)
600
        g.HoveredWindow = backup_hovered_window;
601
602
    // Mouse handling
603
    const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
604
    if (hovered)
605
    {
606
        IM_ASSERT(id != 0); // Lazily check inside rare path.
607
608
        // Poll mouse buttons
609
        // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
610
        // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
611
        int mouse_button_clicked = -1;
612
        int mouse_button_released = -1;
613
        for (int button = 0; button < 3; button++)
614
            if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
615
            {
616
                if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
617
                if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
618
            }
619
620
        // Process initial action
621
        const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt);
622
        if (mods_ok)
623
        {
624
            if (mouse_button_clicked != -1 && g.ActiveId != id)
625
            {
626
                if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
627
                    SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
628
                if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
629
                {
630
                    SetActiveID(id, window);
631
                    g.ActiveIdMouseButton = (ImS8)mouse_button_clicked;
632
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
633
                    {
634
                        SetFocusID(id, window);
635
                        FocusWindow(window);
636
                    }
637
                    else if (!(flags & ImGuiButtonFlags_NoFocus))
638
                    {
639
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
640
                    }
641
                }
642
                if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
643
                {
644
                    pressed = true;
645
                    if (flags & ImGuiButtonFlags_NoHoldingActiveId)
646
                        ClearActiveID();
647
                    else
648
                        SetActiveID(id, window); // Hold on ID
649
                    g.ActiveIdMouseButton = (ImS8)mouse_button_clicked;
650
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
651
                    {
652
                        SetFocusID(id, window);
653
                        FocusWindow(window);
654
                    }
655
                    else if (!(flags & ImGuiButtonFlags_NoFocus))
656
                    {
657
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
658
                    }
659
                }
660
                if (flags & ImGuiButtonFlags_PressedOnRelease)
661
                {
662
                    // FIXME: Traditionally ImGuiButtonFlags_PressedOnRelease never took ActiveId. Adding it in 2026-03-20 since ImGuiButtonFlags_NoHoldingActiveId can always be added.
663
                    // We don't yet perform an explicit ClearActiveID() to reduce scope of change, but this possibility could be investigated.
664
                    if (!(flags & ImGuiButtonFlags_NoHoldingActiveId))
665
                        SetActiveID(id, window); // Hold on ID
666
                    g.ActiveIdMouseButton = (ImS8)mouse_button_clicked;
667
                }
668
            }
669
            if (flags & ImGuiButtonFlags_PressedOnRelease)
670
            {
671
                if (mouse_button_released != -1)
672
                {
673
                    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
674
                    if (!has_repeated_at_least_once)
675
                        pressed = true;
676
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
677
                        SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why.
678
                    ClearActiveID();
679
                }
680
            }
681
682
            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
683
            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
684
            if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
685
                if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))
686
                    pressed = true;
687
        }
688
689
        if (pressed && g.IO.ConfigNavCursorVisibleAuto)
690
            g.NavCursorVisible = false;
691
    }
692
693
    // Keyboard/Gamepad navigation handling
694
    // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
695
    if ((item_flags & ImGuiItemFlags_Disabled) == 0)
696
    {
697
        if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
698
            if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
699
                hovered = true;
700
        if (g.NavActivateDownId == id)
701
        {
702
            bool nav_activated_by_code = (g.NavActivateId == id);
703
            bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
704
            if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
705
            {
706
                // Avoid pressing multiple keys from triggering excessive amount of repeat events
707
                const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
708
                const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
709
                const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
710
                const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
711
                nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
712
            }
713
            if (nav_activated_by_code || nav_activated_by_inputs)
714
            {
715
                // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
716
                pressed = true;
717
                SetActiveID(id, window);
718
                g.ActiveIdSource = g.NavInputSource;
719
                if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
720
                    SetFocusID(id, window);
721
                if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
722
                    g.ActiveIdFromShortcut = true;
723
            }
724
        }
725
    }
726
727
    // Process while held
728
    bool held = false;
729
    if (g.ActiveId == id)
730
    {
731
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
732
        {
733
            if (g.ActiveIdIsJustActivated)
734
                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
735
736
            const int mouse_button = g.ActiveIdMouseButton;
737
            if (mouse_button == -1)
738
            {
739
                // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
740
                ClearActiveID();
741
            }
742
            else if (IsMouseDown(mouse_button, test_owner_id))
743
            {
744
                held = true;
745
            }
746
            else
747
            {
748
                bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
749
                bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
750
                if ((release_in || release_anywhere) && !g.DragDropActive)
751
                {
752
                    // Report as pressed when releasing the mouse (this is the most common path)
753
                    bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
754
                    bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
755
                    bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
756
                    if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
757
                        pressed = true;
758
                }
759
                ClearActiveID();
760
            }
761
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto)
762
                g.NavCursorVisible = false;
763
        }
764
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
765
        {
766
            // When activated using Nav, we hold on the ActiveID until activation button is released
767
            if (g.NavActivateDownId == id)
768
                held = true; // hovered == true not true as we are already likely hovered on direct activation.
769
            else
770
                ClearActiveID();
771
        }
772
        if (pressed)
773
            g.ActiveIdHasBeenPressedBefore = true;
774
    }
775
776
    // Activation highlight (this may be a remote activation)
777
    if (g.NavHighlightActivatedId == id && (item_flags & ImGuiItemFlags_Disabled) == 0)
778
        hovered = true;
779
780
    if (out_hovered) *out_hovered = hovered;
781
    if (out_held) *out_held = held;
782
783
    return pressed;
784
}
785
786
bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
787
{
788
    ImGuiWindow* window = GetCurrentWindow();
789
    if (window->SkipItems)
790
        return false;
791
792
    ImGuiContext& g = *GImGui;
793
    const ImGuiStyle& style = g.Style;
794
    const ImGuiID id = window->GetID(label);
795
    const char* label_end = FindRenderedTextEnd(label);
796
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
797
798
    ImVec2 pos = window->DC.CursorPos;
799
    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)
800
        pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
801
    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
802
803
    const ImRect bb(pos, pos + size);
804
    ItemSize(size, style.FramePadding.y);
805
    if (!ItemAdd(bb, id))
806
        return false;
807
808
    bool hovered, held;
809
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
810
811
    // Render
812
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
813
    RenderNavCursor(bb, id);
814
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
815
816
    if (g.LogEnabled)
817
        LogSetNextTextDecoration("[", "]");
818
    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, label_end, &label_size, style.ButtonTextAlign, &bb);
819
820
    // Automatically close popups
821
    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
822
    //    CloseCurrentPopup();
823
824
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
825
    return pressed;
826
}
827
828
bool ImGui::Button(const char* label, const ImVec2& size_arg)
829
{
830
    return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
831
}
832
833
// Small buttons fits within text without additional vertical spacing.
834
bool ImGui::SmallButton(const char* label)
835
{
836
    ImGuiContext& g = *GImGui;
837
    float backup_padding_y = g.Style.FramePadding.y;
838
    g.Style.FramePadding.y = 0.0f;
839
    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
840
    g.Style.FramePadding.y = backup_padding_y;
841
    return pressed;
842
}
843
844
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
845
// 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)
846
bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
847
{
848
    ImGuiContext& g = *GImGui;
849
    ImGuiWindow* window = GetCurrentWindow();
850
    if (window->SkipItems)
851
        return false;
852
853
    // Ensure zero-size fits to contents
854
    ImVec2 size = CalcItemSize(ImVec2(size_arg.x != 0.0f ? size_arg.x : -FLT_MIN, size_arg.y != 0.0f ? size_arg.y : -FLT_MIN), 0.0f, 0.0f);
855
856
    const ImGuiID id = window->GetID(str_id);
857
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
858
    ItemSize(size);
859
    if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))
860
        return false;
861
862
    bool hovered, held;
863
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
864
    RenderNavCursor(bb, id);
865
866
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
867
    return pressed;
868
}
869
870
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
871
{
872
    ImGuiContext& g = *GImGui;
873
    ImGuiWindow* window = GetCurrentWindow();
874
    if (window->SkipItems)
875
        return false;
876
877
    const ImGuiID id = window->GetID(str_id);
878
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
879
    const float default_size = GetFrameHeight();
880
    ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
881
    if (!ItemAdd(bb, id))
882
        return false;
883
884
    bool hovered, held;
885
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
886
887
    // Render
888
    const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
889
    const ImU32 text_col = GetColorU32(ImGuiCol_Text);
890
    RenderNavCursor(bb, id);
891
    RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
892
    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);
893
894
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
895
    return pressed;
896
}
897
898
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
899
{
900
    float sz = GetFrameHeight();
901
    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
902
}
903
904
// Button to close a window
905
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
906
{
907
    ImGuiContext& g = *GImGui;
908
    ImGuiWindow* window = g.CurrentWindow;
909
910
    // 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)
911
    // 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?
912
    const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
913
    ImRect bb_interact = bb;
914
    const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
915
    if (area_to_visible_ratio < 1.5f)
916
        bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
917
918
    // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
919
    // (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).
920
    bool is_clipped = !ItemAdd(bb_interact, id);
921
922
    bool hovered, held;
923
    bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
924
    if (is_clipped)
925
        return pressed;
926
927
    // Render
928
    ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
929
    if (hovered)
930
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
931
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
932
    const ImU32 cross_col = GetColorU32(ImGuiCol_Text);
933
    const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
934
    const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
935
    const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI
936
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness);
937
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness);
938
939
    return pressed;
940
}
941
942
// The Collapse button also functions as a Dock Menu button.
943
bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
944
{
945
    ImGuiContext& g = *GImGui;
946
    ImGuiWindow* window = g.CurrentWindow;
947
948
    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
949
    bool is_clipped = !ItemAdd(bb, id);
950
    bool hovered, held;
951
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
952
    if (is_clipped)
953
        return pressed;
954
955
    // Render
956
    //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
957
    ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
958
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
959
    if (hovered || held)
960
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
961
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
962
963
    if (dock_node)
964
        RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
965
    else
966
        RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
967
968
    // Switch to moving the window after mouse is moved beyond the initial drag threshold
969
    if (IsItemActive() && IsMouseDragging(0))
970
        StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button
971
972
    return pressed;
973
}
974
975
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
976
{
977
    return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
978
}
979
980
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
981
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
982
{
983
    ImGuiContext& g = *GImGui;
984
    const ImRect outer_rect = window->Rect();
985
    const ImRect inner_rect = window->InnerRect;
986
    const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
987
    IM_ASSERT(scrollbar_size >= 0.0f);
988
    const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);
989
    const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : (window->Flags & ImGuiWindowFlags_NoTitleBar) ? border_size : 0;
990
    if (axis == ImGuiAxis_X)
991
        return ImRect(inner_rect.Min.x + border_size, ImMax(outer_rect.Min.y + border_size, outer_rect.Max.y - border_size - scrollbar_size), inner_rect.Max.x - border_size, outer_rect.Max.y - border_size);
992
    else
993
        return ImRect(ImMax(outer_rect.Min.x, outer_rect.Max.x - border_size - scrollbar_size), inner_rect.Min.y + border_top, outer_rect.Max.x - border_size, inner_rect.Max.y - border_size);
994
}
995
996
void ImGui::ExtendHitBoxWhenNearViewportEdge(ImGuiWindow* window, ImRect* bb, float threshold, ImGuiAxis axis)
997
{
998
    ImRect window_rect = window->RootWindow->Rect();
999
    ImRect viewport_rect = window->Viewport->GetMainRect();
1000
    if (window_rect.Min[axis] == viewport_rect.Min[axis] && bb->Min[axis] > window_rect.Min[axis] && bb->Min[axis] - threshold <= window_rect.Min[axis])
1001
        bb->Min[axis] = window_rect.Min[axis];
1002
    if (window_rect.Max[axis] == viewport_rect.Max[axis] && bb->Max[axis] < window_rect.Max[axis] && bb->Max[axis] + threshold >= window_rect.Max[axis])
1003
        bb->Max[axis] = window_rect.Max[axis];
1004
}
1005
1006
void ImGui::Scrollbar(ImGuiAxis axis)
1007
{
1008
    ImGuiContext& g = *GImGui;
1009
    ImGuiWindow* window = g.CurrentWindow;
1010
    const ImGuiID id = GetWindowScrollbarID(window, axis);
1011
1012
    // Calculate scrollbar bounding box
1013
    ImRect bb = GetWindowScrollbarRect(window, axis);
1014
    ImRect host_rect = (window->DockIsActive ? window->DockNode->HostWindow : window)->Rect();
1015
    ImDrawFlags rounding_corners = CalcRoundingFlagsForRectInRect(bb, host_rect, g.Style.WindowBorderSize);
1016
    float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
1017
    float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
1018
    ImS64 scroll = (ImS64)window->Scroll[axis];
1019
    ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);
1020
    window->Scroll[axis] = (float)scroll;
1021
}
1022
1023
// Vertical/Horizontal scrollbar
1024
// The entire piece of code below is rather confusing because:
1025
// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
1026
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
1027
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
1028
// Still, the code should probably be made simpler..
1029
bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS64* p_scroll_v, ImS64 size_visible_v, ImS64 size_contents_v, ImDrawFlags draw_rounding_flags)
1030
{
1031
    ImGuiContext& g = *GImGui;
1032
    ImGuiWindow* window = g.CurrentWindow;
1033
    if (window->SkipItems)
1034
        return false;
1035
1036
    const float bb_frame_width = bb_frame.GetWidth();
1037
    const float bb_frame_height = bb_frame.GetHeight();
1038
    if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
1039
        return false;
1040
1041
    // 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)
1042
    float alpha = 1.0f;
1043
    if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width)
1044
        alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f));
1045
    if (alpha <= 0.0f)
1046
        return false;
1047
1048
    const ImGuiStyle& style = g.Style;
1049
    const bool allow_interaction = (alpha >= 1.0f);
1050
1051
    ImRect bb = bb_frame;
1052
    float padding = IM_TRUNC(ImMin(style.ScrollbarPadding, ImMin(bb_frame_width, bb_frame_height) * 0.5f));
1053
    bb.Expand(-padding);
1054
1055
    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
1056
    const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
1057
    if (scrollbar_size_v < 1.0f)
1058
        return false;
1059
1060
    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
1061
    // But we maintain a minimum size in pixel to allow for the user to still aim inside.
1062
    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.
1063
    const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
1064
    const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);
1065
    const float grab_h_pixels = (float)(int)ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
1066
    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
1067
1068
    // As a special thing, we allow scrollbar near the edge of a screen/viewport to be reachable with mouse at the extreme edge (#9276)
1069
    ImRect bb_hit = bb_frame;
1070
    ExtendHitBoxWhenNearViewportEdge(window, &bb_hit, g.Style.WindowBorderSize, (ImGuiAxis)(axis ^ 1));
1071
1072
    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
1073
    bool held = false;
1074
    bool hovered = false;
1075
    ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
1076
    ButtonBehavior(bb_hit, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
1077
1078
    const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);
1079
    float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1080
    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
1081
    if (held && allow_interaction && grab_h_norm < 1.0f)
1082
    {
1083
        const float scrollbar_pos_v = bb.Min[axis];
1084
        const float mouse_pos_v = g.IO.MousePos[axis];
1085
1086
        // Click position in scrollbar normalized space (0.0f->1.0f)
1087
        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
1088
1089
        const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0;
1090
        if (g.ActiveIdIsJustActivated)
1091
        {
1092
            // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab
1093
            const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0);
1094
            g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir;
1095
            g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f;
1096
        }
1097
1098
        // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
1099
        // 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
1100
        if (g.ScrollbarSeekMode == 0)
1101
        {
1102
            // Absolute seeking
1103
            const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
1104
            *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
1105
        }
1106
        else
1107
        {
1108
            // Page by page
1109
            if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)
1110
            {
1111
                float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;
1112
                *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max);
1113
            }
1114
        }
1115
1116
        // Update values for rendering
1117
        scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1118
        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
1119
1120
        // Update distance to grab now that we have seek'ed and saturated
1121
        //if (seek_absolute)
1122
        //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
1123
    }
1124
1125
    // Render
1126
    const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
1127
    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
1128
    window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags);
1129
    ImRect grab_rect;
1130
    if (axis == ImGuiAxis_X)
1131
        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);
1132
    else
1133
        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);
1134
    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
1135
1136
    return held;
1137
}
1138
1139
// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
1140
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
1141
void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1142
{
1143
    ImGuiContext& g = *GImGui;
1144
    ImGuiWindow* window = GetCurrentWindow();
1145
    if (window->SkipItems)
1146
        return;
1147
1148
    const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize);
1149
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1150
    ItemSize(bb);
1151
    if (!ItemAdd(bb, 0))
1152
        return;
1153
1154
    // Render
1155
    float rounding = g.Style.ImageRounding;
1156
    if (bg_col.w > 0.0f)
1157
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col), rounding);
1158
    if (rounding > 0.0f)
1159
        window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), rounding);
1160
    else
1161
        window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1162
    if (g.Style.ImageBorderSize > 0.0f)
1163
        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), rounding, g.Style.ImageBorderSize);
1164
}
1165
1166
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
1167
{
1168
    ImageWithBg(tex_ref, image_size, uv0, uv1);
1169
}
1170
1171
// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)
1172
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1173
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
1174
{
1175
    ImGuiContext& g = *GImGui;
1176
    PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f
1177
    PushStyleColor(ImGuiCol_Border, border_col);
1178
    ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);
1179
    PopStyleColor();
1180
    PopStyleVar();
1181
}
1182
#endif
1183
1184
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
1185
{
1186
    ImGuiContext& g = *GImGui;
1187
    ImGuiWindow* window = GetCurrentWindow();
1188
    if (window->SkipItems)
1189
        return false;
1190
1191
    const ImVec2 padding = g.Style.FramePadding;
1192
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1193
    ItemSize(bb);
1194
    if (!ItemAdd(bb, id))
1195
        return false;
1196
1197
    bool hovered, held;
1198
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1199
1200
    // Render
1201
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1202
    RenderNavCursor(bb, id);
1203
    RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
1204
    if (bg_col.w > 0.0f)
1205
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
1206
    float image_rounding = ImMax(g.Style.FrameRounding - ImMax(padding.x, padding.y), g.Style.ImageRounding);
1207
    if (image_rounding > 0.0f)
1208
        window->DrawList->AddImageRounded(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col), image_rounding);
1209
    else
1210
        window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1211
1212
    return pressed;
1213
}
1214
1215
// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
1216
// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design?
1217
bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1218
{
1219
    ImGuiContext& g = *GImGui;
1220
    ImGuiWindow* window = g.CurrentWindow;
1221
    if (window->SkipItems)
1222
        return false;
1223
1224
    return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col);
1225
}
1226
1227
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1228
// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
1229
// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)
1230
// - new ImageButton() requires an explicit 'const char* str_id'
1231
// - old ImageButton() had frame_padding' override argument.
1232
// - new ImageButton() always use style.FramePadding.
1233
/*
1234
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)
1235
{
1236
    // Default to using texture ID as ID. User can still push string/integer prefixes.
1237
    PushID((ImTextureID)(intptr_t)user_texture_id);
1238
    if (frame_padding >= 0)
1239
        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
1240
    bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col);
1241
    if (frame_padding >= 0)
1242
        PopStyleVar();
1243
    PopID();
1244
    return ret;
1245
}
1246
*/
1247
#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1248
1249
bool ImGui::Checkbox(const char* label, bool* v)
1250
{
1251
    ImGuiWindow* window = GetCurrentWindow();
1252
    if (window->SkipItems)
1253
        return false;
1254
1255
    ImGuiContext& g = *GImGui;
1256
    const ImGuiStyle& style = g.Style;
1257
    const ImGuiID id = window->GetID(label);
1258
    const char* label_end = FindRenderedTextEnd(label);
1259
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1260
1261
    const float square_sz = GetFrameHeight();
1262
    const ImVec2 pos = window->DC.CursorPos;
1263
    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));
1264
    ItemSize(total_bb, style.FramePadding.y);
1265
    const bool is_visible = ItemAdd(total_bb, id);
1266
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1267
    if (!is_visible)
1268
        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support
1269
        {
1270
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1271
            return false;
1272
        }
1273
1274
    // Range-Selection/Multi-selection support (header)
1275
    bool checked = *v;
1276
    if (is_multi_select)
1277
        MultiSelectItemHeader(id, &checked, NULL);
1278
1279
    bool hovered, held;
1280
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1281
1282
    // Range-Selection/Multi-selection support (footer)
1283
    if (is_multi_select)
1284
        MultiSelectItemFooter(id, &checked, &pressed, ImGuiMultiSelectFlags_CheckboxMode_);
1285
    else if (pressed)
1286
        checked = !checked;
1287
1288
    if (*v != checked)
1289
    {
1290
        *v = checked;
1291
        pressed = true; // return value
1292
        MarkItemEdited(id);
1293
    }
1294
1295
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1296
    const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
1297
    if (is_visible)
1298
    {
1299
        RenderNavCursor(total_bb, id);
1300
        ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : (mixed_value || checked) ? ImGuiCol_CheckboxSelectedBg : ImGuiCol_FrameBg);
1301
        ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1302
        RenderFrame(check_bb.Min, check_bb.Max, bg_col, true, style.FrameRounding);
1303
        if (mixed_value)
1304
        {
1305
            // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1306
            // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1307
            ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
1308
            window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1309
        }
1310
        else if (*v)
1311
        {
1312
            const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1313
            RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1314
        }
1315
    }
1316
    const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1317
    if (g.LogEnabled)
1318
        LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1319
    if (is_visible && label_size.x > 0.0f)
1320
        RenderText(label_pos, label, label_end, false);
1321
1322
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1323
    return pressed;
1324
}
1325
1326
template<typename T>
1327
bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1328
{
1329
    bool all_on = (*flags & flags_value) == flags_value;
1330
    bool any_on = (*flags & flags_value) != 0;
1331
    bool pressed;
1332
    if (!all_on && any_on)
1333
    {
1334
        ImGuiContext& g = *GImGui;
1335
        g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1336
        pressed = Checkbox(label, &all_on);
1337
    }
1338
    else
1339
    {
1340
        pressed = Checkbox(label, &all_on);
1341
1342
    }
1343
    if (pressed)
1344
    {
1345
        if (all_on)
1346
            *flags |= flags_value;
1347
        else
1348
            *flags &= ~flags_value;
1349
    }
1350
    return pressed;
1351
}
1352
1353
bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1354
{
1355
    return CheckboxFlagsT(label, flags, flags_value);
1356
}
1357
1358
bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1359
{
1360
    return CheckboxFlagsT(label, flags, flags_value);
1361
}
1362
1363
bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1364
{
1365
    return CheckboxFlagsT(label, flags, flags_value);
1366
}
1367
1368
bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1369
{
1370
    return CheckboxFlagsT(label, flags, flags_value);
1371
}
1372
1373
bool ImGui::RadioButton(const char* label, bool active)
1374
{
1375
    ImGuiWindow* window = GetCurrentWindow();
1376
    if (window->SkipItems)
1377
        return false;
1378
1379
    ImGuiContext& g = *GImGui;
1380
    const ImGuiStyle& style = g.Style;
1381
    const ImGuiID id = window->GetID(label);
1382
    const char* label_end = FindRenderedTextEnd(label);
1383
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1384
1385
    const float square_sz = GetFrameHeight();
1386
    const ImVec2 pos = window->DC.CursorPos;
1387
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1388
    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));
1389
    ItemSize(total_bb, style.FramePadding.y);
1390
    if (!ItemAdd(total_bb, id))
1391
        return false;
1392
1393
    ImVec2 center = check_bb.GetCenter();
1394
    center.x = IM_ROUND(center.x);
1395
    center.y = IM_ROUND(center.y);
1396
    const float radius = (square_sz - 1.0f) * 0.5f;
1397
1398
    bool hovered, held;
1399
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1400
    if (pressed)
1401
        MarkItemEdited(id);
1402
1403
    RenderNavCursor(total_bb, id);
1404
    const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1405
    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
1406
    if (active)
1407
    {
1408
        const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1409
        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
1410
    }
1411
1412
    if (style.FrameBorderSize > 0.0f)
1413
    {
1414
        window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
1415
        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
1416
    }
1417
1418
    ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1419
    if (g.LogEnabled)
1420
        LogRenderedText(&label_pos, active ? "(x)" : "( )");
1421
    if (label_size.x > 0.0f)
1422
        RenderText(label_pos, label, label_end, false);
1423
1424
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1425
    return pressed;
1426
}
1427
1428
// 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..
1429
bool ImGui::RadioButton(const char* label, int* v, int v_button)
1430
{
1431
    const bool pressed = RadioButton(label, *v == v_button);
1432
    if (pressed)
1433
        *v = v_button;
1434
    return pressed;
1435
}
1436
1437
// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1438
void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1439
{
1440
    ImGuiWindow* window = GetCurrentWindow();
1441
    if (window->SkipItems)
1442
        return;
1443
1444
    ImGuiContext& g = *GImGui;
1445
    const ImGuiStyle& style = g.Style;
1446
1447
    ImVec2 pos = window->DC.CursorPos;
1448
    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1449
    ImRect bb(pos, pos + size);
1450
    ItemSize(size, style.FramePadding.y);
1451
    if (!ItemAdd(bb, 0))
1452
        return;
1453
1454
    // Fraction < 0.0f will display an indeterminate progress bar animation
1455
    // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works.
1456
    const bool is_indeterminate = (fraction < 0.0f);
1457
    if (!is_indeterminate)
1458
        fraction = ImSaturate(fraction);
1459
1460
    // Out of courtesy we accept a NaN fraction without crashing
1461
    float fill_n0 = 0.0f;
1462
    float fill_n1 = (fraction == fraction) ? fraction : 0.0f;
1463
1464
    if (is_indeterminate)
1465
    {
1466
        const float fill_width_n = 0.2f;
1467
        fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;
1468
        fill_n1 = ImSaturate(fill_n0 + fill_width_n);
1469
        fill_n0 = ImSaturate(fill_n0);
1470
    }
1471
1472
    // Render
1473
    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1474
    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1475
    float fill_x0 = ImLerp(bb.Min.x, bb.Max.x, fill_n0);
1476
    float fill_x1 = ImLerp(bb.Min.x, bb.Max.x, fill_n1);
1477
    if (fill_x0 < fill_x1)
1478
        RenderRectFilledInRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_x0, fill_x1, style.FrameRounding);
1479
1480
    // Default displaying the fraction as percentage string, but user can override it
1481
    // Don't display text for indeterminate bars by default
1482
    char overlay_buf[32];
1483
    if (!is_indeterminate || overlay != NULL)
1484
    {
1485
        if (!overlay)
1486
        {
1487
            ImFormatString(overlay_buf, IM_COUNTOF(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1488
            overlay = overlay_buf;
1489
        }
1490
1491
        ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1492
        if (overlay_size.x > 0.0f)
1493
        {
1494
            float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : fill_x1 + style.ItemSpacing.x;
1495
            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);
1496
        }
1497
    }
1498
}
1499
1500
void ImGui::Bullet()
1501
{
1502
    ImGuiWindow* window = GetCurrentWindow();
1503
    if (window->SkipItems)
1504
        return;
1505
1506
    ImGuiContext& g = *GImGui;
1507
    const ImGuiStyle& style = g.Style;
1508
    const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
1509
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1510
    ItemSize(bb);
1511
    if (!ItemAdd(bb, 0))
1512
    {
1513
        SameLine(0, style.FramePadding.x * 2);
1514
        return;
1515
    }
1516
1517
    // Render and stay on same line
1518
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1519
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1520
    SameLine(0, style.FramePadding.x * 2.0f);
1521
}
1522
1523
// This is provided as a convenience for being an often requested feature.
1524
// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.
1525
// Because of this we currently don't provide many styling options for this widget
1526
// (e.g. hovered/active colors are automatically inferred from a single color).
1527
bool ImGui::TextLink(const char* label)
1528
{
1529
    ImGuiWindow* window = GetCurrentWindow();
1530
    if (window->SkipItems)
1531
        return false;
1532
1533
    ImGuiContext& g = *GImGui;
1534
    const ImGuiID id = window->GetID(label);
1535
    const char* label_end = FindRenderedTextEnd(label);
1536
1537
    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1538
    ImVec2 size = CalcTextSize(label, label_end, false);
1539
    ImRect bb(pos, pos + size);
1540
    ItemSize(size, 0.0f);
1541
    if (!ItemAdd(bb, id))
1542
        return false;
1543
1544
    bool hovered, held;
1545
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1546
    RenderNavCursor(bb, id);
1547
1548
    if (hovered)
1549
        SetMouseCursor(ImGuiMouseCursor_Hand);
1550
1551
    ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];
1552
    ImVec4 line_colf = text_colf;
1553
    {
1554
        // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets,
1555
        // as we are currently experimenting/planning a different styling system.
1556
        float h, s, v;
1557
        ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);
1558
        if (held || hovered)
1559
        {
1560
            v = ImSaturate(v + (held ? 0.4f : 0.3f));
1561
            h = ImFmod(h + 0.02f, 1.0f);
1562
        }
1563
        ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);
1564
        v = ImSaturate(v - 0.20f);
1565
        ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
1566
    }
1567
1568
    float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f);
1569
    window->DrawList->AddLineH(bb.Min.x, bb.Max.x, line_y, GetColorU32(line_colf), 1.0f * (float)(int)g.Style._MainScale); // FIXME-TEXT: Underline mode // FIXME-DPI
1570
1571
    PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
1572
    RenderText(bb.Min, label, label_end, false);
1573
    PopStyleColor();
1574
1575
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1576
    return pressed;
1577
}
1578
1579
bool ImGui::TextLinkOpenURL(const char* label, const char* url)
1580
{
1581
    ImGuiContext& g = *GImGui;
1582
    if (url == NULL)
1583
        url = label;
1584
    bool pressed = TextLink(label);
1585
    if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL)
1586
        g.PlatformIO.Platform_OpenInShellFn(&g, url);
1587
    SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when it is the same as the label.
1588
    if (BeginPopupContextItem())
1589
    {
1590
        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))
1591
            SetClipboardText(url);
1592
        EndPopup();
1593
    }
1594
    return pressed;
1595
}
1596
1597
//-------------------------------------------------------------------------
1598
// [SECTION] Widgets: Low-level Layout helpers
1599
//-------------------------------------------------------------------------
1600
// - Spacing()
1601
// - Dummy()
1602
// - NewLine()
1603
// - AlignTextToFramePadding()
1604
// - SeparatorEx() [Internal]
1605
// - Separator()
1606
// - SplitterBehavior() [Internal]
1607
// - ShrinkWidths() [Internal]
1608
//-------------------------------------------------------------------------
1609
1610
void ImGui::Spacing()
1611
{
1612
    ImGuiWindow* window = GetCurrentWindow();
1613
    if (window->SkipItems)
1614
        return;
1615
    ItemSize(ImVec2(0, 0));
1616
}
1617
1618
void ImGui::Dummy(const ImVec2& size)
1619
{
1620
    ImGuiWindow* window = GetCurrentWindow();
1621
    if (window->SkipItems)
1622
        return;
1623
1624
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1625
    ItemSize(size);
1626
    ItemAdd(bb, 0);
1627
}
1628
1629
void ImGui::NewLine()
1630
{
1631
    ImGuiWindow* window = GetCurrentWindow();
1632
    if (window->SkipItems)
1633
        return;
1634
1635
    ImGuiContext& g = *GImGui;
1636
    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1637
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1638
    window->DC.IsSameLine = false;
1639
    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.
1640
        ItemSize(ImVec2(0, 0));
1641
    else
1642
        ItemSize(ImVec2(0.0f, g.FontSize));
1643
    window->DC.LayoutType = backup_layout_type;
1644
}
1645
1646
void ImGui::AlignTextToFramePadding()
1647
{
1648
    ImGuiWindow* window = GetCurrentWindow();
1649
    if (window->SkipItems)
1650
        return;
1651
1652
    ImGuiContext& g = *GImGui;
1653
    window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1654
    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1655
}
1656
1657
// Horizontal/vertical separating line
1658
// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1659
// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1660
void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1661
{
1662
    ImGuiWindow* window = GetCurrentWindow();
1663
    if (window->SkipItems)
1664
        return;
1665
1666
    ImGuiContext& g = *GImGui;
1667
    IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
1668
    IM_ASSERT(thickness > 0.0f);
1669
1670
    if (flags & ImGuiSeparatorFlags_Vertical)
1671
    {
1672
        // Vertical separator, for menu bars (use current line height).
1673
        float y1 = window->DC.CursorPos.y;
1674
        float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1675
        const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1676
        ItemSize(ImVec2(thickness, 0.0f));
1677
        if (!ItemAdd(bb, 0))
1678
            return;
1679
1680
        // Draw
1681
        window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1682
        if (g.LogEnabled)
1683
            LogText(" |");
1684
    }
1685
    else if (flags & ImGuiSeparatorFlags_Horizontal)
1686
    {
1687
        // Horizontal Separator
1688
        float x1 = window->DC.CursorPos.x;
1689
        float x2 = window->WorkRect.Max.x;
1690
1691
        // Preserve legacy behavior inside Columns()
1692
        // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1693
        // We currently don't need to provide the same feature for tables because tables naturally have border features.
1694
        ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1695
        if (columns)
1696
        {
1697
            x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1698
            x2 = window->Pos.x + window->Size.x;
1699
            PushColumnsBackground();
1700
        }
1701
1702
        // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1703
        // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1704
1705
        // Between 1.71 and 1.92.7, we maintained a hack where a 1.0f thin Separator() would not impact layout.
1706
        // This was mostly chosen to allow backward compatibility with user's code assuming zero-height when calculating height for layout (e.g. bottom alignment of a status bar).
1707
        // In order to handle scaling we need to scale separator thickness and it would not makes sense to have a disparity depending on height.
1708
        ////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.
1709
        const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1710
        ItemSize(ImVec2(0.0f, thickness));
1711
1712
        if (ItemAdd(bb, 0))
1713
        {
1714
            // Draw
1715
            window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1716
            if (g.LogEnabled)
1717
                LogRenderedText(&bb.Min, "--------------------------------\n");
1718
1719
        }
1720
        if (columns)
1721
        {
1722
            PopColumnsBackground();
1723
            columns->LineMinY = window->DC.CursorPos.y;
1724
        }
1725
    }
1726
}
1727
1728
void ImGui::Separator()
1729
{
1730
    ImGuiContext& g = *GImGui;
1731
    ImGuiWindow* window = g.CurrentWindow;
1732
    if (window->SkipItems)
1733
        return;
1734
1735
    // Those flags should eventually be configurable by the user
1736
    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1737
1738
    // Only applies to legacy Columns() api as they relied on Separator() a lot.
1739
    if (window->DC.CurrentColumns)
1740
        flags |= ImGuiSeparatorFlags_SpanAllColumns;
1741
1742
    SeparatorEx(flags, ImMax(g.Style.SeparatorSize, 1.0f));
1743
}
1744
1745
void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1746
{
1747
    ImGuiContext& g = *GImGui;
1748
    ImGuiWindow* window = g.CurrentWindow;
1749
    ImGuiStyle& style = g.Style;
1750
1751
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1752
    const ImVec2 pos = window->DC.CursorPos;
1753
    const ImVec2 padding = style.SeparatorTextPadding;
1754
1755
    const float separator_thickness = style.SeparatorTextBorderSize;
1756
    const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
1757
    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1758
    const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
1759
    ItemSize(min_size, text_baseline_y);
1760
    if (!ItemAdd(bb, id))
1761
        return;
1762
1763
    const float sep1_x1 = pos.x;
1764
    const float sep2_x2 = bb.Max.x;
1765
    const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f);
1766
1767
    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
1768
    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
1769
1770
    // This allows using SameLine() to position something in the 'extra_w'
1771
    window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1772
1773
    const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
1774
    if (label_size.x > 0.0f)
1775
    {
1776
        const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1777
        const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1778
        if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1779
            window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness);
1780
        if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1781
            window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness);
1782
        if (g.LogEnabled)
1783
            LogSetNextTextDecoration("---", NULL);
1784
        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size);
1785
    }
1786
    else
1787
    {
1788
        if (g.LogEnabled)
1789
            LogText("---");
1790
        if (separator_thickness > 0.0f)
1791
            window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness);
1792
    }
1793
}
1794
1795
void ImGui::SeparatorText(const char* label)
1796
{
1797
    ImGuiWindow* window = GetCurrentWindow();
1798
    if (window->SkipItems)
1799
        return;
1800
1801
    // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1802
    // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1803
    // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1804
    // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1805
    // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1806
    // and then we can turn this into a format function.
1807
    SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
1808
}
1809
1810
// 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.
1811
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)
1812
{
1813
    ImGuiContext& g = *GImGui;
1814
    ImGuiWindow* window = g.CurrentWindow;
1815
1816
    if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
1817
        return false;
1818
1819
    // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1820
    // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1821
    // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1822
    ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1823
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1824
    button_flags |= ImGuiButtonFlags_AllowOverlap;
1825
#endif
1826
1827
    bool hovered, held;
1828
    ImRect bb_interact = bb;
1829
    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1830
    ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
1831
    if (hovered)
1832
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1833
1834
    if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1835
        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1836
1837
    ImRect bb_render = bb;
1838
    if (held)
1839
    {
1840
        float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1841
1842
        // Minimum pane size
1843
        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1844
        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1845
        if (mouse_delta < -size_1_maximum_delta)
1846
            mouse_delta = -size_1_maximum_delta;
1847
        if (mouse_delta > size_2_maximum_delta)
1848
            mouse_delta = size_2_maximum_delta;
1849
1850
        // Apply resize
1851
        if (mouse_delta != 0.0f)
1852
        {
1853
            *size1 = ImMax(*size1 + mouse_delta, min_size1);
1854
            *size2 = ImMax(*size2 - mouse_delta, min_size2);
1855
            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1856
            MarkItemEdited(id);
1857
        }
1858
    }
1859
1860
    // Render at new position
1861
    if (bg_col & IM_COL32_A_MASK)
1862
        window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
1863
    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1864
    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1865
1866
    return held;
1867
}
1868
1869
static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1870
{
1871
    const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1872
    const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1873
    if (int d = (int)(b->Width - a->Width))
1874
        return d;
1875
    return b->Index - a->Index;
1876
}
1877
1878
// Shrink excess width from a set of item, by removing width from the larger items first.
1879
// Set items Width to -1.0f to disable shrinking this item.
1880
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min)
1881
{
1882
    if (count == 1)
1883
    {
1884
        if (items[0].Width >= 0.0f)
1885
            items[0].Width = ImMax(items[0].Width - width_excess, width_min);
1886
        return;
1887
    }
1888
    ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last.
1889
    int count_same_width = 1;
1890
    while (width_excess > 0.001f && count_same_width < count)
1891
    {
1892
        while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1893
            count_same_width++;
1894
        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);
1895
        max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item);
1896
        if (max_width_to_remove_per_item <= 0.0f)
1897
            break;
1898
        float base_width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1899
        for (int item_n = 0; item_n < count_same_width; item_n++)
1900
        {
1901
            float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min);
1902
            items[item_n].Width -= width_to_remove_for_this_item;
1903
            width_excess -= width_to_remove_for_this_item;
1904
        }
1905
    }
1906
1907
    // Round width and redistribute remainder
1908
    // 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.
1909
    width_excess = 0.0f;
1910
    for (int n = 0; n < count; n++)
1911
    {
1912
        float width_rounded = ImTrunc(items[n].Width);
1913
        width_excess += items[n].Width - width_rounded;
1914
        items[n].Width = width_rounded;
1915
    }
1916
    while (width_excess > 0.0f)
1917
        for (int n = 0; n < count && width_excess > 0.0f; n++)
1918
        {
1919
            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
1920
            items[n].Width += width_to_add;
1921
            width_excess -= width_to_add;
1922
        }
1923
}
1924
1925
//-------------------------------------------------------------------------
1926
// [SECTION] Widgets: ComboBox
1927
//-------------------------------------------------------------------------
1928
// - CalcMaxPopupHeightFromItemCount() [Internal]
1929
// - BeginCombo()
1930
// - BeginComboPopup() [Internal]
1931
// - EndCombo()
1932
// - BeginComboPreview() [Internal]
1933
// - EndComboPreview() [Internal]
1934
// - Combo()
1935
//-------------------------------------------------------------------------
1936
1937
static float CalcMaxPopupHeightFromItemCount(int items_count)
1938
{
1939
    ImGuiContext& g = *GImGui;
1940
    if (items_count <= 0)
1941
        return FLT_MAX;
1942
    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1943
}
1944
1945
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1946
{
1947
    ImGuiContext& g = *GImGui;
1948
    ImGuiWindow* window = GetCurrentWindow();
1949
1950
    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;
1951
    g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1952
    if (window->SkipItems)
1953
        return false;
1954
1955
    const ImGuiStyle& style = g.Style;
1956
    const ImGuiID id = window->GetID(label);
1957
    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1958
    if (flags & ImGuiComboFlags_WidthFitPreview)
1959
        IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1960
1961
    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1962
    const char* label_end = FindRenderedTextEnd(label);
1963
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1964
    const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, false).x : 0.0f;
1965
    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1966
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1967
    const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1968
    ItemSize(total_bb, style.FramePadding.y);
1969
    if (!ItemAdd(total_bb, id, &bb))
1970
        return false;
1971
1972
    // Open on click
1973
    bool hovered, held;
1974
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1975
    const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1976
    bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1977
    if (pressed && !popup_open)
1978
    {
1979
        OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1980
        popup_open = true;
1981
    }
1982
1983
    // Render shape
1984
    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1985
    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1986
    RenderNavCursor(bb, id);
1987
    if (!(flags & ImGuiComboFlags_NoPreview))
1988
        window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1989
    if (!(flags & ImGuiComboFlags_NoArrowButton))
1990
    {
1991
        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1992
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
1993
        window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1994
        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1995
            RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1996
    }
1997
    RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1998
1999
    // Custom preview
2000
    if (flags & ImGuiComboFlags_CustomPreview)
2001
    {
2002
        g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
2003
        IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
2004
        preview_value = NULL;
2005
    }
2006
2007
    // Render preview and label
2008
    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
2009
    {
2010
        if (g.LogEnabled)
2011
            LogSetNextTextDecoration("{", "}");
2012
        RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
2013
    }
2014
    if (label_size.x > 0)
2015
        RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label, label_end, false);
2016
2017
    if (!popup_open)
2018
        return false;
2019
2020
    g.NextWindowData.HasFlags = backup_next_window_data_flags;
2021
    return BeginComboPopup(popup_id, bb, flags);
2022
}
2023
2024
bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
2025
{
2026
    ImGuiContext& g = *GImGui;
2027
    if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
2028
    {
2029
        g.NextWindowData.ClearFlags();
2030
        return false;
2031
    }
2032
2033
    // Set popup size
2034
    float w = bb.GetWidth();
2035
    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
2036
    {
2037
        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
2038
    }
2039
    else
2040
    {
2041
        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
2042
            flags |= ImGuiComboFlags_HeightRegular;
2043
        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
2044
        int popup_max_height_in_items = -1;
2045
        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
2046
        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
2047
        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
2048
        ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
2049
        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
2050
            constraint_min.x = w;
2051
        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
2052
            constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
2053
        SetNextWindowSizeConstraints(constraint_min, constraint_max);
2054
    }
2055
2056
    // This is essentially a specialized version of BeginPopupEx()
2057
    char name[16];
2058
    ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
2059
2060
    // Set position given a custom constraint (peak into expected window size so we can position it)
2061
    // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
2062
    // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
2063
    if (ImGuiWindow* popup_window = FindWindowByName(name))
2064
        if (popup_window->WasActive)
2065
        {
2066
            // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
2067
            ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
2068
            popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
2069
            ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
2070
            ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
2071
            SetNextWindowPos(pos);
2072
        }
2073
2074
    // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
2075
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
2076
    PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text
2077
    bool ret = Begin(name, NULL, window_flags);
2078
    PopStyleVar();
2079
    if (!ret)
2080
    {
2081
        EndPopup();
2082
        if (!g.IO.ConfigDebugBeginReturnValueOnce && !g.IO.ConfigDebugBeginReturnValueLoop) // Begin may only return false with those debug tools activated.
2083
            IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
2084
        return false;
2085
    }
2086
    g.BeginComboDepth++;
2087
    return true;
2088
}
2089
2090
void ImGui::EndCombo()
2091
{
2092
    ImGuiContext& g = *GImGui;
2093
    g.BeginComboDepth--;
2094
    char name[16];
2095
    ImFormatString(name, IM_COUNTOF(name), "##Combo_%02d", g.BeginComboDepth); // FIXME: Move those to helpers?
2096
    if (strcmp(g.CurrentWindow->Name, name) != 0)
2097
        IM_ASSERT_USER_ERROR_RET(0, "Calling EndCombo() in wrong window!");
2098
    EndPopup();
2099
}
2100
2101
// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
2102
// (Experimental, see GitHub issues: #1658, #4168)
2103
bool ImGui::BeginComboPreview()
2104
{
2105
    ImGuiContext& g = *GImGui;
2106
    ImGuiWindow* window = g.CurrentWindow;
2107
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
2108
2109
    if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
2110
        return false;
2111
    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?
2112
    if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
2113
        return false;
2114
2115
    // FIXME: This could be contained in a PushWorkRect() api
2116
    preview_data->BackupCursorPos = window->DC.CursorPos;
2117
    preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
2118
    preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
2119
    preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
2120
    preview_data->BackupLayout = window->DC.LayoutType;
2121
    window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
2122
    window->DC.CursorMaxPos = window->DC.CursorPos;
2123
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
2124
    window->DC.IsSameLine = false;
2125
    PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
2126
2127
    return true;
2128
}
2129
2130
void ImGui::EndComboPreview()
2131
{
2132
    ImGuiContext& g = *GImGui;
2133
    ImGuiWindow* window = g.CurrentWindow;
2134
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
2135
2136
    // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
2137
    ImDrawList* draw_list = window->DrawList;
2138
    if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
2139
        if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
2140
        {
2141
            draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
2142
            draw_list->_TryMergeDrawCmds();
2143
        }
2144
    PopClipRect();
2145
    window->DC.CursorPos = preview_data->BackupCursorPos;
2146
    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
2147
    window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
2148
    window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
2149
    window->DC.LayoutType = preview_data->BackupLayout;
2150
    window->DC.IsSameLine = false;
2151
    preview_data->PreviewRect = ImRect();
2152
}
2153
2154
// Getter for the old Combo() API: const char*[]
2155
static const char* Items_ArrayGetter(void* data, int idx)
2156
{
2157
    const char* const* items = (const char* const*)data;
2158
    return items[idx];
2159
}
2160
2161
// Getter for the old Combo() API: "item1\0item2\0item3\0"
2162
static const char* Items_SingleStringGetter(void* data, int idx)
2163
{
2164
    const char* items_separated_by_zeros = (const char*)data;
2165
    int items_count = 0;
2166
    const char* p = items_separated_by_zeros;
2167
    while (*p)
2168
    {
2169
        if (idx == items_count)
2170
            break;
2171
        p += ImStrlen(p) + 1;
2172
        items_count++;
2173
    }
2174
    return *p ? p : NULL;
2175
}
2176
2177
// Old API, prefer using BeginCombo() nowadays if you can.
2178
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)
2179
{
2180
    ImGuiContext& g = *GImGui;
2181
2182
    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
2183
    const char* preview_value = NULL;
2184
    if (*current_item >= 0 && *current_item < items_count)
2185
        preview_value = getter(user_data, *current_item);
2186
2187
    // 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.
2188
    if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint))
2189
        SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
2190
2191
    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
2192
        return false;
2193
2194
    // Display items
2195
    bool value_changed = false;
2196
    ImGuiListClipper clipper;
2197
    clipper.Begin(items_count);
2198
    clipper.IncludeItemByIndex(*current_item);
2199
    while (clipper.Step())
2200
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
2201
        {
2202
            const char* item_text = getter(user_data, i);
2203
            if (item_text == NULL)
2204
                item_text = "*Unknown item*";
2205
2206
            PushID(i);
2207
            const bool item_selected = (i == *current_item);
2208
            if (Selectable(item_text, item_selected) && *current_item != i)
2209
            {
2210
                value_changed = true;
2211
                *current_item = i;
2212
            }
2213
            if (item_selected)
2214
                SetItemDefaultFocus();
2215
            PopID();
2216
        }
2217
2218
    EndCombo();
2219
    if (value_changed)
2220
        MarkItemEdited(g.LastItemData.ID);
2221
2222
    return value_changed;
2223
}
2224
2225
// Combo box helper allowing to pass an array of strings.
2226
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
2227
{
2228
    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
2229
    return value_changed;
2230
}
2231
2232
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
2233
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
2234
{
2235
    int items_count = 0;
2236
    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
2237
    while (*p)
2238
    {
2239
        p += ImStrlen(p) + 1;
2240
        items_count++;
2241
    }
2242
    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
2243
    return value_changed;
2244
}
2245
2246
//-------------------------------------------------------------------------
2247
// [SECTION] Data Type and Data Formatting Helpers [Internal]
2248
//-------------------------------------------------------------------------
2249
// - DataTypeGetInfo()
2250
// - DataTypeFormatString()
2251
// - DataTypeApplyOp()
2252
// - DataTypeApplyFromText()
2253
// - DataTypeCompare()
2254
// - DataTypeClamp()
2255
// - GetMinimumStepAtDecimalPrecision
2256
// - RoundScalarWithFormat<>()
2257
//-------------------------------------------------------------------------
2258
2259
static const ImU32 GDefaultRgbaColorMarkers[4] =
2260
{
2261
    IM_COL32(240,20,20,255), IM_COL32(20,240,20,255), IM_COL32(20,20,240,255), IM_COL32(140,140,140,255)
2262
};
2263
2264
static const ImGuiDataTypeInfo GDataTypeInfo[] =
2265
{
2266
    { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
2267
    { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
2268
    { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
2269
    { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
2270
    { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
2271
    { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
2272
#ifdef _MSC_VER
2273
    { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
2274
    { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
2275
#else
2276
    { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
2277
    { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
2278
#endif
2279
    { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
2280
    { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
2281
    { sizeof(bool),             "bool", "%d",   "%d"    },  // ImGuiDataType_Bool
2282
    { 0,                        "char*","%s",   "%s"    },  // ImGuiDataType_String
2283
};
2284
IM_STATIC_ASSERT(IM_COUNTOF(GDataTypeInfo) == ImGuiDataType_COUNT);
2285
2286
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
2287
{
2288
    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
2289
    return &GDataTypeInfo[data_type];
2290
}
2291
2292
int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
2293
{
2294
    // Signedness doesn't matter when pushing integer arguments
2295
    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
2296
        return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
2297
    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
2298
        return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
2299
    if (data_type == ImGuiDataType_Float)
2300
        return ImFormatString(buf, buf_size, format, *(const float*)p_data);
2301
    if (data_type == ImGuiDataType_Double)
2302
        return ImFormatString(buf, buf_size, format, *(const double*)p_data);
2303
    if (data_type == ImGuiDataType_S8)
2304
        return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
2305
    if (data_type == ImGuiDataType_U8)
2306
        return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
2307
    if (data_type == ImGuiDataType_S16)
2308
        return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
2309
    if (data_type == ImGuiDataType_U16)
2310
        return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
2311
    IM_ASSERT(0);
2312
    return 0;
2313
}
2314
2315
void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
2316
{
2317
    IM_ASSERT(op == '+' || op == '-');
2318
    switch (data_type)
2319
    {
2320
        case ImGuiDataType_S8:
2321
            if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
2322
            if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
2323
            return;
2324
        case ImGuiDataType_U8:
2325
            if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
2326
            if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
2327
            return;
2328
        case ImGuiDataType_S16:
2329
            if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2330
            if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
2331
            return;
2332
        case ImGuiDataType_U16:
2333
            if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2334
            if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
2335
            return;
2336
        case ImGuiDataType_S32:
2337
            if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2338
            if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
2339
            return;
2340
        case ImGuiDataType_U32:
2341
            if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2342
            if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
2343
            return;
2344
        case ImGuiDataType_S64:
2345
            if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2346
            if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
2347
            return;
2348
        case ImGuiDataType_U64:
2349
            if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2350
            if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
2351
            return;
2352
        case ImGuiDataType_Float:
2353
            if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
2354
            if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
2355
            return;
2356
        case ImGuiDataType_Double:
2357
            if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
2358
            if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
2359
            return;
2360
        case ImGuiDataType_COUNT: break;
2361
    }
2362
    IM_ASSERT(0);
2363
}
2364
2365
// User can input math operators (e.g. +100) to edit a numerical values.
2366
// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
2367
bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)
2368
{
2369
    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
2370
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
2371
    ImGuiDataTypeStorage data_backup;
2372
    memcpy(&data_backup, p_data, type_info->Size);
2373
2374
    while (ImCharIsBlankA(*buf))
2375
        buf++;
2376
    if (!buf[0])
2377
    {
2378
        if (p_data_when_empty != NULL)
2379
        {
2380
            memcpy(p_data, p_data_when_empty, type_info->Size);
2381
            return memcmp(&data_backup, p_data, type_info->Size) != 0;
2382
        }
2383
        return false;
2384
    }
2385
2386
    // Sanitize format
2387
    // - 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
2388
    char format_sanitized[32];
2389
    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
2390
    {
2391
        format = type_info->ScanFmt;
2392
    }
2393
    else
2394
    {
2395
        format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_COUNTOF(format_sanitized));
2396
        if (format[0] == '\0')
2397
            format = type_info->ScanFmt; // Format doesn't want us to show the number currently, but we still need to parse the resulting input
2398
    }
2399
2400
    // Small types need a 32-bit buffer to receive the result from scanf()
2401
    int v32 = 0;
2402
    if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
2403
        return false;
2404
    if (type_info->Size < 4)
2405
    {
2406
        if (data_type == ImGuiDataType_S8)
2407
            *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
2408
        else if (data_type == ImGuiDataType_U8)
2409
            *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
2410
        else if (data_type == ImGuiDataType_S16)
2411
            *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
2412
        else if (data_type == ImGuiDataType_U16)
2413
            *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
2414
        else
2415
            IM_ASSERT(0);
2416
    }
2417
2418
    return memcmp(&data_backup, p_data, type_info->Size) != 0;
2419
}
2420
2421
template<typename T>
2422
static int DataTypeCompareT(const T* lhs, const T* rhs)
2423
{
2424
    if (*lhs < *rhs) return -1;
2425
    if (*lhs > *rhs) return +1;
2426
    return 0;
2427
}
2428
2429
int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
2430
{
2431
    switch (data_type)
2432
    {
2433
    case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
2434
    case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
2435
    case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
2436
    case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
2437
    case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
2438
    case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
2439
    case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
2440
    case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
2441
    case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
2442
    case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
2443
    case ImGuiDataType_COUNT:  break;
2444
    }
2445
    IM_ASSERT(0);
2446
    return 0;
2447
}
2448
2449
template<typename T>
2450
static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
2451
{
2452
    // Clamp, both sides are optional, return true if modified
2453
    if (v_min && *v < *v_min) { *v = *v_min; return true; }
2454
    if (v_max && *v > *v_max) { *v = *v_max; return true; }
2455
    return false;
2456
}
2457
2458
bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
2459
{
2460
    switch (data_type)
2461
    {
2462
    case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
2463
    case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
2464
    case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
2465
    case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
2466
    case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
2467
    case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
2468
    case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
2469
    case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
2470
    case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
2471
    case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
2472
    case ImGuiDataType_COUNT:  break;
2473
    }
2474
    IM_ASSERT(0);
2475
    return false;
2476
}
2477
2478
bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data)
2479
{
2480
    ImGuiContext& g = *GImGui;
2481
    return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0;
2482
}
2483
2484
static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
2485
{
2486
    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 };
2487
    if (decimal_precision < 0)
2488
        return FLT_MIN;
2489
    return (decimal_precision < IM_COUNTOF(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
2490
}
2491
2492
template<typename TYPE>
2493
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
2494
{
2495
    IM_UNUSED(data_type);
2496
    IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
2497
    const char* fmt_start = ImParseFormatFindStart(format);
2498
    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
2499
        return v;
2500
2501
    // Sanitize format
2502
    char fmt_sanitized[32];
2503
    ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_COUNTOF(fmt_sanitized));
2504
    fmt_start = fmt_sanitized;
2505
2506
    // Format value with our rounding, and read back
2507
    char v_str[64];
2508
    ImFormatString(v_str, IM_COUNTOF(v_str), fmt_start, v);
2509
    const char* p = v_str;
2510
    while (*p == ' ')
2511
        p++;
2512
    v = (TYPE)ImAtof(p);
2513
2514
    return v;
2515
}
2516
2517
//-------------------------------------------------------------------------
2518
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
2519
//-------------------------------------------------------------------------
2520
// - DragBehaviorT<>() [Internal]
2521
// - DragBehavior() [Internal]
2522
// - DragScalar()
2523
// - DragScalarN()
2524
// - DragFloat()
2525
// - DragFloat2()
2526
// - DragFloat3()
2527
// - DragFloat4()
2528
// - DragFloatRange2()
2529
// - DragInt()
2530
// - DragInt2()
2531
// - DragInt3()
2532
// - DragInt4()
2533
// - DragIntRange2()
2534
//-------------------------------------------------------------------------
2535
2536
// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
2537
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2538
bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
2539
{
2540
    ImGuiContext& g = *GImGui;
2541
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
2542
    const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange)));
2543
    const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);
2544
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
2545
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
2546
2547
    // Default tweak speed
2548
    if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))
2549
        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
2550
2551
    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
2552
    float adjust_delta = 0.0f;
2553
    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2554
    {
2555
        adjust_delta = g.IO.MouseDelta[axis];
2556
        if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
2557
            adjust_delta *= 1.0f / 100.0f;
2558
        if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
2559
            adjust_delta *= 10.0f;
2560
    }
2561
    else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
2562
    {
2563
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
2564
        const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
2565
        const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
2566
        const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
2567
        adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
2568
        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
2569
    }
2570
    adjust_delta *= v_speed;
2571
2572
    // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
2573
    if (axis == ImGuiAxis_Y)
2574
        adjust_delta = -adjust_delta;
2575
2576
    // For logarithmic use our range is effectively 0..1 so scale the delta into that range
2577
    if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
2578
        adjust_delta /= (float)(v_max - v_min);
2579
2580
    // Clear current value on activation
2581
    // 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.
2582
    const bool is_just_activated = g.ActiveIdIsJustActivated;
2583
    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));
2584
    if (is_just_activated || is_already_past_limits_and_pushing_outward)
2585
    {
2586
        g.DragCurrentAccum = 0.0f;
2587
        g.DragCurrentAccumDirty = false;
2588
    }
2589
    else if (adjust_delta != 0.0f)
2590
    {
2591
        g.DragCurrentAccum += adjust_delta;
2592
        g.DragCurrentAccumDirty = true;
2593
    }
2594
2595
    if (!g.DragCurrentAccumDirty)
2596
        return false;
2597
2598
    TYPE v_cur = *v;
2599
    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
2600
2601
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
2602
    const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
2603
    if (is_logarithmic)
2604
    {
2605
        // 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.
2606
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
2607
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
2608
2609
        // Convert to parametric space, apply delta, convert back
2610
        float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2611
        float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
2612
        v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2613
        v_old_ref_for_accum_remainder = v_old_parametric;
2614
    }
2615
    else
2616
    {
2617
        v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
2618
    }
2619
2620
    // Round to user desired precision based on format string
2621
    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
2622
        v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
2623
2624
    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
2625
    g.DragCurrentAccumDirty = false;
2626
    if (is_logarithmic)
2627
    {
2628
        // Convert to parametric space, apply delta, convert back
2629
        float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
2630
        g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
2631
    }
2632
    else
2633
    {
2634
        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
2635
    }
2636
2637
    // Lose zero sign for float/double
2638
    if (v_cur == (TYPE)-0)
2639
        v_cur = (TYPE)0;
2640
2641
    if (*v != v_cur && is_bounded)
2642
    {
2643
        if (is_wrapped)
2644
        {
2645
            // Wrap values
2646
            if (v_cur < v_min)
2647
                v_cur += v_max - v_min + (is_floating_point ? 0 : 1);
2648
            if (v_cur > v_max)
2649
                v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);
2650
        }
2651
        else
2652
        {
2653
            // Clamp values + handle overflow/wrap-around for integer types.
2654
            if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
2655
                v_cur = v_min;
2656
            if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
2657
                v_cur = v_max;
2658
        }
2659
    }
2660
2661
    // Apply result
2662
    if (*v == v_cur)
2663
        return false;
2664
    *v = v_cur;
2665
    return true;
2666
}
2667
2668
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)
2669
{
2670
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
2671
    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.");
2672
2673
    ImGuiContext& g = *GImGui;
2674
    if (g.ActiveId == id)
2675
    {
2676
        // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
2677
        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
2678
            ClearActiveID();
2679
        else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
2680
            ClearActiveID();
2681
    }
2682
    if (g.ActiveId != id)
2683
        return false;
2684
    if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
2685
        return false;
2686
2687
    switch (data_type)
2688
    {
2689
    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; }
2690
    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; }
2691
    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; }
2692
    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; }
2693
    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);
2694
    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);
2695
    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);
2696
    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);
2697
    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);
2698
    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);
2699
    case ImGuiDataType_COUNT:  break;
2700
    }
2701
    IM_ASSERT(0);
2702
    return false;
2703
}
2704
2705
// Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
2706
static bool TempInputIsClampEnabled(ImGuiSliderFlags flags, ImGuiDataType data_type, const void* p_min, const void* p_max)
2707
{
2708
    if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
2709
    {
2710
        const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? ImGui::DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max
2711
        if (p_min == NULL || p_max == NULL || clamp_range_dir < 0)
2712
            return true;
2713
        if (clamp_range_dir == 0)
2714
            return ImGui::DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;
2715
    }
2716
    return false;
2717
}
2718
2719
// 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.
2720
// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
2721
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)
2722
{
2723
    ImGuiWindow* window = GetCurrentWindow();
2724
    if (window->SkipItems)
2725
        return false;
2726
2727
    ImGuiContext& g = *GImGui;
2728
    const ImGuiStyle& style = g.Style;
2729
    const ImGuiID id = window->GetID(label);
2730
    const float w = CalcItemWidth();
2731
    const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
2732
2733
    const char* label_end = FindRenderedTextEnd(label);
2734
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
2735
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
2736
    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));
2737
2738
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
2739
    ItemSize(total_bb, style.FramePadding.y);
2740
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
2741
        return false;
2742
2743
    // Default format string when passing NULL
2744
    if (format == NULL)
2745
        format = DataTypeGetInfo(data_type)->PrintFmt;
2746
2747
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
2748
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
2749
    if (!temp_input_is_active)
2750
    {
2751
        // Tabbing or Ctrl+Click on Drag turns it into an InputText
2752
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
2753
        const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
2754
        const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
2755
        if (make_active && (clicked || double_clicked))
2756
            SetKeyOwner(ImGuiKey_MouseLeft, id);
2757
        if (make_active && temp_input_allowed)
2758
            if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
2759
                temp_input_is_active = true;
2760
2761
        // (Optional) simple click (without moving) turns Drag into an InputText
2762
        if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
2763
            if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
2764
            {
2765
                g.NavActivateId = id;
2766
                g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
2767
                temp_input_is_active = true;
2768
            }
2769
2770
        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
2771
        if (make_active)
2772
            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
2773
2774
        if (make_active && !temp_input_is_active)
2775
        {
2776
            SetActiveID(id, window);
2777
            SetFocusID(id, window);
2778
            FocusWindow(window);
2779
            g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
2780
        }
2781
    }
2782
2783
    if (temp_input_is_active)
2784
    {
2785
        const bool clamp_enabled = TempInputIsClampEnabled(flags, data_type, p_min, p_max);
2786
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
2787
    }
2788
2789
    // Draw frame
2790
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
2791
    RenderNavCursor(frame_bb, id);
2792
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding);
2793
    if (color_marker != 0 && style.ColorMarkerSize > 0.0f)
2794
        RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), style.FrameRounding);
2795
    RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding);
2796
2797
    // Drag behavior
2798
    const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
2799
    if (value_changed)
2800
        MarkItemEdited(id);
2801
2802
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
2803
    char value_buf[64];
2804
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
2805
    if (g.LogEnabled)
2806
        LogSetNextTextDecoration("{", "}");
2807
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
2808
2809
    if (label_size.x > 0.0f)
2810
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
2811
2812
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
2813
    return value_changed;
2814
}
2815
2816
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)
2817
{
2818
    ImGuiWindow* window = GetCurrentWindow();
2819
    if (window->SkipItems)
2820
        return false;
2821
2822
    ImGuiContext& g = *GImGui;
2823
    bool value_changed = false;
2824
    BeginGroup();
2825
    PushID(label);
2826
    PushMultiItemsWidths(components, CalcItemWidth());
2827
    size_t type_size = GDataTypeInfo[data_type].Size;
2828
    for (int i = 0; i < components; i++)
2829
    {
2830
        PushID(i);
2831
        if (i > 0)
2832
            SameLine(0, g.Style.ItemInnerSpacing.x);
2833
        if (flags & ImGuiSliderFlags_ColorMarkers)
2834
            SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]);
2835
        value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
2836
        PopID();
2837
        PopItemWidth();
2838
        p_data = (void*)((char*)p_data + type_size);
2839
    }
2840
    PopID();
2841
2842
    const char* label_end = FindRenderedTextEnd(label);
2843
    if (label != label_end)
2844
    {
2845
        SameLine(0, g.Style.ItemInnerSpacing.x);
2846
        TextEx(label, label_end);
2847
    }
2848
2849
    EndGroup();
2850
    return value_changed;
2851
}
2852
2853
bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2854
{
2855
    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
2856
}
2857
2858
bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2859
{
2860
    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
2861
}
2862
2863
bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2864
{
2865
    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
2866
}
2867
2868
bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
2869
{
2870
    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
2871
}
2872
2873
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2874
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)
2875
{
2876
    ImGuiWindow* window = GetCurrentWindow();
2877
    if (window->SkipItems)
2878
        return false;
2879
2880
    ImGuiContext& g = *GImGui;
2881
    PushID(label);
2882
    BeginGroup();
2883
    PushMultiItemsWidths(2, CalcItemWidth());
2884
2885
    float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
2886
    float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2887
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2888
    bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
2889
    PopItemWidth();
2890
    SameLine(0, g.Style.ItemInnerSpacing.x);
2891
2892
    float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2893
    float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
2894
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2895
    value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
2896
    PopItemWidth();
2897
    SameLine(0, g.Style.ItemInnerSpacing.x);
2898
2899
    TextEx(label, FindRenderedTextEnd(label));
2900
    EndGroup();
2901
    PopID();
2902
2903
    return value_changed;
2904
}
2905
2906
// NB: v_speed is float to allow adjusting the drag speed with more precision
2907
bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2908
{
2909
    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
2910
}
2911
2912
bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2913
{
2914
    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
2915
}
2916
2917
bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2918
{
2919
    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
2920
}
2921
2922
bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
2923
{
2924
    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
2925
}
2926
2927
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
2928
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)
2929
{
2930
    ImGuiWindow* window = GetCurrentWindow();
2931
    if (window->SkipItems)
2932
        return false;
2933
2934
    ImGuiContext& g = *GImGui;
2935
    PushID(label);
2936
    BeginGroup();
2937
    PushMultiItemsWidths(2, CalcItemWidth());
2938
2939
    int min_min = (v_min >= v_max) ? INT_MIN : v_min;
2940
    int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
2941
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
2942
    bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
2943
    PopItemWidth();
2944
    SameLine(0, g.Style.ItemInnerSpacing.x);
2945
2946
    int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
2947
    int max_max = (v_min >= v_max) ? INT_MAX : v_max;
2948
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
2949
    value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
2950
    PopItemWidth();
2951
    SameLine(0, g.Style.ItemInnerSpacing.x);
2952
2953
    TextEx(label, FindRenderedTextEnd(label));
2954
    EndGroup();
2955
    PopID();
2956
2957
    return value_changed;
2958
}
2959
2960
//-------------------------------------------------------------------------
2961
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
2962
//-------------------------------------------------------------------------
2963
// - ScaleRatioFromValueT<> [Internal]
2964
// - ScaleValueFromRatioT<> [Internal]
2965
// - SliderBehaviorT<>() [Internal]
2966
// - SliderBehavior() [Internal]
2967
// - SliderScalar()
2968
// - SliderScalarN()
2969
// - SliderFloat()
2970
// - SliderFloat2()
2971
// - SliderFloat3()
2972
// - SliderFloat4()
2973
// - SliderAngle()
2974
// - SliderInt()
2975
// - SliderInt2()
2976
// - SliderInt3()
2977
// - SliderInt4()
2978
// - VSliderScalar()
2979
// - VSliderFloat()
2980
// - VSliderInt()
2981
//-------------------------------------------------------------------------
2982
2983
// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
2984
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
2985
float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
2986
{
2987
    if (v_min == v_max)
2988
        return 0.0f;
2989
    IM_UNUSED(data_type);
2990
2991
    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
2992
    if (logarithmic_zero_epsilon > 0.0f) // == is_logarithmic from caller
2993
    {
2994
        bool flipped = v_max < v_min;
2995
2996
        if (flipped) // Handle the case where the range is backwards
2997
            ImSwap(v_min, v_max);
2998
2999
        // Fudge min/max to avoid getting close to log(0)
3000
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
3001
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
3002
3003
        // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
3004
        if ((v_min == 0.0f) && (v_max < 0.0f))
3005
            v_min_fudged = -logarithmic_zero_epsilon;
3006
        else if ((v_max == 0.0f) && (v_min < 0.0f))
3007
            v_max_fudged = -logarithmic_zero_epsilon;
3008
3009
        float result;
3010
        if (v_clamped <= v_min_fudged)
3011
            result = 0.0f; // Workaround for values that are in-range but below our fudge
3012
        else if (v_clamped >= v_max_fudged)
3013
            result = 1.0f; // Workaround for values that are in-range but above our fudge
3014
        else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
3015
        {
3016
            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)
3017
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
3018
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
3019
            if (v == 0.0f)
3020
                result = zero_point_center; // Special case for exactly zero
3021
            else if (v < 0.0f)
3022
                result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
3023
            else
3024
                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));
3025
        }
3026
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
3027
            result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
3028
        else
3029
            result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
3030
3031
        return flipped ? (1.0f - result) : result;
3032
    }
3033
    else
3034
    {
3035
        // Linear slider
3036
        return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
3037
    }
3038
}
3039
3040
// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
3041
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
3042
TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
3043
{
3044
    // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
3045
    // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
3046
    if (t <= 0.0f || v_min == v_max)
3047
        return v_min;
3048
    if (t >= 1.0f)
3049
        return v_max;
3050
3051
    TYPE result = (TYPE)0;
3052
    if (logarithmic_zero_epsilon > 0.0f) // == is_logarithmic from caller
3053
    {
3054
        // Fudge min/max to avoid getting silly results close to zero
3055
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
3056
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
3057
3058
        const bool flipped = v_max < v_min; // Check if range is "backwards"
3059
        if (flipped)
3060
            ImSwap(v_min_fudged, v_max_fudged);
3061
3062
        // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
3063
        if ((v_max == 0.0f) && (v_min < 0.0f))
3064
            v_max_fudged = -logarithmic_zero_epsilon;
3065
3066
        float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
3067
3068
        if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
3069
        {
3070
            float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
3071
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
3072
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
3073
            if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
3074
                result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
3075
            else if (t_with_flip < zero_point_center)
3076
                result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
3077
            else
3078
                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))));
3079
        }
3080
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
3081
            result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
3082
        else
3083
            result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
3084
    }
3085
    else
3086
    {
3087
        // Linear slider
3088
        const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
3089
        if (is_floating_point)
3090
        {
3091
            result = ImLerp(v_min, v_max, t);
3092
        }
3093
        else if (t < 1.0)
3094
        {
3095
            // - For integer values we want the clicking position to match the grab box so we round above
3096
            //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
3097
            // - 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
3098
            //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
3099
            FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
3100
            result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
3101
        }
3102
    }
3103
3104
    return result;
3105
}
3106
3107
// FIXME: Try to move more of the code into shared SliderBehavior()
3108
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
3109
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)
3110
{
3111
    ImGuiContext& g = *GImGui;
3112
    const ImGuiStyle& style = g.Style;
3113
3114
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
3115
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
3116
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
3117
    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.
3118
3119
    // Calculate bounds
3120
    const float grab_padding = 2.0f; // FIXME: Should be part of style.
3121
    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
3122
    float grab_sz = style.GrabMinSize;
3123
    if (!is_floating_point && v_range_f >= 0.0f)                         // v_range_f < 0 may happen on integer overflows
3124
        grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
3125
    grab_sz = ImMin(grab_sz, slider_sz);
3126
    const float slider_usable_sz = slider_sz - grab_sz;
3127
    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
3128
    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
3129
3130
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
3131
    float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
3132
    if (is_logarithmic)
3133
    {
3134
        // 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.
3135
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
3136
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
3137
        zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
3138
    }
3139
3140
    // Process interacting with the slider
3141
    bool value_changed = false;
3142
    if (g.ActiveId == id)
3143
    {
3144
        bool set_new_value = false;
3145
        float clicked_t = 0.0f;
3146
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
3147
        {
3148
            if (!g.IO.MouseDown[0])
3149
            {
3150
                ClearActiveID();
3151
            }
3152
            else
3153
            {
3154
                const float mouse_abs_pos = g.IO.MousePos[axis];
3155
                if (g.ActiveIdIsJustActivated)
3156
                {
3157
                    float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3158
                    if (axis == ImGuiAxis_Y)
3159
                        grab_t = 1.0f - grab_t;
3160
                    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
3161
                    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.
3162
                    g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
3163
                }
3164
                if (slider_usable_sz > 0.0f)
3165
                    clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
3166
                if (axis == ImGuiAxis_Y)
3167
                    clicked_t = 1.0f - clicked_t;
3168
                set_new_value = true;
3169
            }
3170
        }
3171
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
3172
        {
3173
            if (g.ActiveIdIsJustActivated)
3174
            {
3175
                g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
3176
                g.SliderCurrentAccumDirty = false;
3177
            }
3178
3179
            float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
3180
            if (input_delta != 0.0f)
3181
            {
3182
                const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
3183
                const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
3184
                const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
3185
                if (decimal_precision > 0)
3186
                {
3187
                    input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds
3188
                    if (tweak_slow)
3189
                        input_delta /= 10.0f;
3190
                }
3191
                else
3192
                {
3193
                    if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
3194
                        input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps
3195
                    else
3196
                        input_delta /= 100.0f;
3197
                }
3198
                if (tweak_fast)
3199
                    input_delta *= 10.0f;
3200
3201
                g.SliderCurrentAccum += input_delta;
3202
                g.SliderCurrentAccumDirty = true;
3203
            }
3204
3205
            float delta = g.SliderCurrentAccum;
3206
            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
3207
            {
3208
                ClearActiveID();
3209
            }
3210
            else if (g.SliderCurrentAccumDirty)
3211
            {
3212
                clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3213
3214
                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
3215
                {
3216
                    set_new_value = false;
3217
                    g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
3218
                }
3219
                else
3220
                {
3221
                    set_new_value = true;
3222
                    float old_clicked_t = clicked_t;
3223
                    clicked_t = ImSaturate(clicked_t + delta);
3224
3225
                    // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
3226
                    TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3227
                    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
3228
                        v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
3229
                    float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3230
3231
                    if (delta > 0)
3232
                        g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
3233
                    else
3234
                        g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
3235
                }
3236
3237
                g.SliderCurrentAccumDirty = false;
3238
            }
3239
        }
3240
3241
        if (set_new_value)
3242
            if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
3243
                set_new_value = false;
3244
3245
        if (set_new_value)
3246
        {
3247
            TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3248
3249
            // Round to user desired precision based on format string
3250
            if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
3251
                v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
3252
3253
            // Apply result
3254
            if (*v != v_new)
3255
            {
3256
                *v = v_new;
3257
                value_changed = true;
3258
            }
3259
        }
3260
    }
3261
3262
    if (slider_sz < 1.0f)
3263
    {
3264
        *out_grab_bb = ImRect(bb.Min, bb.Min);
3265
    }
3266
    else
3267
    {
3268
        // Output grab position so it can be displayed by the caller
3269
        float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, logarithmic_zero_epsilon, zero_deadzone_halfsize);
3270
        if (axis == ImGuiAxis_Y)
3271
            grab_t = 1.0f - grab_t;
3272
        const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
3273
        if (axis == ImGuiAxis_X)
3274
            *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);
3275
        else
3276
            *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);
3277
    }
3278
3279
    return value_changed;
3280
}
3281
3282
// For 32-bit and larger types, slider bounds are limited to half the natural type range.
3283
// 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.
3284
// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
3285
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)
3286
{
3287
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
3288
    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.");
3289
    IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()
3290
3291
    switch (data_type)
3292
    {
3293
    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; }
3294
    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; }
3295
    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; }
3296
    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; }
3297
    case ImGuiDataType_S32:
3298
        IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
3299
        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);
3300
    case ImGuiDataType_U32:
3301
        IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
3302
        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);
3303
    case ImGuiDataType_S64:
3304
        IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
3305
        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);
3306
    case ImGuiDataType_U64:
3307
        IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
3308
        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);
3309
    case ImGuiDataType_Float:
3310
        IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
3311
        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);
3312
    case ImGuiDataType_Double:
3313
        IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
3314
        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);
3315
    case ImGuiDataType_COUNT: break;
3316
    }
3317
    IM_ASSERT(0);
3318
    return false;
3319
}
3320
3321
// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
3322
// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3323
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)
3324
{
3325
    ImGuiWindow* window = GetCurrentWindow();
3326
    if (window->SkipItems)
3327
        return false;
3328
3329
    ImGuiContext& g = *GImGui;
3330
    const ImGuiStyle& style = g.Style;
3331
    const ImGuiID id = window->GetID(label);
3332
    const float w = CalcItemWidth();
3333
    const ImU32 color_marker = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasColorMarker) ? g.NextItemData.ColorMarker : 0;
3334
3335
    const char* label_end = FindRenderedTextEnd(label);
3336
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
3337
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
3338
    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));
3339
3340
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
3341
    ItemSize(total_bb, style.FramePadding.y);
3342
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
3343
        return false;
3344
3345
    // Default format string when passing NULL
3346
    if (format == NULL)
3347
        format = DataTypeGetInfo(data_type)->PrintFmt;
3348
3349
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
3350
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
3351
    if (!temp_input_is_active)
3352
    {
3353
        // Tabbing or Ctrl+Click on Slider turns it into an input box
3354
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
3355
        const bool make_active = (clicked || g.NavActivateId == id);
3356
        if (make_active && clicked)
3357
            SetKeyOwner(ImGuiKey_MouseLeft, id);
3358
        if (make_active && temp_input_allowed)
3359
            if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
3360
                temp_input_is_active = true;
3361
3362
        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
3363
        if (make_active)
3364
            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
3365
3366
        if (make_active && !temp_input_is_active)
3367
        {
3368
            SetActiveID(id, window);
3369
            SetFocusID(id, window);
3370
            FocusWindow(window);
3371
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
3372
        }
3373
    }
3374
3375
    if (temp_input_is_active)
3376
    {
3377
        // Only clamp Ctrl+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
3378
        const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0; // Don't use TempInputIsClampEnabled()
3379
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
3380
    }
3381
3382
    // Draw frame
3383
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3384
    RenderNavCursor(frame_bb, id);
3385
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, false, style.FrameRounding);
3386
    if (color_marker != 0 && style.ColorMarkerSize > 0.0f)
3387
        RenderColorComponentMarker(frame_bb, GetColorU32(color_marker), style.FrameRounding);
3388
    RenderFrameBorder(frame_bb.Min, frame_bb.Max, g.Style.FrameRounding);
3389
3390
    // Slider behavior
3391
    ImRect grab_bb;
3392
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
3393
    if (value_changed)
3394
        MarkItemEdited(id);
3395
3396
    // Render grab
3397
    if (grab_bb.Max.x > grab_bb.Min.x)
3398
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3399
3400
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3401
    char value_buf[64];
3402
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
3403
    if (g.LogEnabled)
3404
        LogSetNextTextDecoration("{", "}");
3405
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
3406
3407
    if (label_size.x > 0.0f)
3408
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
3409
3410
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
3411
    return value_changed;
3412
}
3413
3414
// Add multiple sliders on 1 line for compact edition of multiple components
3415
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)
3416
{
3417
    ImGuiWindow* window = GetCurrentWindow();
3418
    if (window->SkipItems)
3419
        return false;
3420
3421
    ImGuiContext& g = *GImGui;
3422
    bool value_changed = false;
3423
    BeginGroup();
3424
    PushID(label);
3425
    PushMultiItemsWidths(components, CalcItemWidth());
3426
    size_t type_size = GDataTypeInfo[data_type].Size;
3427
    for (int i = 0; i < components; i++)
3428
    {
3429
        PushID(i);
3430
        if (i > 0)
3431
            SameLine(0, g.Style.ItemInnerSpacing.x);
3432
        if (flags & ImGuiSliderFlags_ColorMarkers)
3433
            SetNextItemColorMarker(GDefaultRgbaColorMarkers[i]);
3434
        value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
3435
        PopID();
3436
        PopItemWidth();
3437
        v = (void*)((char*)v + type_size);
3438
    }
3439
    PopID();
3440
3441
    const char* label_end = FindRenderedTextEnd(label);
3442
    if (label != label_end)
3443
    {
3444
        SameLine(0, g.Style.ItemInnerSpacing.x);
3445
        TextEx(label, label_end);
3446
    }
3447
3448
    EndGroup();
3449
    return value_changed;
3450
}
3451
3452
bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3453
{
3454
    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3455
}
3456
3457
bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3458
{
3459
    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
3460
}
3461
3462
bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3463
{
3464
    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
3465
}
3466
3467
bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3468
{
3469
    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
3470
}
3471
3472
bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
3473
{
3474
    if (format == NULL)
3475
        format = "%.0f deg";
3476
    float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
3477
    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
3478
    if (value_changed)
3479
        *v_rad = v_deg * (2 * IM_PI) / 360.0f;
3480
    return value_changed;
3481
}
3482
3483
bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3484
{
3485
    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3486
}
3487
3488
bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3489
{
3490
    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
3491
}
3492
3493
bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3494
{
3495
    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
3496
}
3497
3498
bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3499
{
3500
    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
3501
}
3502
3503
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)
3504
{
3505
    ImGuiWindow* window = GetCurrentWindow();
3506
    if (window->SkipItems)
3507
        return false;
3508
3509
    ImGuiContext& g = *GImGui;
3510
    const ImGuiStyle& style = g.Style;
3511
    const ImGuiID id = window->GetID(label);
3512
3513
    const char* label_end = FindRenderedTextEnd(label);
3514
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
3515
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
3516
    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));
3517
3518
    ItemSize(bb, style.FramePadding.y);
3519
    if (!ItemAdd(frame_bb, id))
3520
        return false;
3521
3522
    // Default format string when passing NULL
3523
    if (format == NULL)
3524
        format = DataTypeGetInfo(data_type)->PrintFmt;
3525
3526
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
3527
    const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
3528
    if (clicked || g.NavActivateId == id)
3529
    {
3530
        if (clicked)
3531
            SetKeyOwner(ImGuiKey_MouseLeft, id);
3532
        SetActiveID(id, window);
3533
        SetFocusID(id, window);
3534
        FocusWindow(window);
3535
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
3536
    }
3537
3538
    // Draw frame
3539
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
3540
    RenderNavCursor(frame_bb, id);
3541
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
3542
3543
    // Slider behavior
3544
    ImRect grab_bb;
3545
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
3546
    if (value_changed)
3547
        MarkItemEdited(id);
3548
3549
    // Render grab
3550
    if (grab_bb.Max.y > grab_bb.Min.y)
3551
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
3552
3553
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
3554
    // For the vertical slider we allow centered text to overlap the frame padding
3555
    char value_buf[64];
3556
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
3557
    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));
3558
    if (label_size.x > 0.0f)
3559
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
3560
3561
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
3562
    return value_changed;
3563
}
3564
3565
bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
3566
{
3567
    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
3568
}
3569
3570
bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
3571
{
3572
    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
3573
}
3574
3575
//-------------------------------------------------------------------------
3576
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
3577
//-------------------------------------------------------------------------
3578
// - ImParseFormatFindStart() [Internal]
3579
// - ImParseFormatFindEnd() [Internal]
3580
// - ImParseFormatTrimDecorations() [Internal]
3581
// - ImParseFormatSanitizeForPrinting() [Internal]
3582
// - ImParseFormatSanitizeForScanning() [Internal]
3583
// - ImParseFormatPrecision() [Internal]
3584
// - TempInputText() [Internal]
3585
// - TempInputScalar() [Internal]
3586
// - InputScalar()
3587
// - InputScalarN()
3588
// - InputFloat()
3589
// - InputFloat2()
3590
// - InputFloat3()
3591
// - InputFloat4()
3592
// - InputInt()
3593
// - InputInt2()
3594
// - InputInt3()
3595
// - InputInt4()
3596
// - InputDouble()
3597
//-------------------------------------------------------------------------
3598
3599
// We don't use strchr() because our strings are usually very short and often start with '%'
3600
const char* ImParseFormatFindStart(const char* fmt)
3601
{
3602
    while (char c = fmt[0])
3603
    {
3604
        if (c == '%' && fmt[1] != '%')
3605
            return fmt;
3606
        else if (c == '%')
3607
            fmt++;
3608
        fmt++;
3609
    }
3610
    return fmt;
3611
}
3612
3613
const char* ImParseFormatFindEnd(const char* fmt)
3614
{
3615
    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
3616
    if (fmt[0] != '%')
3617
        return fmt;
3618
    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
3619
    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
3620
    for (char c; (c = *fmt) != 0; fmt++)
3621
    {
3622
        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
3623
            return fmt + 1;
3624
        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
3625
            return fmt + 1;
3626
    }
3627
    return fmt;
3628
}
3629
3630
// Extract the format out of a format string with leading or trailing decorations
3631
//  fmt = "blah blah"  -> return ""
3632
//  fmt = "%.3f"       -> return fmt
3633
//  fmt = "hello %.3f" -> return fmt + 6
3634
//  fmt = "%.3f hello" -> return buf written with "%.3f"
3635
const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
3636
{
3637
    const char* fmt_start = ImParseFormatFindStart(fmt);
3638
    if (fmt_start[0] != '%')
3639
        return "";
3640
    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
3641
    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
3642
        return fmt_start;
3643
    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
3644
    return buf;
3645
}
3646
3647
// Sanitize format
3648
// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
3649
// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
3650
void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3651
{
3652
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3653
    IM_UNUSED(fmt_out_size);
3654
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3655
    while (fmt_in < fmt_end)
3656
    {
3657
        char c = *fmt_in++;
3658
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3659
            *(fmt_out++) = c;
3660
    }
3661
    *fmt_out = 0; // Zero-terminate
3662
}
3663
3664
// - 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"
3665
const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
3666
{
3667
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
3668
    const char* fmt_out_begin = fmt_out;
3669
    IM_UNUSED(fmt_out_size);
3670
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
3671
    bool has_type = false;
3672
    while (fmt_in < fmt_end)
3673
    {
3674
        char c = *fmt_in++;
3675
        if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
3676
            continue;
3677
        has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
3678
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
3679
            *(fmt_out++) = c;
3680
    }
3681
    *fmt_out = 0; // Zero-terminate
3682
    return fmt_out_begin;
3683
}
3684
3685
template<typename TYPE>
3686
static const char* ImAtoi(const char* src, TYPE* output)
3687
{
3688
    int negative = 0;
3689
    if (*src == '-') { negative = 1; src++; }
3690
    if (*src == '+') { src++; }
3691
    TYPE v = 0;
3692
    while (*src >= '0' && *src <= '9')
3693
        v = (v * 10) + (*src++ - '0');
3694
    *output = negative ? -v : v;
3695
    return src;
3696
}
3697
3698
// Parse display precision back from the display format string
3699
// 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.
3700
int ImParseFormatPrecision(const char* fmt, int default_precision)
3701
{
3702
    fmt = ImParseFormatFindStart(fmt);
3703
    if (fmt[0] != '%')
3704
        return default_precision;
3705
    fmt++;
3706
    while (*fmt >= '0' && *fmt <= '9')
3707
        fmt++;
3708
    int precision = INT_MAX;
3709
    if (*fmt == '.')
3710
    {
3711
        fmt = ImAtoi<int>(fmt + 1, &precision);
3712
        if (precision < 0 || precision > 99)
3713
            precision = default_precision;
3714
    }
3715
    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
3716
        precision = -1;
3717
    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
3718
        precision = -1;
3719
    return (precision == INT_MAX) ? default_precision : precision;
3720
}
3721
3722
// Create text input in place of another active widget (e.g. used when doing a Ctrl+Click on drag/slider widgets)
3723
// - This must be submitted right after the item it is overlaying.
3724
// FIXME: Facilitate using this in variety of other situations.
3725
bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3726
{
3727
    // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
3728
    // We clear ActiveID on the first frame to allow the InputText() taking it back.
3729
    ImGuiContext& g = *GImGui;
3730
    ImGuiWindow* window = g.CurrentWindow;
3731
3732
    const bool init = (g.TempInputId != id);
3733
    if (init)
3734
        ClearActiveID();
3735
3736
    ImVec2 backup_pos = window->DC.CursorPos;
3737
    window->DC.CursorPos = bb.Min;
3738
    g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId; // Using ImGuiInputTextFlags_MergedItem above will skip ItemAdd() so we poke here.
3739
    bool value_changed = InputTextEx(label, NULL, buf, (int)buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_TempInput | ImGuiInputTextFlags_AutoSelectAll, callback, user_data);
3740
    KeepAliveID(id); // Not done because of ImGuiInputTextFlags_TempInput
3741
    if (init)
3742
    {
3743
        // First frame we started displaying the InputText widget, we expect it to take the active id.
3744
        IM_ASSERT(g.ActiveId == id);
3745
        g.TempInputId = g.ActiveId;
3746
    }
3747
    if (g.ActiveId != id)
3748
        g.TempInputId = 0;
3749
    window->DC.CursorPos = backup_pos;
3750
    return value_changed;
3751
}
3752
3753
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
3754
// This is intended: this way we allow Ctrl+Click manual input to set a value out of bounds, for maximum flexibility.
3755
// However this may not be ideal for all uses, as some user code may break on out of bound values.
3756
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)
3757
{
3758
    // FIXME: May need to clarify display behavior if format doesn't contain %.
3759
    // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
3760
    ImGuiContext& g = *GImGui;
3761
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
3762
    char fmt_buf[32];
3763
    char data_buf[32];
3764
    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_COUNTOF(fmt_buf));
3765
    if (format[0] == 0)
3766
        format = type_info->PrintFmt;
3767
    DataTypeFormatString(data_buf, IM_COUNTOF(data_buf), data_type, p_data, format);
3768
    ImStrTrimBlanks(data_buf);
3769
3770
    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
3771
    g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
3772
    if (!TempInputText(bb, id, label, data_buf, IM_COUNTOF(data_buf), flags))
3773
        return false;
3774
3775
    // Backup old value
3776
    size_t data_type_size = type_info->Size;
3777
    ImGuiDataTypeStorage data_backup;
3778
    memcpy(&data_backup, p_data, data_type_size);
3779
3780
    // Apply new value (or operations) then clamp
3781
    DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);
3782
    if (p_clamp_min || p_clamp_max)
3783
    {
3784
        if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
3785
            ImSwap(p_clamp_min, p_clamp_max);
3786
        DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
3787
    }
3788
3789
    // Only mark as edited if new value is different
3790
    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
3791
    bool value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
3792
    if (value_changed)
3793
        MarkItemEdited(id);
3794
    return value_changed;
3795
}
3796
3797
void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)
3798
{
3799
    ImGuiContext& g = *GImGui;
3800
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal;
3801
    memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);
3802
}
3803
3804
// 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.
3805
// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
3806
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)
3807
{
3808
    ImGuiWindow* window = GetCurrentWindow();
3809
    if (window->SkipItems)
3810
        return false;
3811
3812
    ImGuiContext& g = *GImGui;
3813
    ImGuiStyle& style = g.Style;
3814
    //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()!
3815
3816
    if (format == NULL)
3817
        format = DataTypeGetInfo(data_type)->PrintFmt;
3818
3819
    void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;
3820
3821
    char buf[64];
3822
    if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
3823
        buf[0] = 0;
3824
    else
3825
        DataTypeFormatString(buf, IM_COUNTOF(buf), data_type, p_data, format);
3826
3827
    // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
3828
    // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
3829
    g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
3830
    flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
3831
3832
    const bool has_step_buttons = (p_step != NULL);
3833
    const float button_size = has_step_buttons ? GetFrameHeight() : 0.0f;
3834
    bool ret;
3835
    if (has_step_buttons)
3836
    {
3837
        // With Step Buttons
3838
        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
3839
        PushID(label);
3840
        SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
3841
        ret = InputText("", buf, IM_COUNTOF(buf), flags); // PushID(label) + "" gives us the expected ID from outside point of view
3842
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
3843
    }
3844
    else
3845
    {
3846
        // Without Step Buttons
3847
        ret = InputText(label, buf, IM_COUNTOF(buf), flags);
3848
    }
3849
3850
    // Apply
3851
    bool input_edited = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_EditedInternal) != 0; // We would be using 'ret' if ImGuiInputTextFlags_EnterReturnsTrue was not involved.
3852
    bool value_changed = input_edited ? DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL) : false;
3853
3854
    // Step buttons
3855
    if (has_step_buttons)
3856
    {
3857
        const ImVec2 backup_frame_padding = style.FramePadding;
3858
        style.FramePadding.x = style.FramePadding.y;
3859
        if (flags & ImGuiInputTextFlags_ReadOnly)
3860
            BeginDisabled();
3861
        PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
3862
        SameLine(0, style.ItemInnerSpacing.x);
3863
        if (ButtonEx("-", ImVec2(button_size, button_size)))
3864
        {
3865
            DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3866
            value_changed = ret = true;
3867
        }
3868
        SameLine(0, style.ItemInnerSpacing.x);
3869
        if (ButtonEx("+", ImVec2(button_size, button_size)))
3870
        {
3871
            DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
3872
            value_changed = ret = true;
3873
        }
3874
        PopItemFlag();
3875
        if (flags & ImGuiInputTextFlags_ReadOnly)
3876
            EndDisabled();
3877
3878
        const char* label_end = FindRenderedTextEnd(label);
3879
        if (label != label_end)
3880
        {
3881
            SameLine(0, style.ItemInnerSpacing.x);
3882
            TextEx(label, label_end);
3883
        }
3884
        style.FramePadding = backup_frame_padding;
3885
3886
        PopID();
3887
        EndGroup();
3888
    }
3889
3890
    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
3891
    if (value_changed)
3892
        MarkItemEdited(g.LastItemData.ID);
3893
3894
    if (flags & ImGuiInputTextFlags_EnterReturnsTrue)
3895
        return ret;
3896
    return value_changed;
3897
}
3898
3899
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)
3900
{
3901
    ImGuiWindow* window = GetCurrentWindow();
3902
    if (window->SkipItems)
3903
        return false;
3904
3905
    ImGuiContext& g = *GImGui;
3906
    bool value_changed = false;
3907
    BeginGroup();
3908
    PushID(label);
3909
    PushMultiItemsWidths(components, CalcItemWidth());
3910
    size_t type_size = GDataTypeInfo[data_type].Size;
3911
    for (int i = 0; i < components; i++)
3912
    {
3913
        PushID(i);
3914
        if (i > 0)
3915
            SameLine(0, g.Style.ItemInnerSpacing.x);
3916
        value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
3917
        PopID();
3918
        PopItemWidth();
3919
        p_data = (void*)((char*)p_data + type_size);
3920
    }
3921
    PopID();
3922
3923
    const char* label_end = FindRenderedTextEnd(label);
3924
    if (label != label_end)
3925
    {
3926
        SameLine(0.0f, g.Style.ItemInnerSpacing.x);
3927
        TextEx(label, label_end);
3928
    }
3929
3930
    EndGroup();
3931
    return value_changed;
3932
}
3933
3934
bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
3935
{
3936
    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
3937
}
3938
3939
bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
3940
{
3941
    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
3942
}
3943
3944
bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
3945
{
3946
    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
3947
}
3948
3949
bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
3950
{
3951
    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
3952
}
3953
3954
bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
3955
{
3956
    // 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.
3957
    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
3958
    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
3959
}
3960
3961
bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
3962
{
3963
    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
3964
}
3965
3966
bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
3967
{
3968
    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
3969
}
3970
3971
bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
3972
{
3973
    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
3974
}
3975
3976
bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
3977
{
3978
    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
3979
}
3980
3981
//-------------------------------------------------------------------------
3982
// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
3983
//-------------------------------------------------------------------------
3984
// - imstb_textedit.h include
3985
// - InputText()
3986
// - InputTextWithHint()
3987
// - InputTextMultiline()
3988
// - InputTextEx() [Internal]
3989
// - DebugNodeInputTextState() [Internal]
3990
//-------------------------------------------------------------------------
3991
3992
namespace ImStb
3993
{
3994
#include "imstb_textedit.h"
3995
}
3996
3997
// If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp!
3998
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
3999
{
4000
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
4001
    return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
4002
}
4003
4004
bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
4005
{
4006
    return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
4007
}
4008
4009
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
4010
{
4011
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.
4012
    return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
4013
}
4014
4015
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags)
4016
{
4017
    ImGuiContext& g = *ctx;
4018
    ImGuiInputTextState* obj = &g.InputTextState;
4019
    IM_ASSERT(text_end_display >= text_begin && text_end_display <= text_end);
4020
    return ImFontCalcTextSizeEx(g.Font, g.FontSize, FLT_MAX, obj->WrapWidth, text_begin, text_end_display, text_end, out_remaining, out_offset, flags);
4021
}
4022
4023
// 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)
4024
// With our UTF-8 use of stb_textedit:
4025
// - 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.
4026
// - 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).
4027
// - ...but we don't use that feature.
4028
namespace ImStb
4029
{
4030
static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->TextLen; }
4031
static char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx >= 0 && idx <= obj->TextLen); return obj->TextSrc[idx]; }
4032
static float   STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx)  { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; }
4033
static char    STB_TEXTEDIT_NEWLINE = '\n';
4034
static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
4035
{
4036
    const char* text = obj->TextSrc;
4037
    const char* text_remaining = NULL;
4038
    const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, text + obj->TextLen, &text_remaining, NULL, ImDrawTextFlags_StopOnNewLine | ImDrawTextFlags_WrapKeepBlanks);
4039
    r->x0 = 0.0f;
4040
    r->x1 = size.x;
4041
    r->baseline_y_delta = size.y;
4042
    r->ymin = 0.0f;
4043
    r->ymax = size.y;
4044
    r->num_chars = (int)(text_remaining - (text + line_start_idx));
4045
}
4046
4047
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX  IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL
4048
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX  IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL
4049
4050
static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
4051
{
4052
    if (idx >= obj->TextLen)
4053
        return obj->TextLen + 1;
4054
    unsigned int c;
4055
    return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);
4056
}
4057
4058
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
4059
{
4060
    if (idx <= 0)
4061
        return -1;
4062
    const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);
4063
    return (int)(p - obj->TextSrc);
4064
}
4065
4066
static bool ImCharIsSeparatorW(unsigned int c)
4067
{
4068
    static const unsigned int separator_list[] =
4069
    {
4070
        ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,
4071
        '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F,
4072
        '\n', '\r',
4073
    };
4074
    for (unsigned int separator : separator_list)
4075
        if (c == separator)
4076
            return true;
4077
    return false;
4078
}
4079
4080
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
4081
{
4082
    // 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.
4083
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
4084
        return 0;
4085
4086
    const char* curr_p = obj->TextSrc + idx;
4087
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
4088
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);
4089
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);
4090
4091
    bool prev_white = ImCharIsBlankW(prev_c);
4092
    bool prev_separ = ImCharIsSeparatorW(prev_c);
4093
    bool curr_white = ImCharIsBlankW(curr_c);
4094
    bool curr_separ = ImCharIsSeparatorW(curr_c);
4095
    return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
4096
}
4097
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
4098
{
4099
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
4100
        return 0;
4101
4102
    const char* curr_p = obj->TextSrc + idx;
4103
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
4104
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);
4105
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);
4106
4107
    bool prev_white = ImCharIsBlankW(prev_c);
4108
    bool prev_separ = ImCharIsSeparatorW(prev_c);
4109
    bool curr_white = ImCharIsBlankW(curr_c);
4110
    bool curr_separ = ImCharIsSeparatorW(curr_c);
4111
    return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
4112
}
4113
static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)
4114
{
4115
    idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
4116
    while (idx >= 0 && !is_word_boundary_from_right(obj, idx))
4117
        idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
4118
    return idx < 0 ? 0 : idx;
4119
}
4120
static int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx)
4121
{
4122
    int len = obj->TextLen;
4123
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4124
    while (idx < len && !is_word_boundary_from_left(obj, idx))
4125
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4126
    return idx > len ? len : idx;
4127
}
4128
static int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx)
4129
{
4130
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4131
    int len = obj->TextLen;
4132
    while (idx < len && !is_word_boundary_from_right(obj, idx))
4133
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4134
    return idx > len ? len : idx;
4135
}
4136
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); }
4137
#define STB_TEXTEDIT_MOVEWORDLEFT       STB_TEXTEDIT_MOVEWORDLEFT_IMPL  // They need to be #define for stb_textedit.h
4138
#define STB_TEXTEDIT_MOVEWORDRIGHT      STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
4139
4140
// Reimplementation of stb_textedit_move_line_start()/stb_textedit_move_line_end() which supports word-wrapping.
4141
static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
4142
{
4143
    if (state->single_line)
4144
        return 0;
4145
4146
    if (obj->WrapWidth > 0.0f)
4147
    {
4148
        ImGuiContext& g = *obj->Ctx;
4149
        const char* p_cursor = obj->TextSrc + cursor;
4150
        const char* p_bol = ImStrbol(p_cursor, obj->TextSrc);
4151
        const char* p = p_bol;
4152
        const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
4153
        while (p >= p_bol)
4154
        {
4155
            const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
4156
            if (p == p_cursor) // If we are already on a visible beginning-of-line, return real beginning-of-line (would be same as regular handler below)
4157
                return (int)(p_bol - obj->TextSrc);
4158
            if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left)
4159
                return (int)(p_bol - obj->TextSrc);
4160
            if (p_eol >= p_cursor)
4161
                return (int)(p - obj->TextSrc);
4162
            p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
4163
        }
4164
    }
4165
4166
    // Regular handler, same as stb_textedit_move_line_start()
4167
    while (cursor > 0)
4168
    {
4169
        int prev_cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, cursor);
4170
        if (STB_TEXTEDIT_GETCHAR(obj, prev_cursor) == STB_TEXTEDIT_NEWLINE)
4171
            break;
4172
        cursor = prev_cursor;
4173
    }
4174
    return cursor;
4175
}
4176
4177
static int STB_TEXTEDIT_MOVELINEEND_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
4178
{
4179
    int n = STB_TEXTEDIT_STRINGLEN(obj);
4180
    if (state->single_line)
4181
        return n;
4182
4183
    if (obj->WrapWidth > 0.0f)
4184
    {
4185
        ImGuiContext& g = *obj->Ctx;
4186
        const char* p_cursor = obj->TextSrc + cursor;
4187
        const char* p = ImStrbol(p_cursor, obj->TextSrc);
4188
        const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
4189
        while (p < text_end)
4190
        {
4191
            const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
4192
            cursor = (int)(p_eol - obj->TextSrc);
4193
            if (p_eol == p_cursor && obj->LastMoveDirectionLR != ImGuiDir_Left) // If we are already on a visible end-of-line, switch to regular handle
4194
                break;
4195
            if (p_eol > p_cursor)
4196
                return cursor;
4197
            p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
4198
        }
4199
    }
4200
    // Regular handler, same as stb_textedit_move_line_end()
4201
    while (cursor < n && STB_TEXTEDIT_GETCHAR(obj, cursor) != STB_TEXTEDIT_NEWLINE)
4202
        cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, cursor);
4203
    return cursor;
4204
}
4205
4206
#define STB_TEXTEDIT_MOVELINESTART      STB_TEXTEDIT_MOVELINESTART_IMPL
4207
#define STB_TEXTEDIT_MOVELINEEND        STB_TEXTEDIT_MOVELINEEND_IMPL
4208
4209
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
4210
{
4211
    // Offset remaining text (+ copy zero terminator)
4212
    IM_ASSERT(obj->TextSrc == obj->TextA.Data);
4213
    char* dst = obj->TextA.Data + pos;
4214
    char* src = obj->TextA.Data + pos + n;
4215
    memmove(dst, src, obj->TextLen - n - pos + 1);
4216
    obj->EditedBefore = obj->EditedThisFrame = true;
4217
    obj->TextLen -= n;
4218
}
4219
4220
static int STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)
4221
{
4222
    const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4223
    const int text_len = obj->TextLen;
4224
    IM_ASSERT(pos <= text_len);
4225
4226
    // We support partial insertion (with a mod in stb_textedit.h)
4227
    const int avail = obj->BufCapacity - 1 - obj->TextLen;
4228
    if (!is_resizable && new_text_len > avail)
4229
        new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion.
4230
    if (new_text_len == 0)
4231
        return 0;
4232
4233
    // Grow internal buffer if needed
4234
    IM_ASSERT(obj->TextSrc == obj->TextA.Data);
4235
    if (text_len + new_text_len + 1 > obj->TextA.Size && is_resizable)
4236
    {
4237
        obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
4238
        obj->TextSrc = obj->TextA.Data;
4239
    }
4240
4241
    char* text = obj->TextA.Data;
4242
    if (pos != text_len)
4243
        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));
4244
    memcpy(text + pos, new_text, (size_t)new_text_len);
4245
4246
    obj->EditedBefore = obj->EditedThisFrame = true;
4247
    obj->TextLen += new_text_len;
4248
    obj->TextA[obj->TextLen] = '\0';
4249
4250
    return new_text_len;
4251
}
4252
4253
// 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)
4254
#define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
4255
#define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
4256
#define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
4257
#define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
4258
#define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
4259
#define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
4260
#define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
4261
#define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
4262
#define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
4263
#define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
4264
#define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
4265
#define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
4266
#define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
4267
#define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
4268
#define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
4269
#define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
4270
#define STB_TEXTEDIT_K_SHIFT        0x400000
4271
4272
#define IMSTB_TEXTEDIT_IMPLEMENTATION
4273
#define IMSTB_TEXTEDIT_memmove memmove
4274
#include "imstb_textedit.h"
4275
4276
// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
4277
// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
4278
static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
4279
{
4280
    stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len);
4281
    ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen);
4282
    state->cursor = state->select_start = state->select_end = 0;
4283
    if (text_len <= 0)
4284
        return;
4285
    int text_len_inserted = ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len);
4286
    if (text_len_inserted > 0)
4287
    {
4288
        state->cursor = state->select_start = state->select_end = text_len;
4289
        state->has_preferred_x = 0;
4290
        return;
4291
    }
4292
    IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
4293
}
4294
4295
} // namespace ImStb
4296
4297
// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators.
4298
ImGuiInputTextState::ImGuiInputTextState()
4299
{
4300
    memset((void*)this, 0, sizeof(*this));
4301
    Stb = IM_NEW(ImStbTexteditState);
4302
    memset(Stb, 0, sizeof(*Stb));
4303
}
4304
4305
ImGuiInputTextState::~ImGuiInputTextState()
4306
{
4307
    IM_DELETE(Stb);
4308
}
4309
4310
void ImGuiInputTextState::OnKeyPressed(int key)
4311
{
4312
    stb_textedit_key(this, Stb, key);
4313
    CursorFollow = true;
4314
    CursorAnimReset();
4315
    const int key_u = (key & ~STB_TEXTEDIT_K_SHIFT);
4316
    if (key_u == STB_TEXTEDIT_K_LEFT || key_u == STB_TEXTEDIT_K_LINESTART || key_u == STB_TEXTEDIT_K_TEXTSTART || key_u == STB_TEXTEDIT_K_BACKSPACE || key_u == STB_TEXTEDIT_K_WORDLEFT)
4317
        LastMoveDirectionLR = ImGuiDir_Left;
4318
    else if (key_u == STB_TEXTEDIT_K_RIGHT || key_u == STB_TEXTEDIT_K_LINEEND || key_u == STB_TEXTEDIT_K_TEXTEND || key_u == STB_TEXTEDIT_K_DELETE || key_u == STB_TEXTEDIT_K_WORDRIGHT)
4319
        LastMoveDirectionLR = ImGuiDir_Right;
4320
}
4321
4322
void ImGuiInputTextState::OnCharPressed(unsigned int c)
4323
{
4324
    // Convert the key to a UTF8 byte sequence.
4325
    // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
4326
    char utf8[5];
4327
    ImTextCharToUtf8(utf8, c);
4328
    stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8));
4329
    CursorFollow = true;
4330
    CursorAnimReset();
4331
}
4332
4333
// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header.
4334
void ImGuiInputTextState::CursorAnimReset()                 { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
4335
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); }
4336
bool ImGuiInputTextState::HasSelection() const              { return Stb->select_start != Stb->select_end; }
4337
void ImGuiInputTextState::ClearSelection()                  { Stb->select_start = Stb->select_end = Stb->cursor; }
4338
int  ImGuiInputTextState::GetCursorPos() const              { return Stb->cursor; }
4339
int  ImGuiInputTextState::GetSelectionStart() const         { return Stb->select_start; }
4340
int  ImGuiInputTextState::GetSelectionEnd() const           { return Stb->select_end; }
4341
void ImGuiInputTextState::SetSelection(int start, int end)  { Stb->select_start = start; Stb->cursor = Stb->select_end = end; }
4342
float ImGuiInputTextState::GetPreferredOffsetX() const      { return Stb->has_preferred_x ? Stb->preferred_x : -1; }
4343
void ImGuiInputTextState::SelectAll()                       { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
4344
void ImGuiInputTextState::ReloadUserBufAndSelectAll()       { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
4345
void ImGuiInputTextState::ReloadUserBufAndKeepSelection()   { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
4346
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd()       { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
4347
4348
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
4349
{
4350
    memset((void*)this, 0, sizeof(*this));
4351
}
4352
4353
// Public API to manipulate UTF-8 text from within a callback.
4354
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
4355
// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar
4356
// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both.
4357
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
4358
{
4359
    IM_ASSERT(pos + bytes_count <= BufTextLen);
4360
    char* dst = Buf + pos;
4361
    const char* src = Buf + pos + bytes_count;
4362
    memmove(dst, src, BufTextLen - bytes_count - pos + 1);
4363
4364
    if (CursorPos >= pos + bytes_count)
4365
        CursorPos -= bytes_count;
4366
    else if (CursorPos >= pos)
4367
        CursorPos = pos;
4368
    SelectionStart = SelectionEnd = CursorPos;
4369
    BufDirty = true;
4370
    BufTextLen -= bytes_count;
4371
}
4372
4373
void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
4374
{
4375
    // Accept null ranges
4376
    if (new_text == new_text_end)
4377
        return;
4378
4379
    ImGuiContext& g = *Ctx;
4380
    ImGuiInputTextState* obj = &g.InputTextState;
4381
    IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID);
4382
    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4383
    const bool is_readonly = (Flags & ImGuiInputTextFlags_ReadOnly) != 0;
4384
    int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);
4385
4386
    // We support partial insertion (with a mod in stb_textedit.h)
4387
    const int avail = BufSize - 1 - BufTextLen;
4388
    if (!is_resizable && new_text_len > avail)
4389
        new_text_len = (int)(ImTextFindValidUtf8CodepointEnd(new_text, new_text + new_text_len, new_text + avail) - new_text); // Truncate to closest UTF-8 codepoint. Alternative: return 0 to cancel insertion.
4390
    if (new_text_len == 0)
4391
        return;
4392
4393
    // Grow internal buffer if needed
4394
    if (new_text_len + BufTextLen + 1 > obj->TextA.Size && is_resizable && !is_readonly)
4395
    {
4396
        IM_ASSERT(Buf == obj->TextA.Data);
4397
        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
4398
        obj->TextA.resize(new_buf_size + 1);
4399
        obj->TextSrc = obj->TextA.Data;
4400
        Buf = obj->TextA.Data;
4401
        BufSize = obj->BufCapacity = new_buf_size;
4402
    }
4403
4404
    if (BufTextLen != pos)
4405
        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
4406
    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
4407
    Buf[BufTextLen + new_text_len] = '\0';
4408
4409
    BufDirty = true;
4410
    BufTextLen += new_text_len;
4411
    if (CursorPos >= pos)
4412
        CursorPos += new_text_len;
4413
    CursorPos = ImClamp(CursorPos, 0, BufTextLen);
4414
    SelectionStart = SelectionEnd = CursorPos;
4415
}
4416
4417
void ImGui::PushPasswordFont()
4418
{
4419
    ImGuiContext& g = *GImGui;
4420
    ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
4421
    IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
4422
    ImFontGlyph* glyph = g.FontBaked->FindGlyph('*');
4423
    g.InputTextPasswordFontBackupFlags = g.Font->Flags;
4424
    backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex;
4425
    backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX;
4426
    backup->IndexLookup.swap(g.FontBaked->IndexLookup);
4427
    backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX);
4428
    g.Font->Flags |= ImFontFlags_NoLoadGlyphs;
4429
    g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph);
4430
    g.FontBaked->FallbackAdvanceX = glyph->AdvanceX;
4431
}
4432
4433
void ImGui::PopPasswordFont()
4434
{
4435
    ImGuiContext& g = *GImGui;
4436
    ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
4437
    g.Font->Flags = g.InputTextPasswordFontBackupFlags;
4438
    g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex;
4439
    g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX;
4440
    g.FontBaked->IndexLookup.swap(backup->IndexLookup);
4441
    g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX);
4442
    IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
4443
}
4444
4445
// Return false to discard a character.
4446
static bool InputTextFilterCharacter(ImGuiContext* ctx, ImGuiInputTextState* state, unsigned int* p_char, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
4447
{
4448
    IM_ASSERT(state != NULL);
4449
    unsigned int c = *p_char;
4450
    ImGuiInputTextFlags flags = state->Flags;
4451
4452
    // Filter non-printable (NB: isprint is unreliable! see #2467)
4453
    bool apply_named_filters = true;
4454
    if (c < 0x20)
4455
    {
4456
        bool pass = false;
4457
        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)
4458
        if (c == '\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \n with a space
4459
        {
4460
            c = *p_char = ' ';
4461
            pass = true;
4462
        }
4463
        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;
4464
        pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
4465
        if (!pass)
4466
            return false;
4467
        apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
4468
    }
4469
4470
    if (input_source_is_clipboard == false)
4471
    {
4472
        // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
4473
        if (c == 127)
4474
            return false;
4475
4476
        // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
4477
        if (c >= 0xE000 && c <= 0xF8FF)
4478
            return false;
4479
    }
4480
4481
    // Filter Unicode ranges we are not handling in this build
4482
    if (c > IM_UNICODE_CODEPOINT_MAX)
4483
        return false;
4484
4485
    // Generic named filters
4486
    if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
4487
    {
4488
        // 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 '.'.
4489
        // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
4490
        // 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.
4491
        // Change the default decimal_point with:
4492
        //   ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;
4493
        // 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.
4494
        ImGuiContext& g = *ctx;
4495
        const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;
4496
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
4497
            if (c == '.' || c == ',')
4498
                c = c_decimal_point;
4499
4500
        // Full-width -> half-width conversion for numeric fields: https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
4501
        // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
4502
        // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
4503
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
4504
            if (c >= 0xFF01 && c <= 0xFF5E)
4505
                c = c - 0xFF01 + 0x21;
4506
4507
        // Allow 0-9 . - + * /
4508
        if (flags & ImGuiInputTextFlags_CharsDecimal)
4509
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
4510
                return false;
4511
4512
        // Allow 0-9 . - + * / e E
4513
        if (flags & ImGuiInputTextFlags_CharsScientific)
4514
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
4515
                return false;
4516
4517
        // Allow 0-9 a-F A-F
4518
        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
4519
            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
4520
                return false;
4521
4522
        // Turn a-z into A-Z
4523
        if (flags & ImGuiInputTextFlags_CharsUppercase)
4524
            if (c >= 'a' && c <= 'z')
4525
                c += (unsigned int)('A' - 'a');
4526
4527
        if (flags & ImGuiInputTextFlags_CharsNoBlank)
4528
            if (ImCharIsBlankW(c))
4529
                return false;
4530
4531
        *p_char = c;
4532
    }
4533
4534
    // Custom callback filter
4535
    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
4536
    {
4537
        ImGuiContext& g = *GImGui;
4538
        ImGuiInputTextCallbackData callback_data;
4539
        callback_data.Ctx = &g;
4540
        callback_data.ID = state->ID;
4541
        callback_data.Flags = flags;
4542
        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
4543
        callback_data.EventChar = (ImWchar)c;
4544
        callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
4545
        callback_data.CursorPos = state->Stb->cursor;
4546
        callback_data.SelectionStart = state->Stb->select_start;
4547
        callback_data.SelectionEnd = state->Stb->select_end;
4548
        callback_data.UserData = user_data;
4549
        if (callback(&callback_data) != 0)
4550
            return false;
4551
        *p_char = callback_data.EventChar;
4552
        if (!callback_data.EventChar)
4553
            return false;
4554
    }
4555
4556
    return true;
4557
}
4558
4559
// Find the shortest single replacement we can make to get from old_buf to new_buf
4560
// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately.
4561
// 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.
4562
static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length)
4563
{
4564
    const int shorter_length = ImMin(old_length, new_length);
4565
    int first_diff;
4566
    for (first_diff = 0; first_diff < shorter_length; first_diff++)
4567
        if (old_buf[first_diff] != new_buf[first_diff])
4568
            break;
4569
    if (first_diff == old_length && first_diff == new_length)
4570
        return;
4571
4572
    int old_last_diff = old_length   - 1;
4573
    int new_last_diff = new_length - 1;
4574
    for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
4575
        if (old_buf[old_last_diff] != new_buf[new_last_diff])
4576
            break;
4577
4578
    const int insert_len = new_last_diff - first_diff + 1;
4579
    const int delete_len = old_last_diff - first_diff + 1;
4580
    if (insert_len > 0 || delete_len > 0)
4581
        if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len))
4582
            for (int i = 0; i < delete_len; i++)
4583
                p[i] = old_buf[first_diff + i];
4584
}
4585
4586
// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
4587
// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
4588
// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
4589
// but that more likely be attractive when we do have _NoLiveEdit flag available.
4590
void ImGui::InputTextDeactivateHook(ImGuiID id)
4591
{
4592
    ImGuiContext& g = *GImGui;
4593
    ImGuiInputTextState* state = &g.InputTextState;
4594
    if (id == 0 || state->ID != id)
4595
        return;
4596
    //IMGUI_DEBUG_LOG_ACTIVEID("InputTextDeactivateHook() id = 0x%08X\n", id);
4597
    g.InputTextDeactivatedState.ID = state->ID;
4598
    if (state->Flags & ImGuiInputTextFlags_ReadOnly)
4599
    {
4600
        g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
4601
    }
4602
    else
4603
    {
4604
        IM_ASSERT(state->TextA.Data != 0);
4605
        IM_ASSERT(state->TextA[state->TextLen] == 0);
4606
        g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1);
4607
        memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1);
4608
    }
4609
}
4610
4611
static int* ImLowerBound(int* in_begin, int* in_end, int v)
4612
{
4613
    int* in_p = in_begin;
4614
    for (size_t count = (size_t)(in_end - in_p); count > 0; )
4615
    {
4616
        size_t count2 = count >> 1;
4617
        int* mid = in_p + count2;
4618
        if (*mid < v)
4619
        {
4620
            in_p = ++mid;
4621
            count -= count2 + 1;
4622
        }
4623
        else
4624
        {
4625
            count = count2;
4626
        }
4627
    }
4628
    return in_p;
4629
}
4630
4631
// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool?
4632
// 'max_output_buffer_size' happens to be a meaningful optimization to avoid writing the full line_index when not necessarily needed (e.g. very large buffer, scrolled up, inactive)
4633
static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, float wrap_width, int max_output_buffer_size, const char** out_buf_end)
4634
{
4635
    ImGuiContext& g = *GImGui;
4636
    int size = 0;
4637
    const char* s;
4638
    bool trailing_line_already_counted = false;
4639
    if (flags & ImGuiInputTextFlags_WordWrap)
4640
    {
4641
        for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s)
4642
        {
4643
            if (size++ <= max_output_buffer_size)
4644
                line_index->Offsets.push_back((int)(s - buf));
4645
            s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
4646
        }
4647
    }
4648
    else if (buf_end != NULL)
4649
    {
4650
        for (s = buf; s < buf_end; s = s ? s + 1 : buf_end)
4651
        {
4652
            if (size++ <= max_output_buffer_size)
4653
                line_index->Offsets.push_back((int)(s - buf));
4654
            s = (const char*)ImMemchr(s, '\n', buf_end - s);
4655
        }
4656
    }
4657
    else
4658
    {
4659
        // Inactive path: we don't know buf_end ahead of time.
4660
        const char* s_eol;
4661
        for (s = buf; ; s = s_eol + 1)
4662
        {
4663
            if (size++ <= max_output_buffer_size)
4664
                line_index->Offsets.push_back((int)(s - buf));
4665
            if ((s_eol = strchr(s, '\n')) != NULL)
4666
                continue;
4667
            s += strlen(s);
4668
            trailing_line_already_counted = true;
4669
            break;
4670
        }
4671
    }
4672
    if (out_buf_end != NULL)
4673
        *out_buf_end = buf_end = s;
4674
    if (size == 0)
4675
    {
4676
        line_index->Offsets.push_back(0);
4677
        size++;
4678
    }
4679
    if (buf_end > buf && buf_end[-1] == '\n' && !trailing_line_already_counted && size++ <= max_output_buffer_size)
4680
        line_index->Offsets.push_back((int)(buf_end - buf));
4681
    return size;
4682
}
4683
4684
static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n)
4685
{
4686
    const char* cursor_ptr = buf + cursor_n;
4687
    int* it_begin = line_index->Offsets.begin();
4688
    int* it_end = line_index->Offsets.end();
4689
    const int* it = ImLowerBound(it_begin, it_end, cursor_n);
4690
    if (it > it_begin)
4691
        if (it == it_end || *it != cursor_n || (state != NULL && state->WrapWidth > 0.0f && state->LastMoveDirectionLR == ImGuiDir_Right && cursor_ptr[-1] != '\n' && cursor_ptr[-1] != 0))
4692
            it--;
4693
4694
    const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it);
4695
    const char* line_start = line_index->get_line_begin(buf, line_no);
4696
    ImVec2 offset;
4697
    offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x;
4698
    offset.y = (line_no + 1) * g.FontSize;
4699
    return offset;
4700
}
4701
4702
// Edit a string of text
4703
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
4704
//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
4705
//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
4706
// - 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.
4707
// - If you want to use InputText() with std::string or any custom dynamic string type, use the wrapper in misc/cpp/imgui_stdlib.h/.cpp!
4708
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)
4709
{
4710
    ImGuiWindow* window = GetCurrentWindow();
4711
    if (window->SkipItems)
4712
        return false;
4713
4714
    IM_ASSERT(buf != NULL && buf_size >= 0);
4715
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
4716
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
4717
    IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline)));              // Multiline does not not work with left-trimming
4718
    IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0);         // WordWrap does not work with Password mode.
4719
    IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0);        // WordWrap does not work in single-line mode.
4720
4721
    ImGuiContext& g = *GImGui;
4722
    ImGuiIO& io = g.IO;
4723
    const ImGuiStyle& style = g.Style;
4724
4725
    const bool RENDER_SELECTION_WHEN_INACTIVE = false;
4726
    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
4727
4728
    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
4729
        BeginGroup();
4730
    const ImGuiID id = window->GetID(label);
4731
    const char* label_end = FindRenderedTextEnd(label);
4732
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
4733
    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
4734
    const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
4735
4736
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
4737
    const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
4738
4739
    ImGuiWindow* draw_window = window;
4740
    ImVec2 inner_size = frame_size;
4741
    ImGuiLastItemData item_data_backup;
4742
    if (is_multiline)
4743
    {
4744
        ImVec2 backup_pos = window->DC.CursorPos;
4745
        ItemSize(total_bb, style.FramePadding.y);
4746
        bool no_clip = (g.InputTextDeactivatedState.ID == id) || (g.ActiveId == id) || (id == g.NavActivateId); // Mimic some of ItemAdd() logic + add InputTextDeactivatedState.ID check.
4747
        if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable) && !no_clip)
4748
        {
4749
            EndGroup();
4750
            return false;
4751
        }
4752
        item_data_backup = g.LastItemData;
4753
        window->DC.CursorPos = backup_pos;
4754
4755
        // Prevent NavActivation from explicit Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
4756
        if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && !(g.NavActivateFlags & ImGuiActivateFlags_FromFocusApi) && (flags & ImGuiInputTextFlags_AllowTabInput))
4757
            g.NavActivateId = 0;
4758
4759
        // Prevent NavActivate reactivating in BeginChild() when we are already active.
4760
        const ImGuiID backup_activate_id = g.NavActivateId;
4761
        if (g.ActiveId == id) // Prevent reactivation
4762
            g.NavActivateId = 0;
4763
4764
        // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
4765
        PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
4766
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
4767
        PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
4768
        PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
4769
        bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);
4770
        g.NavActivateId = backup_activate_id;
4771
        PopStyleVar(3);
4772
        PopStyleColor();
4773
        if (!child_visible && !no_clip)
4774
        {
4775
            EndChild();
4776
            EndGroup();
4777
            return false;
4778
        }
4779
        draw_window = g.CurrentWindow; // Child window
4780
        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.
4781
        draw_window->DC.CursorPos += style.FramePadding;
4782
        inner_size.x -= draw_window->ScrollbarSizes.x;
4783
4784
        // FIXME: Could this be a ImGuiChildFlags to affect the SetLastItemDataForWindow() call?
4785
        g.LastItemData.ID = id;
4786
        g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
4787
        g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
4788
    }
4789
    else
4790
    {
4791
        // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
4792
        ItemSize(total_bb, style.FramePadding.y);
4793
        if (!(flags & ImGuiInputTextFlags_TempInput))
4794
            if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
4795
                return false;
4796
    }
4797
4798
    // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)
4799
    bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);
4800
    if (hovered)
4801
        SetMouseCursor(ImGuiMouseCursor_TextInput);
4802
    if (hovered && g.NavHighlightItemUnderNav)
4803
        hovered = false;
4804
4805
    // We are only allowed to access the state if we are already the active widget.
4806
    ImGuiInputTextState* state = GetInputTextState(id);
4807
4808
    if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)
4809
        flags |= ImGuiInputTextFlags_ReadOnly;
4810
    const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
4811
    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
4812
    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
4813
    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
4814
    if (is_resizable)
4815
        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
4816
4817
    // Word-wrapping: enforcing a fixed width not altered by vertical scrollbar makes things easier, notably to track cursor reliably and avoid one-frame glitches.
4818
    // Instead of using ImGuiWindowFlags_AlwaysVerticalScrollbar we account for that space if the scrollbar is not visible.
4819
    const bool is_wordwrap = (flags & ImGuiInputTextFlags_WordWrap) != 0;
4820
    float wrap_width = 0.0f;
4821
    if (is_wordwrap)
4822
        wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize));
4823
4824
    const bool user_clicked = hovered && io.MouseClicked[0];
4825
    const bool input_requested_by_nav = (g.ActiveId != id) && (g.NavActivateId == id);
4826
    const bool input_requested_by_reactivate = (g.InputTextReactivateId == id); // for io.ConfigInputTextEnterKeepActive
4827
    const bool input_requested_by_user = (user_clicked) || (g.ActiveId == 0 && (flags & ImGuiInputTextFlags_TempInput));
4828
    const ImGuiID scrollbar_id = (is_multiline && state != NULL) ? GetWindowScrollbarID(draw_window, ImGuiAxis_Y) : 0;
4829
    const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == scrollbar_id;
4830
    const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == scrollbar_id;
4831
    bool clear_active_id = false;
4832
    bool select_all = false;
4833
4834
    float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
4835
4836
    const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
4837
    const bool init_changed_specs_multiline = (state != NULL && (state->Stb->single_line != !is_multiline)); // state != NULL means its our state.
4838
    const bool init_changed_specs_readonly = (state != NULL && ((state->Flags ^ flags) & ImGuiInputTextFlags_ReadOnly)); // state != NULL means its our state.
4839
    const bool init_make_active = (input_requested_by_user || input_requested_by_nav || input_requested_by_reactivate || user_scroll_finish);
4840
    if (init_reload_from_user_buf)
4841
    {
4842
        int new_len = (int)ImStrlen(buf);
4843
        IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
4844
        state->WantReloadUserBuf = false;
4845
        InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);
4846
        state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
4847
        state->TextLen = new_len;
4848
        memcpy(state->TextA.Data, buf, state->TextLen + 1);
4849
        state->Stb->select_start = state->ReloadSelectionStart;
4850
        state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd; // will be clamped to bounds below
4851
    }
4852
    else if ((init_make_active && g.ActiveId != id) || init_changed_specs_multiline || init_changed_specs_readonly)
4853
    {
4854
        // Access state even if we don't own it yet.
4855
        state = &g.InputTextState;
4856
        state->CursorAnimReset();
4857
4858
        // 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)
4859
        InputTextDeactivateHook(state->ID);
4860
4861
        // Take a copy of the initial buffer value.
4862
        // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
4863
        const int buf_len = (int)ImStrlen(buf);
4864
        IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?");
4865
        if (!user_scroll_finish)
4866
        {
4867
            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.
4868
            memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
4869
        }
4870
4871
        // Preserve cursor position and undo/redo stack if we come back to same widget
4872
        // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
4873
        bool recycle_state = (state->ID == id && !init_changed_specs_multiline);
4874
        if (recycle_state && !init_changed_specs_readonly && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
4875
            recycle_state = false;
4876
4877
        // Start edition
4878
        state->ID = id;
4879
        state->TextLen = buf_len;
4880
        state->EditedBefore = false;
4881
        if (!is_readonly)
4882
        {
4883
            state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
4884
            memcpy(state->TextA.Data, buf, state->TextLen + 1);
4885
        }
4886
4887
        // Find initial scroll position for right alignment
4888
        state->Scroll = ImVec2(0.0f, 0.0f);
4889
        if (flags & ImGuiInputTextFlags_ElideLeft)
4890
            state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f);
4891
4892
        // Recycle existing cursor/selection/undo stack but clamp position
4893
        // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
4894
        if (!recycle_state)
4895
            stb_textedit_initialize_state(state->Stb, !is_multiline);
4896
4897
        if (!is_multiline)
4898
        {
4899
            if (flags & ImGuiInputTextFlags_AutoSelectAll)
4900
                select_all = true;
4901
            if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
4902
                select_all = true;
4903
            if (user_clicked && io.KeyCtrl)
4904
                select_all = true;
4905
        }
4906
4907
        if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
4908
            state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)
4909
    }
4910
4911
    const bool is_osx = io.ConfigMacOSXBehaviors;
4912
    if (init_make_active && g.ActiveId != id)
4913
    {
4914
        IM_ASSERT(state && state->ID == id);
4915
        SetActiveID(id, window);
4916
        SetFocusID(id, window);
4917
        FocusWindow(window);
4918
        if (input_requested_by_nav)
4919
            SetNavCursorVisibleAfterMove();
4920
    }
4921
    if (g.ActiveId == id)
4922
    {
4923
        // Declare some inputs, the other are registered and polled via Shortcut() routing system.
4924
        // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combination into individual shortcuts.
4925
        const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };
4926
        for (ImGuiKey key : always_owned_keys)
4927
            SetKeyOwner(key, id);
4928
        if (user_clicked)
4929
            SetKeyOwner(ImGuiKey_MouseLeft, id);
4930
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
4931
        if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
4932
        {
4933
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
4934
            SetKeyOwner(ImGuiKey_UpArrow, id);
4935
            SetKeyOwner(ImGuiKey_DownArrow, id);
4936
        }
4937
        if (is_multiline)
4938
        {
4939
            SetKeyOwner(ImGuiKey_PageUp, id);
4940
            SetKeyOwner(ImGuiKey_PageDown, id);
4941
        }
4942
        // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu
4943
        if (is_osx)
4944
            SetKeyOwner(ImGuiMod_Alt, id);
4945
4946
        // Expose scroll in a manner that is agnostic to us using a child window
4947
        if (is_multiline && state != NULL)
4948
            state->Scroll.y = draw_window->Scroll.y;
4949
4950
        // Read-only mode always ever read from source buffer. Refresh TextLen when active.
4951
        if (is_readonly && state != NULL)
4952
            state->TextLen = (int)ImStrlen(buf);
4953
        if (state != NULL)
4954
            state->CursorClamp();
4955
        //if (is_readonly && state != NULL)
4956
        //    state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
4957
    }
4958
    if (state != NULL)
4959
        state->TextSrc = is_readonly ? buf : state->TextA.Data;
4960
4961
    // 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)
4962
    if (g.ActiveId == id && state == NULL)
4963
        ClearActiveID();
4964
4965
    // Release focus when we click outside
4966
    if (g.ActiveId == id && io.MouseClicked[0] && !init_make_active) //-V560
4967
        clear_active_id = true;
4968
4969
    // Lock the decision of whether we are going to take the path displaying the cursor or selection
4970
    bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
4971
    bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
4972
    bool value_changed = false;
4973
    bool validated = false;
4974
4975
    // Select the buffer to render.
4976
    const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
4977
    bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4978
4979
    // Password pushes a temporary font with only a fallback glyph
4980
    if (is_password && !is_displaying_hint)
4981
        PushPasswordFont();
4982
4983
    if (state != NULL && state->ID == id)
4984
    {
4985
        state->Flags = flags;
4986
4987
        // Word-wrapping: attempt to keep cursor in view while resizing frame/parent (FIXME-WORDWRAP: would be better to preserve same relative offset)
4988
        if (is_wordwrap && state->WrapWidth != wrap_width)
4989
        {
4990
            state->CursorCenterY = true;
4991
            state->WrapWidth = wrap_width;
4992
            render_cursor = true;
4993
        }
4994
    }
4995
4996
    // Process mouse inputs and character inputs
4997
    if (g.ActiveId == id)
4998
    {
4999
        IM_ASSERT(state != NULL);
5000
        state->EditedThisFrame = false;
5001
        state->BufCapacity = buf_size;
5002
        state->WrapWidth = wrap_width;
5003
5004
        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
5005
        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
5006
        g.ActiveIdAllowOverlap = !io.MouseDown[0];
5007
5008
        // Edit in progress
5009
        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x;
5010
        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
5011
5012
        if (select_all)
5013
        {
5014
            state->SelectAll();
5015
            state->SelectedAllMouseLock = true;
5016
        }
5017
        else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
5018
        {
5019
            stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
5020
            const int multiclick_count = (io.MouseClickedCount[0] - 2);
5021
            if ((multiclick_count % 2) == 0)
5022
            {
5023
                // Double-click: Select word
5024
                // We always use the "Mac" word advance for double-click select vs Ctrl+Right which use the platform dependent variant:
5025
                // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
5026
                const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n';
5027
                if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)
5028
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
5029
                //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
5030
                if (!STB_TEXT_HAS_SELECTION(state->Stb))
5031
                    ImStb::stb_textedit_prep_selection_at_cursor(state->Stb);
5032
                state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);
5033
                state->Stb->select_end = state->Stb->cursor;
5034
                ImStb::stb_textedit_clamp(state, state->Stb);
5035
            }
5036
            else
5037
            {
5038
                // Triple-click: Select line
5039
                const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n';
5040
                state->WrapWidth = 0.0f; // Temporarily disable wrapping so we use real line start.
5041
                state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
5042
                state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
5043
                state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
5044
                state->WrapWidth = wrap_width;
5045
                if (!is_eol && is_multiline)
5046
                {
5047
                    ImSwap(state->Stb->select_start, state->Stb->select_end);
5048
                    state->Stb->cursor = state->Stb->select_end;
5049
                }
5050
                state->CursorFollow = false;
5051
            }
5052
            state->CursorAnimReset();
5053
        }
5054
        else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
5055
        {
5056
            if (hovered)
5057
            {
5058
                if (io.KeyShift)
5059
                    stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
5060
                else
5061
                    stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
5062
                state->CursorAnimReset();
5063
            }
5064
        }
5065
        else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
5066
        {
5067
            stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
5068
            state->CursorAnimReset();
5069
            state->CursorFollow = true;
5070
        }
5071
        if (state->SelectedAllMouseLock && !io.MouseDown[0])
5072
            state->SelectedAllMouseLock = false;
5073
5074
        // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
5075
        // (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)
5076
        if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
5077
        {
5078
            if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
5079
            {
5080
                unsigned int c = '\t'; // Insert TAB
5081
                if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
5082
                    state->OnCharPressed(c);
5083
            }
5084
            // FIXME: Implement Shift+Tab
5085
            /*
5086
            if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))
5087
            {
5088
            }
5089
            */
5090
        }
5091
5092
        // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
5093
        // 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.
5094
        const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
5095
        if (io.InputQueueCharacters.Size > 0)
5096
        {
5097
            if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
5098
                for (int n = 0; n < io.InputQueueCharacters.Size; n++)
5099
                {
5100
                    // Insert character if they pass filtering
5101
                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];
5102
                    if (c == '\t') // Skip Tab, see above.
5103
                        continue;
5104
                    if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
5105
                        state->OnCharPressed(c);
5106
                }
5107
5108
            // Consume characters
5109
            io.InputQueueCharacters.resize(0);
5110
        }
5111
    }
5112
5113
    // Process other shortcuts/key-presses
5114
    bool revert_edit = false;
5115
    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
5116
    {
5117
        IM_ASSERT(state != NULL);
5118
5119
        const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
5120
        state->Stb->row_count_per_page = row_count_per_page;
5121
5122
        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
5123
        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
5124
        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
5125
5126
        // 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: former would be handled by InputText)
5127
        // Otherwise we could simply assume that we own the keys as we are active.
5128
        const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
5129
        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());
5130
        const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());
5131
        const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
5132
        const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
5133
        const bool is_redo =  (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Y, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
5134
        const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
5135
5136
        // 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.
5137
        const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
5138
        const bool is_enter = Shortcut(ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiKey_KeypadEnter, f_repeat, id);
5139
        const bool is_ctrl_enter = Shortcut(ImGuiMod_Ctrl | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Ctrl | ImGuiKey_KeypadEnter, f_repeat, id);
5140
        const bool is_shift_enter = Shortcut(ImGuiMod_Shift | ImGuiKey_Enter, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_KeypadEnter, f_repeat, id);
5141
        const bool is_gamepad_validate = nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false);
5142
        const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));
5143
5144
        // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.
5145
        // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line.
5146
        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); }
5147
        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); }
5148
        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); }
5149
        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); }
5150
        else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
5151
        else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
5152
        else if (IsKeyPressed(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
5153
        else if (IsKeyPressed(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
5154
        else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
5155
        {
5156
            if (!state->HasSelection())
5157
            {
5158
                // 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)
5159
                if (is_wordmove_key_down)
5160
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
5161
            }
5162
            state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
5163
        }
5164
        else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
5165
        {
5166
            if (!state->HasSelection())
5167
            {
5168
                if (is_wordmove_key_down)
5169
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
5170
                else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
5171
                    state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
5172
            }
5173
            state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
5174
        }
5175
        else if (is_enter || is_ctrl_enter || is_shift_enter || is_gamepad_validate)
5176
        {
5177
            // Determine if we turn Enter into a \n character
5178
            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
5179
            bool is_new_line = is_multiline && !is_gamepad_validate && (is_shift_enter || (is_enter && !ctrl_enter_for_new_line) || (is_ctrl_enter && ctrl_enter_for_new_line));
5180
            if (!is_new_line)
5181
            {
5182
                validated = clear_active_id = true;
5183
                if (io.ConfigInputTextEnterKeepActive && !is_multiline)
5184
                {
5185
                    // Queue reactivation, so that e.g. IsItemDeactivatedAfterEdit() will work. (#9001)
5186
                    state->SelectAll(); // No need to scroll
5187
                    g.InputTextReactivateId = id; // Mark for reactivation on next frame
5188
                }
5189
            }
5190
            else if (!is_readonly)
5191
            {
5192
                // Insert new line
5193
                unsigned int c = '\n';
5194
                if (InputTextFilterCharacter(&g, state, &c, callback, callback_user_data))
5195
                    state->OnCharPressed(c);
5196
            }
5197
        }
5198
        else if (is_cancel)
5199
        {
5200
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
5201
            {
5202
                if (state->TextA.Data[0] != 0)
5203
                {
5204
                    revert_edit = true;
5205
                }
5206
                else
5207
                {
5208
                    render_cursor = render_selection = false;
5209
                    clear_active_id = true;
5210
                }
5211
            }
5212
            else
5213
            {
5214
                clear_active_id = revert_edit = true;
5215
                render_cursor = render_selection = false;
5216
            }
5217
        }
5218
        else if (is_undo || is_redo)
5219
        {
5220
            state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
5221
            state->ClearSelection();
5222
        }
5223
        else if (is_select_all)
5224
        {
5225
            state->SelectAll();
5226
            state->CursorFollow = true;
5227
        }
5228
        else if (is_cut || is_copy)
5229
        {
5230
            // Cut, Copy
5231
            if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
5232
            {
5233
                // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy.
5234
                const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
5235
                const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;
5236
                g.TempBuffer.reserve(ie - ib + 1);
5237
                memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib);
5238
                g.TempBuffer.Data[ie - ib] = 0;
5239
                SetClipboardText(g.TempBuffer.Data);
5240
            }
5241
            if (is_cut)
5242
            {
5243
                if (!state->HasSelection())
5244
                    state->SelectAll();
5245
                state->CursorFollow = true;
5246
                stb_textedit_cut(state, state->Stb);
5247
            }
5248
        }
5249
        else if (is_paste)
5250
        {
5251
            if (const char* clipboard = GetClipboardText())
5252
            {
5253
                // Filter pasted buffer
5254
                const int clipboard_len = (int)ImStrlen(clipboard);
5255
                const char* clipboard_end = clipboard + clipboard_len;
5256
                ImVector<char> clipboard_filtered;
5257
                clipboard_filtered.reserve(clipboard_len + 1);
5258
                for (const char* s = clipboard; *s != 0; )
5259
                {
5260
                    unsigned int c;
5261
                    int in_len = ImTextCharFromUtf8(&c, s, clipboard_end);
5262
                    s += in_len;
5263
                    if (!InputTextFilterCharacter(&g, state, &c, callback, callback_user_data, true))
5264
                        continue;
5265
                    char c_utf8[5];
5266
                    ImTextCharToUtf8(c_utf8, c);
5267
                    int out_len = (int)ImStrlen(c_utf8);
5268
                    clipboard_filtered.resize(clipboard_filtered.Size + out_len);
5269
                    memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len);
5270
                }
5271
                if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation
5272
                {
5273
                    clipboard_filtered.push_back(0);
5274
                    stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1);
5275
                    state->CursorFollow = true;
5276
                }
5277
            }
5278
        }
5279
5280
        // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
5281
        render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
5282
    }
5283
5284
    // Process revert and user callbacks
5285
    const char* apply_new_text = NULL;
5286
    int apply_new_text_length = 0;
5287
    if (g.ActiveId == id)
5288
    {
5289
        IM_ASSERT(state != NULL);
5290
        if (revert_edit && !is_readonly)
5291
        {
5292
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
5293
            {
5294
                // Clear input
5295
                IM_ASSERT(state->TextA.Data[0] != 0);
5296
                apply_new_text = "";
5297
                apply_new_text_length = 0;
5298
                value_changed = true;
5299
                char empty_string = 0;
5300
                stb_textedit_replace(state, state->Stb, &empty_string, 0);
5301
            }
5302
            else if (strcmp(state->TextA.Data, state->TextToRevertTo.Data) != 0)
5303
            {
5304
                apply_new_text = state->TextToRevertTo.Data;
5305
                apply_new_text_length = state->TextToRevertTo.Size - 1;
5306
5307
                // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
5308
                // Push records into the undo stack so we can Ctrl+Z the revert operation itself
5309
                value_changed = true;
5310
                stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1);
5311
            }
5312
        }
5313
5314
        // User callback
5315
        if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
5316
        {
5317
            IM_ASSERT(callback != NULL);
5318
5319
            // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
5320
            ImGuiInputTextFlags event_flag = 0;
5321
            ImGuiKey event_key = ImGuiKey_None;
5322
            if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
5323
            {
5324
                event_flag = ImGuiInputTextFlags_CallbackCompletion;
5325
                event_key = ImGuiKey_Tab;
5326
            }
5327
            else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
5328
            {
5329
                event_flag = ImGuiInputTextFlags_CallbackHistory;
5330
                event_key = ImGuiKey_UpArrow;
5331
            }
5332
            else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
5333
            {
5334
                event_flag = ImGuiInputTextFlags_CallbackHistory;
5335
                event_key = ImGuiKey_DownArrow;
5336
            }
5337
            else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->EditedThisFrame)
5338
            {
5339
                event_flag = ImGuiInputTextFlags_CallbackEdit;
5340
            }
5341
            else if (flags & ImGuiInputTextFlags_CallbackAlways)
5342
            {
5343
                event_flag = ImGuiInputTextFlags_CallbackAlways;
5344
            }
5345
5346
            if (event_flag)
5347
            {
5348
                ImGuiInputTextCallbackData callback_data;
5349
                callback_data.Ctx = &g;
5350
                callback_data.ID = id;
5351
                callback_data.Flags = flags;
5352
                callback_data.EventFlag = event_flag;
5353
                callback_data.EventActivated = (g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
5354
                callback_data.UserData = callback_user_data;
5355
5356
                // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
5357
                char* callback_buf = is_readonly ? buf : state->TextA.Data;
5358
                IM_ASSERT(callback_buf == state->TextSrc);
5359
                state->CallbackTextBackup.resize(state->TextLen + 1);
5360
                memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
5361
5362
                callback_data.EventKey = event_key;
5363
                callback_data.Buf = callback_buf;
5364
                callback_data.BufTextLen = state->TextLen;
5365
                callback_data.BufSize = state->BufCapacity;
5366
                callback_data.BufDirty = false;
5367
                callback_data.CursorPos = state->Stb->cursor;
5368
                callback_data.SelectionStart = state->Stb->select_start;
5369
                callback_data.SelectionEnd = state->Stb->select_end;
5370
5371
                // Call user code
5372
                callback(&callback_data);
5373
5374
                // Read back what user may have modified
5375
                callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
5376
                IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields
5377
                IM_ASSERT(callback_data.BufSize == state->BufCapacity);
5378
                IM_ASSERT(callback_data.Flags == flags);
5379
                if (callback_data.BufDirty || callback_data.CursorPos != state->Stb->cursor)
5380
                    state->CursorFollow = true;
5381
                state->Stb->cursor = ImClamp(callback_data.CursorPos, 0, callback_data.BufTextLen);
5382
                state->Stb->select_start = ImClamp(callback_data.SelectionStart, 0, callback_data.BufTextLen);
5383
                state->Stb->select_end = ImClamp(callback_data.SelectionEnd, 0, callback_data.BufTextLen);
5384
                if (callback_data.BufDirty)
5385
                {
5386
                    // Callback may update buffer and thus set buf_dirty even in read-only mode.
5387
                    IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
5388
                    InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
5389
                    state->TextLen = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
5390
                    state->CursorAnimReset();
5391
                }
5392
            }
5393
        }
5394
5395
        // Will copy result string if modified.
5396
        // FIXME-OPT: Could mark dirty state from the stb_textedit callbacks
5397
        if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
5398
        {
5399
            apply_new_text = state->TextSrc;
5400
            apply_new_text_length = state->TextLen;
5401
            value_changed = true;
5402
        }
5403
    }
5404
5405
    // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
5406
    // This is used when e.g. losing focus or tabbing out into another InputText() which may already be using the temp buffer.
5407
    if (g.InputTextDeactivatedState.ID == id)
5408
    {
5409
        if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
5410
        {
5411
            apply_new_text = g.InputTextDeactivatedState.TextA.Data;
5412
            apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
5413
            value_changed = true;
5414
            //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
5415
        }
5416
        g.InputTextDeactivatedState.ID = 0;
5417
    }
5418
5419
    // Write back result to user buffer. This can currently only happen when (g.ActiveId == id) or when just deactivated.
5420
    // - As soon as the InputText() is active, our stored in-widget value gets priority over any underlying modification of the user buffer.
5421
    // - Make sure we always reapply the live buffer back to the input/user buffer before clearing ActiveId, even thought strictly speaking
5422
    //   it was not modified on this frame. This allows the user to use InputText() without maintaining any user-side storage.
5423
    //   (PS: if you use this property together with ImGuiInputTextFlags_CallbackResize, you are at the risk of recreating a temporary
5424
    //    allocated/string object every frame. Which in the grand scheme of scheme is nothing, but isn't dear imgui vibe).
5425
    if (apply_new_text != NULL)
5426
    {
5427
        IM_ASSERT(apply_new_text_length >= 0);
5428
        if (is_resizable)
5429
        {
5430
            ImGuiInputTextCallbackData callback_data;
5431
            callback_data.Ctx = &g;
5432
            callback_data.ID = id;
5433
            callback_data.Flags = flags;
5434
            callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
5435
            callback_data.EventActivated = (state != NULL && g.ActiveId == state->ID && g.ActiveIdIsJustActivated);
5436
            callback_data.Buf = buf;
5437
            callback_data.BufTextLen = apply_new_text_length;
5438
            callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
5439
            callback_data.UserData = callback_user_data;
5440
            callback(&callback_data);
5441
            buf = callback_data.Buf;
5442
            buf_size = callback_data.BufSize;
5443
            apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
5444
            IM_ASSERT(apply_new_text_length <= buf_size);
5445
        }
5446
        //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
5447
5448
        // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
5449
        ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
5450
    }
5451
5452
    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
5453
    // Otherwise request text input ahead for next frame.
5454
    if (g.ActiveId == id && clear_active_id)
5455
        ClearActiveID();
5456
5457
    // Render frame
5458
    if (!is_multiline)
5459
    {
5460
        RenderNavCursor(frame_bb, id);
5461
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
5462
    }
5463
5464
    ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
5465
    ImRect 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
5466
    if (is_multiline)
5467
        clip_rect.ClipWith(draw_window->ClipRect);
5468
5469
    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
5470
    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
5471
    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
5472
    const int buf_display_max_length = 2 * 1024 * 1024;
5473
    const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
5474
    const char* buf_display_end = NULL; // We have specialized paths below for setting the length
5475
5476
    // Display hint when contents is empty
5477
    // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368)
5478
    const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
5479
    if (new_is_displaying_hint != is_displaying_hint)
5480
    {
5481
        if (is_password && !is_displaying_hint)
5482
            PopPasswordFont();
5483
        is_displaying_hint = new_is_displaying_hint;
5484
        if (is_password && !is_displaying_hint)
5485
            PushPasswordFont();
5486
    }
5487
    if (is_displaying_hint)
5488
    {
5489
        buf_display = hint;
5490
        buf_display_end = hint + ImStrlen(hint);
5491
    }
5492
    else
5493
    {
5494
        if (render_cursor || render_selection || g.ActiveId == id)
5495
            buf_display_end = buf_display + state->TextLen; //-V595
5496
        else if (is_multiline && !is_wordwrap)
5497
            buf_display_end = NULL; // Inactive multi-line: end of buffer will be output by InputTextLineIndexBuild() special strchr() path.
5498
        else
5499
            buf_display_end = buf_display + ImStrlen(buf_display);
5500
    }
5501
5502
    // Calculate visibility
5503
    int line_visible_n0 = 0, line_visible_n1 = 1;
5504
    if (is_multiline)
5505
        CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
5506
5507
    // Build line index for easy data access (makes code below simpler and faster)
5508
    ImGuiTextIndex* line_index = &g.InputTextLineIndex;
5509
    line_index->Offsets.resize(0);
5510
    int line_count = 1;
5511
    if (is_multiline)
5512
    {
5513
        // If scrolling is expected to change build full index.
5514
        // FIXME-OPT: Could append to index when new value of line_visible_n1 becomes bigger, see second call to CalcClipRectVisibleItemsY() below.
5515
        bool will_scroll_y = state && ((state->CursorFollow && render_cursor) || (state->CursorCenterY && (render_cursor || render_selection)));
5516
        line_count = InputTextLineIndexBuild(flags, line_index, buf_display, buf_display_end, wrap_width, will_scroll_y ? INT_MAX : line_visible_n1 + 1, buf_display_end ? NULL : &buf_display_end);
5517
    }
5518
    line_index->EndOffset = (int)(buf_display_end - buf_display);
5519
    line_visible_n1 = ImMin(line_visible_n1, line_count);
5520
5521
    // Store text height (we don't need width)
5522
    float text_size_y = line_count * g.FontSize;
5523
    //GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255));
5524
5525
    // Calculate blinking cursor position
5526
    const ImVec2 cursor_offset = render_cursor && state ? InputTextLineIndexGetPosOffset(g, state, line_index, buf_display, buf_display_end, state->Stb->cursor) : ImVec2(0.0f, 0.0f);
5527
    ImVec2 draw_scroll;
5528
5529
    // Render text. We currently only render selection when the widget is active or while scrolling.
5530
    const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
5531
    if (render_cursor || render_selection)
5532
    {
5533
        // Render text (with cursor and selection)
5534
        // This is going to be messy. We need to:
5535
        // - Display the text (this alone can be more easily clipped)
5536
        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
5537
        // - Measure text height (for scrollbar)
5538
        // 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)
5539
        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
5540
        IM_ASSERT(state != NULL);
5541
        state->LineCount = line_count;
5542
5543
        // Scroll
5544
        float new_scroll_y = scroll_y;
5545
        if (render_cursor && state->CursorFollow)
5546
        {
5547
            // Horizontal scroll in chunks of quarter width
5548
            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
5549
            {
5550
                const float scroll_increment_x = inner_size.x * 0.25f;
5551
                const float visible_width = inner_size.x - style.FramePadding.x;
5552
                if (cursor_offset.x < state->Scroll.x)
5553
                    state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
5554
                else if (cursor_offset.x - visible_width >= state->Scroll.x)
5555
                    state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
5556
            }
5557
            else
5558
            {
5559
                state->Scroll.x = 0.0f;
5560
            }
5561
5562
            // Vertical scroll
5563
            if (is_multiline)
5564
            {
5565
                // Test if cursor is vertically visible
5566
                if (cursor_offset.y - g.FontSize < scroll_y)
5567
                    new_scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
5568
                else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
5569
                    new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
5570
            }
5571
            state->CursorFollow = false;
5572
        }
5573
        if (state->CursorCenterY)
5574
        {
5575
            if (is_multiline)
5576
                new_scroll_y = cursor_offset.y - g.FontSize - (inner_size.y * 0.5f - style.FramePadding.y);
5577
            state->CursorCenterY = false;
5578
            render_cursor = false;
5579
        }
5580
        if (new_scroll_y != scroll_y)
5581
        {
5582
            const float scroll_max_y = ImMax((text_size_y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
5583
            scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y);
5584
            draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
5585
            draw_window->Scroll.y = scroll_y;
5586
            CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
5587
            line_visible_n1 = ImMin(line_visible_n1, line_count);
5588
        }
5589
5590
        // Draw selection
5591
        draw_scroll.x = state->Scroll.x;
5592
        if (render_selection)
5593
        {
5594
            const 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.
5595
            const float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME-DPI: those offsets should be part of the style? they don't play so well with multi-line selection.
5596
            const float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
5597
            const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
5598
5599
            const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end);
5600
            const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end);
5601
            for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++)
5602
            {
5603
                const char* p = line_index->get_line_begin(buf_display, line_n);
5604
                const char* p_eol = line_index->get_line_end(buf_display, line_n);
5605
                const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n');
5606
                if (p_eol_is_wrap)
5607
                    p_eol++;
5608
                const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
5609
                const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol;
5610
5611
                float rect_width = 0.0f;
5612
                if (line_selected_begin < line_selected_end)
5613
                    rect_width += CalcTextSize(line_selected_begin, line_selected_end).x;
5614
                if (text_selected_begin <= p_eol && text_selected_end > p_eol && !p_eol_is_wrap)
5615
                    rect_width += bg_eol_width; // So we can see selected empty lines
5616
                if (rect_width == 0.0f)
5617
                    continue;
5618
5619
                ImRect rect;
5620
                rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x;
5621
                rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize;
5622
                rect.Max.x = rect.Min.x + rect_width;
5623
                rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize;
5624
                rect.Min.y += bg_offy_up;
5625
                rect.ClipWith(clip_rect);
5626
                draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
5627
            }
5628
        }
5629
    }
5630
5631
    // Find render position for right alignment (single-line only)
5632
    if (g.ActiveId != id && (flags & ImGuiInputTextFlags_ElideLeft) && !render_cursor && !render_selection)
5633
        draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x);
5634
    //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive?
5635
5636
    // Render text
5637
    if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1))
5638
        g.Font->RenderText(draw_window->DrawList, g.FontSize,
5639
            draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize),
5640
            text_col, clip_rect.AsVec4(),
5641
            line_index->get_line_begin(buf_display, line_visible_n0),
5642
            line_index->get_line_end(buf_display, line_visible_n1 - 1),
5643
            wrap_width, ImDrawTextFlags_WrapKeepBlanks | ImDrawTextFlags_CpuFineClip);
5644
5645
    // Render blinking cursor
5646
    if (render_cursor)
5647
    {
5648
        state->CursorAnim += io.DeltaTime;
5649
        bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
5650
        ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
5651
        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);
5652
        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
5653
            draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize);
5654
5655
        // 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.)
5656
        // This is required for some backends (SDL3) to start emitting character/text inputs.
5657
        // As per #6341, make sure we don't set that on the deactivating frame.
5658
        if (!is_readonly && g.ActiveId == id)
5659
        {
5660
            ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
5661
            ime_data->WantVisible = true;
5662
            ime_data->WantTextInput = true;
5663
            ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
5664
            ime_data->InputLineHeight = g.FontSize;
5665
            ime_data->ViewportId = window->Viewport->ID;
5666
        }
5667
    }
5668
5669
    if (is_password && !is_displaying_hint)
5670
        PopPasswordFont();
5671
5672
    if (is_multiline)
5673
    {
5674
        // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
5675
        Dummy(ImVec2(0.0f, text_size_y + style.FramePadding.y));
5676
        g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
5677
        EndChild();
5678
        item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
5679
5680
        // ...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...
5681
        // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
5682
        EndGroup();
5683
        if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y))
5684
        {
5685
            g.LastItemData.ID = id;
5686
            g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
5687
            g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
5688
        }
5689
    }
5690
    if (state && is_readonly)
5691
        state->TextSrc = NULL;
5692
5693
    // Log as text
5694
    if (g.LogEnabled && (!is_password || is_displaying_hint))
5695
    {
5696
        LogSetNextTextDecoration("{", "}");
5697
        LogRenderedText(&draw_pos, buf_display, buf_display_end);
5698
    }
5699
5700
    if (label_size.x > 0)
5701
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
5702
5703
    if (value_changed)
5704
        MarkItemEdited(id);
5705
5706
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
5707
    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
5708
        return validated;
5709
    else
5710
        return value_changed;
5711
}
5712
5713
void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
5714
{
5715
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
5716
    ImGuiContext& g = *GImGui;
5717
    ImStb::STB_TexteditState* stb_state = state->Stb;
5718
    ImStb::StbUndoState* undo_state = &stb_state->undostate;
5719
    Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
5720
    DebugLocateItemOnHover(state->ID);
5721
    Text("TextLen: %d, Cursor: %d%s, Selection: %d..%d", state->TextLen, stb_state->cursor,
5722
        (state->Flags & ImGuiInputTextFlags_WordWrap) ? (state->LastMoveDirectionLR == ImGuiDir_Left ? " (L)" : " (R)") : "",
5723
        stb_state->select_start, stb_state->select_end);
5724
    Text("BufCapacity: %d, LineCount: %d", state->BufCapacity, state->LineCount);
5725
    Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity);
5726
    Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
5727
    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);
5728
    if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state
5729
    {
5730
        PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
5731
        for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
5732
        {
5733
            ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
5734
            const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
5735
            if (undo_rec_type == ' ')
5736
                BeginDisabled();
5737
            const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0;
5738
            const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage;
5739
            Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"",
5740
                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);
5741
            if (undo_rec_type == ' ')
5742
                EndDisabled();
5743
        }
5744
        PopStyleVar();
5745
    }
5746
    EndChild();
5747
#else
5748
    IM_UNUSED(state);
5749
#endif
5750
}
5751
5752
//-------------------------------------------------------------------------
5753
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
5754
//-------------------------------------------------------------------------
5755
// - ColorEdit3()
5756
// - ColorEdit4()
5757
// - ColorPicker3()
5758
// - RenderColorRectWithAlphaCheckerboard() [Internal]
5759
// - ColorPicker4()
5760
// - ColorButton()
5761
// - SetColorEditOptions()
5762
// - ColorTooltip() [Internal]
5763
// - ColorEditOptionsPopup() [Internal]
5764
// - ColorPickerOptionsPopup() [Internal]
5765
//-------------------------------------------------------------------------
5766
5767
bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
5768
{
5769
    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
5770
}
5771
5772
static void ColorEditRestoreH(const float* col, float* H)
5773
{
5774
    ImGuiContext& g = *GImGui;
5775
    IM_ASSERT(g.ColorEditCurrentID != 0);
5776
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5777
        return;
5778
    *H = g.ColorEditSavedHue;
5779
}
5780
5781
// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
5782
// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.
5783
static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
5784
{
5785
    ImGuiContext& g = *GImGui;
5786
    IM_ASSERT(g.ColorEditCurrentID != 0);
5787
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
5788
        return;
5789
5790
    // When S == 0, H is undefined.
5791
    // When H == 1 it wraps around to 0.
5792
    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
5793
        *H = g.ColorEditSavedHue;
5794
5795
    // When V == 0, S is undefined.
5796
    if (*V == 0.0f)
5797
        *S = g.ColorEditSavedSat;
5798
}
5799
5800
// Edit colors components (each component in 0.0f..1.0f range).
5801
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
5802
// 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.
5803
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
5804
{
5805
    ImGuiWindow* window = GetCurrentWindow();
5806
    if (window->SkipItems)
5807
        return false;
5808
5809
    ImGuiContext& g = *GImGui;
5810
    const ImGuiStyle& style = g.Style;
5811
    const float square_sz = GetFrameHeight();
5812
    const char* label_display_end = FindRenderedTextEnd(label);
5813
    float w_full = CalcItemWidth();
5814
    g.NextItemData.ClearFlags();
5815
5816
    BeginGroup();
5817
    PushID(label);
5818
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
5819
    if (set_current_color_edit_id)
5820
        g.ColorEditCurrentID = window->IDStack.back();
5821
5822
    // If we're not showing any slider there's no point in doing any HSV conversions
5823
    const ImGuiColorEditFlags flags_untouched = flags;
5824
    if (flags & ImGuiColorEditFlags_NoInputs)
5825
        flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
5826
5827
    // Context menu: display and modify options (before defaults are applied)
5828
    if (!(flags & ImGuiColorEditFlags_NoOptions))
5829
        ColorEditOptionsPopup(col, flags);
5830
5831
    // Read stored options
5832
    if (!(flags & ImGuiColorEditFlags_DisplayMask_))
5833
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
5834
    if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
5835
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
5836
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
5837
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
5838
    if (!(flags & ImGuiColorEditFlags_InputMask_))
5839
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
5840
    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
5841
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
5842
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
5843
5844
    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
5845
    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
5846
    const int components = alpha ? 4 : 3;
5847
    const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
5848
    const float w_inputs = ImMax(w_full - w_button, 1.0f);
5849
    w_full = w_inputs + w_button;
5850
5851
    // Convert to the formats we need
5852
    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
5853
    if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
5854
        ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
5855
    else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
5856
    {
5857
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
5858
        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
5859
        ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
5860
    }
5861
    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]) };
5862
5863
    bool value_changed = false;
5864
    bool value_changed_as_float = false;
5865
5866
    const ImVec2 pos = window->DC.CursorPos;
5867
    const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
5868
    window->DC.CursorPos.x = pos.x + inputs_offset_x;
5869
5870
    if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5871
    {
5872
        // RGB/HSV 0..255 Sliders
5873
        const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
5874
        const float w_per_component = IM_TRUNC(w_items / components);
5875
        const bool draw_color_marker = (flags & (ImGuiColorEditFlags_DisplayHSV | ImGuiColorEditFlags_NoColorMarkers)) == 0;
5876
5877
        const bool hide_prefix = draw_color_marker || (w_per_component <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
5878
        static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
5879
        static const char* fmt_table_int[3][4] =
5880
        {
5881
            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
5882
            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
5883
            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
5884
        };
5885
        static const char* fmt_table_float[3][4] =
5886
        {
5887
            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
5888
            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
5889
            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
5890
        };
5891
        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
5892
        const ImGuiSliderFlags drag_flags = draw_color_marker ? ImGuiSliderFlags_ColorMarkers : ImGuiSliderFlags_None;
5893
5894
        float prev_split = 0.0f;
5895
        for (int n = 0; n < components; n++)
5896
        {
5897
            if (n > 0)
5898
                SameLine(0, style.ItemInnerSpacing.x);
5899
            float next_split = IM_TRUNC(w_items * (n + 1) / components);
5900
            SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
5901
            prev_split = next_split;
5902
            if (draw_color_marker)
5903
                SetNextItemColorMarker(GDefaultRgbaColorMarkers[n]);
5904
5905
            // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
5906
            if (flags & ImGuiColorEditFlags_Float)
5907
            {
5908
                value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n], drag_flags);
5909
                value_changed_as_float |= value_changed;
5910
            }
5911
            else
5912
            {
5913
                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n], drag_flags);
5914
            }
5915
            if (!(flags & ImGuiColorEditFlags_NoOptions))
5916
                OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5917
        }
5918
    }
5919
    else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
5920
    {
5921
        // RGB Hexadecimal Input
5922
        char buf[64];
5923
        if (alpha)
5924
            ImFormatString(buf, IM_COUNTOF(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));
5925
        else
5926
            ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
5927
        SetNextItemWidth(w_inputs);
5928
        if (InputText("##Text", buf, IM_COUNTOF(buf), ImGuiInputTextFlags_CharsUppercase))
5929
        {
5930
            value_changed = true;
5931
            char* p = buf;
5932
            while (*p == '#' || ImCharIsBlankA(*p))
5933
                p++;
5934
            i[0] = i[1] = i[2] = 0;
5935
            i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
5936
            int r;
5937
            if (alpha)
5938
                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)
5939
            else
5940
                r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
5941
            IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
5942
        }
5943
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5944
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5945
    }
5946
5947
    ImGuiWindow* picker_active_window = NULL;
5948
    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
5949
    {
5950
        const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
5951
        window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
5952
5953
        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
5954
        if (ColorButton("##ColorButton", col_v4, flags))
5955
        {
5956
            if (!(flags & ImGuiColorEditFlags_NoPicker))
5957
            {
5958
                // Store current color and open a picker
5959
                g.ColorPickerRef = col_v4;
5960
                OpenPopup("picker");
5961
                SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
5962
            }
5963
        }
5964
        if (!(flags & ImGuiColorEditFlags_NoOptions))
5965
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
5966
5967
        if (BeginPopup("picker"))
5968
        {
5969
            if (g.CurrentWindow->BeginCount == 1)
5970
            {
5971
                picker_active_window = g.CurrentWindow;
5972
                if (label != label_display_end)
5973
                {
5974
                    TextEx(label, label_display_end);
5975
                    Spacing();
5976
                }
5977
                ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
5978
                ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
5979
                SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
5980
                value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
5981
            }
5982
            EndPopup();
5983
        }
5984
    }
5985
5986
    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
5987
    {
5988
        // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
5989
        // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.
5990
        SameLine(0.0f, style.ItemInnerSpacing.x);
5991
        window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
5992
        TextEx(label, label_display_end);
5993
    }
5994
5995
    // Convert back
5996
    if (value_changed && picker_active_window == NULL)
5997
    {
5998
        if (!value_changed_as_float)
5999
            for (int n = 0; n < 4; n++)
6000
                f[n] = i[n] / 255.0f;
6001
        if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
6002
        {
6003
            g.ColorEditSavedHue = f[0];
6004
            g.ColorEditSavedSat = f[1];
6005
            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
6006
            g.ColorEditSavedID = g.ColorEditCurrentID;
6007
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
6008
        }
6009
        if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
6010
            ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
6011
6012
        col[0] = f[0];
6013
        col[1] = f[1];
6014
        col[2] = f[2];
6015
        if (alpha)
6016
            col[3] = f[3];
6017
    }
6018
6019
    if (set_current_color_edit_id)
6020
        g.ColorEditCurrentID = 0;
6021
    PopID();
6022
    EndGroup();
6023
6024
    // Drag and Drop Target
6025
    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
6026
    if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
6027
    {
6028
        bool accepted_drag_drop = false;
6029
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
6030
        {
6031
            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
6032
            value_changed = accepted_drag_drop = true;
6033
        }
6034
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
6035
        {
6036
            memcpy((float*)col, payload->Data, sizeof(float) * components);
6037
            value_changed = accepted_drag_drop = true;
6038
        }
6039
6040
        // Drag-drop payloads are always RGB
6041
        if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
6042
            ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
6043
        EndDragDropTarget();
6044
    }
6045
6046
    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
6047
    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
6048
        g.LastItemData.ID = g.ActiveId;
6049
6050
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
6051
        MarkItemEdited(g.LastItemData.ID);
6052
6053
    return value_changed;
6054
}
6055
6056
bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
6057
{
6058
    float col4[4] = { col[0], col[1], col[2], 1.0f };
6059
    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
6060
        return false;
6061
    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
6062
    return true;
6063
}
6064
6065
// Helper for ColorPicker4()
6066
static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
6067
{
6068
    ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
6069
    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));
6070
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
6071
    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));
6072
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
6073
}
6074
6075
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
6076
// (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.)
6077
// 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..)
6078
// 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)
6079
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
6080
{
6081
    ImGuiContext& g = *GImGui;
6082
    ImGuiWindow* window = GetCurrentWindow();
6083
    if (window->SkipItems)
6084
        return false;
6085
6086
    ImDrawList* draw_list = window->DrawList;
6087
    ImGuiStyle& style = g.Style;
6088
    ImGuiIO& io = g.IO;
6089
6090
    const float width = CalcItemWidth();
6091
    const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
6092
    g.NextItemData.ClearFlags();
6093
6094
    PushID(label);
6095
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
6096
    if (set_current_color_edit_id)
6097
        g.ColorEditCurrentID = window->IDStack.back();
6098
    BeginGroup();
6099
6100
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
6101
        flags |= ImGuiColorEditFlags_NoSmallPreview;
6102
6103
    // Context menu: display and store options.
6104
    if (!(flags & ImGuiColorEditFlags_NoOptions))
6105
        ColorPickerOptionsPopup(col, flags);
6106
6107
    // Read stored options
6108
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
6109
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
6110
    if (!(flags & ImGuiColorEditFlags_InputMask_))
6111
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
6112
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
6113
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
6114
    if (!(flags & ImGuiColorEditFlags_NoOptions))
6115
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
6116
6117
    // Setup
6118
    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
6119
    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
6120
    ImVec2 picker_pos = window->DC.CursorPos;
6121
    float square_sz = GetFrameHeight();
6122
    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
6123
    float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
6124
    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
6125
    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
6126
    float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
6127
6128
    float backup_initial_col[4];
6129
    memcpy(backup_initial_col, col, components * sizeof(float));
6130
6131
    float wheel_thickness = sv_picker_size * 0.08f;
6132
    float wheel_r_outer = sv_picker_size * 0.50f;
6133
    float wheel_r_inner = wheel_r_outer - wheel_thickness;
6134
    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
6135
6136
    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
6137
    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
6138
    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
6139
    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
6140
    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
6141
6142
    float H = col[0], S = col[1], V = col[2];
6143
    float R = col[0], G = col[1], B = col[2];
6144
    if (flags & ImGuiColorEditFlags_InputRGB)
6145
    {
6146
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
6147
        ColorConvertRGBtoHSV(R, G, B, H, S, V);
6148
        ColorEditRestoreHS(col, &H, &S, &V);
6149
    }
6150
    else if (flags & ImGuiColorEditFlags_InputHSV)
6151
    {
6152
        ColorConvertHSVtoRGB(H, S, V, R, G, B);
6153
    }
6154
6155
    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
6156
6157
    PushItemFlag(ImGuiItemFlags_NoNav, true);
6158
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
6159
    {
6160
        // Hue wheel + SV triangle logic
6161
        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
6162
        if (IsItemActive() && !is_readonly)
6163
        {
6164
            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
6165
            ImVec2 current_off = g.IO.MousePos - wheel_center;
6166
            float initial_dist2 = ImLengthSqr(initial_off);
6167
            if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
6168
            {
6169
                // Interactive with Hue wheel
6170
                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
6171
                if (H < 0.0f)
6172
                    H += 1.0f;
6173
                value_changed = value_changed_h = true;
6174
            }
6175
            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
6176
            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
6177
            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
6178
            {
6179
                // Interacting with SV triangle
6180
                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
6181
                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
6182
                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
6183
                float uu, vv, ww;
6184
                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
6185
                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
6186
                S = ImClamp(uu / V, 0.0001f, 1.0f);
6187
                value_changed = value_changed_sv = true;
6188
            }
6189
        }
6190
        if (!(flags & ImGuiColorEditFlags_NoOptions))
6191
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
6192
    }
6193
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
6194
    {
6195
        // SV rectangle logic
6196
        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
6197
        if (IsItemActive() && !is_readonly)
6198
        {
6199
            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
6200
            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
6201
            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
6202
            value_changed = value_changed_sv = true;
6203
        }
6204
        if (!(flags & ImGuiColorEditFlags_NoOptions))
6205
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
6206
6207
        // Hue bar logic
6208
        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
6209
        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
6210
        if (IsItemActive() && !is_readonly)
6211
        {
6212
            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
6213
            value_changed = value_changed_h = true;
6214
        }
6215
    }
6216
6217
    // Alpha bar logic
6218
    if (alpha_bar)
6219
    {
6220
        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
6221
        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
6222
        if (IsItemActive())
6223
        {
6224
            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
6225
            value_changed = true;
6226
        }
6227
    }
6228
    PopItemFlag(); // ImGuiItemFlags_NoNav
6229
6230
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
6231
    {
6232
        SameLine(0, style.ItemInnerSpacing.x);
6233
        BeginGroup();
6234
    }
6235
6236
    if (!(flags & ImGuiColorEditFlags_NoLabel))
6237
    {
6238
        const char* label_display_end = FindRenderedTextEnd(label);
6239
        if (label != label_display_end)
6240
        {
6241
            if ((flags & ImGuiColorEditFlags_NoSidePreview))
6242
                SameLine(0, style.ItemInnerSpacing.x);
6243
            TextEx(label, label_display_end);
6244
        }
6245
    }
6246
6247
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
6248
    {
6249
        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
6250
        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
6251
        if ((flags & ImGuiColorEditFlags_NoLabel))
6252
            Text("Current");
6253
6254
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip;
6255
        ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
6256
        if (ref_col != NULL)
6257
        {
6258
            Text("Original");
6259
            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
6260
            if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
6261
            {
6262
                memcpy(col, ref_col, components * sizeof(float));
6263
                value_changed = true;
6264
            }
6265
        }
6266
        PopItemFlag();
6267
        EndGroup();
6268
    }
6269
6270
    // Convert back color to RGB
6271
    if (value_changed_h || value_changed_sv)
6272
    {
6273
        if (flags & ImGuiColorEditFlags_InputRGB)
6274
        {
6275
            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
6276
            g.ColorEditSavedHue = H;
6277
            g.ColorEditSavedSat = S;
6278
            g.ColorEditSavedID = g.ColorEditCurrentID;
6279
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
6280
        }
6281
        else if (flags & ImGuiColorEditFlags_InputHSV)
6282
        {
6283
            col[0] = H;
6284
            col[1] = S;
6285
            col[2] = V;
6286
        }
6287
    }
6288
6289
    // R,G,B and H,S,V slider color editor
6290
    bool value_changed_fix_hue_wrap = false;
6291
    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
6292
    {
6293
        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
6294
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview;
6295
        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
6296
        if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
6297
            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
6298
            {
6299
                // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
6300
                // 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)
6301
                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
6302
                value_changed = true;
6303
            }
6304
        if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
6305
            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
6306
        if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
6307
            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
6308
        PopItemWidth();
6309
    }
6310
6311
    // Try to cancel hue wrap (after ColorEdit4 call), if any
6312
    if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
6313
    {
6314
        float new_H, new_S, new_V;
6315
        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
6316
        if (new_H <= 0 && H > 0)
6317
        {
6318
            if (new_V <= 0 && V != new_V)
6319
                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
6320
            else if (new_S <= 0)
6321
                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
6322
        }
6323
    }
6324
6325
    if (value_changed)
6326
    {
6327
        if (flags & ImGuiColorEditFlags_InputRGB)
6328
        {
6329
            R = col[0];
6330
            G = col[1];
6331
            B = col[2];
6332
            ColorConvertRGBtoHSV(R, G, B, H, S, V);
6333
            ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.
6334
        }
6335
        else if (flags & ImGuiColorEditFlags_InputHSV)
6336
        {
6337
            H = col[0];
6338
            S = col[1];
6339
            V = col[2];
6340
            ColorConvertHSVtoRGB(H, S, V, R, G, B);
6341
        }
6342
    }
6343
6344
    const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
6345
    const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
6346
    const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
6347
    const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
6348
    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) };
6349
6350
    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);
6351
    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
6352
    ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
6353
6354
    ImVec2 sv_cursor_pos;
6355
6356
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
6357
    {
6358
        // Render Hue Wheel
6359
        const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
6360
        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
6361
        for (int n = 0; n < 6; n++)
6362
        {
6363
            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
6364
            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
6365
            const int vert_start_idx = draw_list->VtxBuffer.Size;
6366
            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
6367
            draw_list->PathStroke(col_white, wheel_thickness);
6368
            const int vert_end_idx = draw_list->VtxBuffer.Size;
6369
6370
            // Paint colors over existing vertices
6371
            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
6372
            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
6373
            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
6374
        }
6375
6376
        // Render Cursor + preview on Hue Wheel
6377
        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
6378
        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
6379
        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);
6380
        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
6381
        int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.
6382
        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
6383
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
6384
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
6385
6386
        // Render SV triangle (rotated according to hue)
6387
        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
6388
        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
6389
        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
6390
        ImVec2 uv_white = GetFontTexUvWhitePixel();
6391
        draw_list->PrimReserve(3, 3);
6392
        draw_list->PrimVtx(tra, uv_white, hue_color32);
6393
        draw_list->PrimVtx(trb, uv_white, col_black);
6394
        draw_list->PrimVtx(trc, uv_white, col_white);
6395
        draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
6396
        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
6397
    }
6398
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
6399
    {
6400
        // Render SV Square
6401
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
6402
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
6403
        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
6404
        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
6405
        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);
6406
6407
        // Render Hue Bar
6408
        for (int i = 0; i < 6; ++i)
6409
            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]);
6410
        float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
6411
        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
6412
        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);
6413
    }
6414
6415
    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
6416
    float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
6417
    int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.
6418
    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);
6419
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
6420
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
6421
6422
    // Render alpha bar
6423
    if (alpha_bar)
6424
    {
6425
        float alpha = ImSaturate(col[3]);
6426
        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
6427
        RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
6428
        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);
6429
        float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
6430
        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
6431
        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);
6432
    }
6433
6434
    EndGroup();
6435
6436
    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
6437
        value_changed = false;
6438
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
6439
        MarkItemEdited(g.LastItemData.ID);
6440
6441
    if (set_current_color_edit_id)
6442
        g.ColorEditCurrentID = 0;
6443
    PopID();
6444
6445
    return value_changed;
6446
}
6447
6448
// A little color square. Return true when clicked.
6449
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
6450
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
6451
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
6452
bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
6453
{
6454
    ImGuiWindow* window = GetCurrentWindow();
6455
    if (window->SkipItems)
6456
        return false;
6457
6458
    ImGuiContext& g = *GImGui;
6459
    const ImGuiID id = window->GetID(desc_id);
6460
    const float default_size = GetFrameHeight();
6461
    const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
6462
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
6463
    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
6464
    if (!ItemAdd(bb, id))
6465
        return false;
6466
6467
    bool hovered, held;
6468
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
6469
6470
    if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque))
6471
        flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf);
6472
6473
    ImVec4 col_rgb = col;
6474
    if (flags & ImGuiColorEditFlags_InputHSV)
6475
        ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
6476
6477
    ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
6478
    float grid_step = ImMin(size.x, size.y) / 2.99f;
6479
    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
6480
    ImRect bb_inner = bb;
6481
    float off = 0.0f;
6482
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
6483
    {
6484
        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.
6485
        bb_inner.Expand(off);
6486
    }
6487
    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
6488
    {
6489
        float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
6490
        if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0)
6491
            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);
6492
        else
6493
            window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight);
6494
        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
6495
    }
6496
    else
6497
    {
6498
        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
6499
        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb;
6500
        if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0)
6501
            RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
6502
        else
6503
            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
6504
    }
6505
    RenderNavCursor(bb, id);
6506
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
6507
    {
6508
        if (g.Style.FrameBorderSize > 0.0f)
6509
            RenderFrameBorder(bb.Min, bb.Max, rounding);
6510
        else
6511
            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding, 1.0f * (float)(int)g.Style._MainScale); // Color buttons are often in need of some sort of border // FIXME-DPI
6512
    }
6513
6514
    // Drag and Drop Source
6515
    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
6516
    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
6517
    {
6518
        if (flags & ImGuiColorEditFlags_NoAlpha)
6519
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
6520
        else
6521
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
6522
        ColorButton(desc_id, col, flags);
6523
        SameLine();
6524
        TextEx("Color");
6525
        EndDragDropSource();
6526
    }
6527
6528
    // Tooltip
6529
    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
6530
        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_));
6531
6532
    return pressed;
6533
}
6534
6535
// Initialize/override default color options
6536
// FIXME: Could be moved to a simple IO field.
6537
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
6538
{
6539
    ImGuiContext& g = *GImGui;
6540
    if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
6541
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
6542
    if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
6543
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
6544
    if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
6545
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
6546
    if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
6547
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
6548
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
6549
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
6550
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
6551
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
6552
    g.ColorEditOptions = flags;
6553
}
6554
6555
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
6556
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
6557
{
6558
    ImGuiContext& g = *GImGui;
6559
6560
    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
6561
        return;
6562
    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
6563
    if (text_end > text)
6564
    {
6565
        TextEx(text, text_end);
6566
        Separator();
6567
    }
6568
6569
    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
6570
    ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
6571
    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]);
6572
    ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_;
6573
    ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz);
6574
    SameLine();
6575
    if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
6576
    {
6577
        if (flags & ImGuiColorEditFlags_NoAlpha)
6578
            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]);
6579
        else
6580
            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]);
6581
    }
6582
    else if (flags & ImGuiColorEditFlags_InputHSV)
6583
    {
6584
        if (flags & ImGuiColorEditFlags_NoAlpha)
6585
            Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
6586
        else
6587
            Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
6588
    }
6589
    EndTooltip();
6590
}
6591
6592
void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
6593
{
6594
    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
6595
    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
6596
    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
6597
        return;
6598
6599
    ImGuiContext& g = *GImGui;
6600
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
6601
    ImGuiColorEditFlags opts = g.ColorEditOptions;
6602
    if (allow_opt_inputs)
6603
    {
6604
        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
6605
        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
6606
        if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
6607
    }
6608
    if (allow_opt_datatype)
6609
    {
6610
        if (allow_opt_inputs) Separator();
6611
        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
6612
        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
6613
    }
6614
6615
    if (allow_opt_inputs || allow_opt_datatype)
6616
        Separator();
6617
    if (Button("Copy as..", ImVec2(-1, 0)))
6618
        OpenPopup("Copy");
6619
    if (BeginPopup("Copy"))
6620
    {
6621
        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]);
6622
        char buf[64];
6623
        ImFormatString(buf, IM_COUNTOF(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
6624
        if (Selectable(buf))
6625
            SetClipboardText(buf);
6626
        ImFormatString(buf, IM_COUNTOF(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
6627
        if (Selectable(buf))
6628
            SetClipboardText(buf);
6629
        ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X", cr, cg, cb);
6630
        if (Selectable(buf))
6631
            SetClipboardText(buf);
6632
        if (!(flags & ImGuiColorEditFlags_NoAlpha))
6633
        {
6634
            ImFormatString(buf, IM_COUNTOF(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
6635
            if (Selectable(buf))
6636
                SetClipboardText(buf);
6637
        }
6638
        EndPopup();
6639
    }
6640
6641
    g.ColorEditOptions = opts;
6642
    PopItemFlag();
6643
    EndPopup();
6644
}
6645
6646
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
6647
{
6648
    bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
6649
    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
6650
    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
6651
        return;
6652
6653
    ImGuiContext& g = *GImGui;
6654
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
6655
    if (allow_opt_picker)
6656
    {
6657
        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
6658
        PushItemWidth(picker_size.x);
6659
        for (int picker_type = 0; picker_type < 2; picker_type++)
6660
        {
6661
            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
6662
            if (picker_type > 0) Separator();
6663
            PushID(picker_type);
6664
            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
6665
            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
6666
            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
6667
            ImVec2 backup_pos = GetCursorScreenPos();
6668
            if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
6669
                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
6670
            SetCursorScreenPos(backup_pos);
6671
            ImVec4 previewing_ref_col;
6672
            memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
6673
            ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
6674
            PopID();
6675
        }
6676
        PopItemWidth();
6677
    }
6678
    if (allow_opt_alpha_bar)
6679
    {
6680
        if (allow_opt_picker) Separator();
6681
        CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
6682
    }
6683
    PopItemFlag();
6684
    EndPopup();
6685
}
6686
6687
//-------------------------------------------------------------------------
6688
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
6689
//-------------------------------------------------------------------------
6690
// - TreeNode()
6691
// - TreeNodeV()
6692
// - TreeNodeEx()
6693
// - TreeNodeExV()
6694
// - TreeNodeStoreStackData() [Internal]
6695
// - TreeNodeBehavior() [Internal]
6696
// - TreePush()
6697
// - TreePop()
6698
// - GetTreeNodeToLabelSpacing()
6699
// - SetNextItemOpen()
6700
// - CollapsingHeader()
6701
//-------------------------------------------------------------------------
6702
6703
bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
6704
{
6705
    va_list args;
6706
    va_start(args, fmt);
6707
    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
6708
    va_end(args);
6709
    return is_open;
6710
}
6711
6712
bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
6713
{
6714
    va_list args;
6715
    va_start(args, fmt);
6716
    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
6717
    va_end(args);
6718
    return is_open;
6719
}
6720
6721
bool ImGui::TreeNode(const char* label)
6722
{
6723
    ImGuiWindow* window = GetCurrentWindow();
6724
    if (window->SkipItems)
6725
        return false;
6726
    ImGuiID id = window->GetID(label);
6727
    return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);
6728
}
6729
6730
bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
6731
{
6732
    return TreeNodeExV(str_id, 0, fmt, args);
6733
}
6734
6735
bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
6736
{
6737
    return TreeNodeExV(ptr_id, 0, fmt, args);
6738
}
6739
6740
bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
6741
{
6742
    ImGuiWindow* window = GetCurrentWindow();
6743
    if (window->SkipItems)
6744
        return false;
6745
    ImGuiID id = window->GetID(label);
6746
    return TreeNodeBehavior(id, flags, label, NULL);
6747
}
6748
6749
bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6750
{
6751
    va_list args;
6752
    va_start(args, fmt);
6753
    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
6754
    va_end(args);
6755
    return is_open;
6756
}
6757
6758
bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
6759
{
6760
    va_list args;
6761
    va_start(args, fmt);
6762
    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
6763
    va_end(args);
6764
    return is_open;
6765
}
6766
6767
bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6768
{
6769
    ImGuiWindow* window = GetCurrentWindow();
6770
    if (window->SkipItems)
6771
        return false;
6772
6773
    ImGuiID id = window->GetID(str_id);
6774
    const char* label, *label_end;
6775
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6776
    return TreeNodeBehavior(id, flags, label, label_end);
6777
}
6778
6779
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
6780
{
6781
    ImGuiWindow* window = GetCurrentWindow();
6782
    if (window->SkipItems)
6783
        return false;
6784
6785
    ImGuiID id = window->GetID(ptr_id);
6786
    const char* label, *label_end;
6787
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
6788
    return TreeNodeBehavior(id, flags, label, label_end);
6789
}
6790
6791
// The reason those two functions are not yet in public API is because I would like to design a more feature-full and generic API for this.
6792
// They are otherwise function (cc: #3823, #9251, #7553, #6754, #5423, #2958, #2079, #1947, #1131, #722)
6793
bool ImGui::TreeNodeGetOpen(ImGuiID storage_id)
6794
{
6795
    ImGuiContext& g = *GImGui;
6796
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
6797
    return storage->GetInt(storage_id, 0) != 0;
6798
}
6799
6800
void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool is_open)
6801
{
6802
    ImGuiContext& g = *GImGui;
6803
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
6804
    storage->SetInt(storage_id, is_open ? 1 : 0);
6805
}
6806
6807
bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
6808
{
6809
    // Leaf node always open a new tree/id scope. If you never use it, add ImGuiTreeNodeFlags_NoTreePushOnOpen.
6810
    if (flags & ImGuiTreeNodeFlags_Leaf)
6811
        return true;
6812
6813
    // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function
6814
    ImGuiContext& g = *GImGui;
6815
    ImGuiWindow* window = g.CurrentWindow;
6816
    ImGuiStorage* storage = window->DC.StateStorage;
6817
6818
    bool is_open;
6819
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen)
6820
    {
6821
        if (g.NextItemData.OpenCond & ImGuiCond_Always)
6822
        {
6823
            is_open = g.NextItemData.OpenVal;
6824
            TreeNodeSetOpen(storage_id, is_open);
6825
        }
6826
        else
6827
        {
6828
            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
6829
            const int stored_value = storage->GetInt(storage_id, -1);
6830
            if (stored_value == -1)
6831
            {
6832
                is_open = g.NextItemData.OpenVal;
6833
                TreeNodeSetOpen(storage_id, is_open);
6834
            }
6835
            else
6836
            {
6837
                is_open = stored_value != 0;
6838
            }
6839
        }
6840
    }
6841
    else
6842
    {
6843
        is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
6844
    }
6845
6846
    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
6847
    // NB- If we are above max depth we still allow manually opened nodes to be logged.
6848
    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
6849
        is_open = true;
6850
6851
    return is_open;
6852
}
6853
6854
// Store ImGuiTreeNodeStackData for just submitted node.
6855
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
6856
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1)
6857
{
6858
    ImGuiContext& g = *GImGui;
6859
    ImGuiWindow* window = g.CurrentWindow;
6860
6861
    g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
6862
    ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
6863
    tree_node_data->ID = g.LastItemData.ID;
6864
    tree_node_data->TreeFlags = flags;
6865
    tree_node_data->ItemFlags = g.LastItemData.ItemFlags;
6866
    tree_node_data->NavRect = g.LastItemData.NavRect;
6867
6868
    // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees.
6869
    const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0;
6870
    tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX;
6871
    tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1;
6872
    tree_node_data->DrawLinesToNodesY2 = -FLT_MAX;
6873
    window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
6874
    if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes)
6875
        window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth);
6876
}
6877
6878
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
6879
{
6880
    ImGuiWindow* window = GetCurrentWindow();
6881
    if (window->SkipItems)
6882
        return false;
6883
6884
    ImGuiContext& g = *GImGui;
6885
    const ImGuiStyle& style = g.Style;
6886
6887
    // When not framed, we vertically increase height up to typical framed widget height
6888
    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
6889
    const bool use_frame_padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding));
6890
    const ImVec2 padding = use_frame_padding ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
6891
6892
    if (!label_end)
6893
        label_end = FindRenderedTextEnd(label);
6894
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
6895
6896
    const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);   // Collapsing arrow width + Spacing
6897
    const float text_offset_y = use_frame_padding ? ImMax(style.FramePadding.y, window->DC.CurrLineTextBaseOffset) : window->DC.CurrLineTextBaseOffset; // Latch before ItemSize changes it
6898
    const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow
6899
6900
    const float frame_height = label_size.y + padding.y * 2;
6901
    const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
6902
    const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL);
6903
    ImRect frame_bb;
6904
    frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
6905
    frame_bb.Min.y = window->DC.CursorPos.y + (text_offset_y - padding.y);
6906
    frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanLabelWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x;
6907
    frame_bb.Max.y = window->DC.CursorPos.y + (text_offset_y - padding.y) + frame_height;
6908
    if (display_frame)
6909
    {
6910
        const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits
6911
        frame_bb.Min.x -= outer_extend;
6912
        frame_bb.Max.x += outer_extend;
6913
    }
6914
6915
    ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
6916
    ItemSize(ImVec2(text_width, frame_height), padding.y);
6917
6918
    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
6919
    ImRect interact_bb = frame_bb;
6920
    if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
6921
        interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f);
6922
6923
    // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
6924
    ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;
6925
    bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);
6926
6927
    bool is_visible;
6928
    if (span_all_columns || span_all_columns_label)
6929
    {
6930
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
6931
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
6932
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
6933
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
6934
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
6935
        is_visible = ItemAdd(interact_bb, id);
6936
        window->ClipRect.Min.x = backup_clip_rect_min_x;
6937
        window->ClipRect.Max.x = backup_clip_rect_max_x;
6938
    }
6939
    else
6940
    {
6941
        is_visible = ItemAdd(interact_bb, id);
6942
    }
6943
    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
6944
    g.LastItemData.DisplayRect = frame_bb;
6945
6946
    // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsToParent enabled:
6947
    // Store data for the current depth to allow returning to this node from any child item.
6948
    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
6949
    // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsToParent by default or move it to ImGuiStyle.
6950
    bool store_tree_node_stack_data = false;
6951
    if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0)
6952
        flags |= g.Style.TreeLinesFlags;
6953
    const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f);
6954
    if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6955
    {
6956
        store_tree_node_stack_data = draw_tree_lines;
6957
        if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive)
6958
            if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
6959
                store_tree_node_stack_data = true;
6960
    }
6961
6962
    const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
6963
    if (!is_visible)
6964
    {
6965
        if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1))))
6966
        {
6967
            ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
6968
            parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway.
6969
            if (frame_bb.Min.y >= window->ClipRect.Max.y)
6970
                window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done
6971
        }
6972
        if (is_open && store_tree_node_stack_data)
6973
            TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
6974
        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
6975
            TreePushOverrideID(id);
6976
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
6977
        return is_open;
6978
    }
6979
6980
    if (span_all_columns || span_all_columns_label)
6981
    {
6982
        TablePushBackgroundChannel();
6983
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
6984
        g.LastItemData.ClipRect = window->ClipRect;
6985
    }
6986
6987
    ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
6988
    if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap))
6989
        button_flags |= ImGuiButtonFlags_AllowOverlap;
6990
    if (!is_leaf)
6991
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
6992
6993
    // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
6994
    // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
6995
    // When clicking on the rest of the tree node we always disallow keyboard modifiers.
6996
    const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
6997
    const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
6998
    const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
6999
7000
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
7001
    if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default
7002
        flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow;
7003
7004
    // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
7005
    // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
7006
    // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
7007
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
7008
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
7009
    // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
7010
    // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
7011
    // It is rather standard that arrow click react on Down rather than Up.
7012
    // 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.
7013
    if (is_mouse_x_over_arrow)
7014
        button_flags |= ImGuiButtonFlags_PressedOnClick;
7015
    else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
7016
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
7017
    else
7018
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
7019
    if (flags & ImGuiTreeNodeFlags_NoNavFocus)
7020
        button_flags |= ImGuiButtonFlags_NoNavFocus;
7021
7022
    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
7023
    const bool was_selected = selected;
7024
7025
    // Multi-selection support (header)
7026
    if (is_multi_select)
7027
    {
7028
        // Handle multi-select + alter button flags for it
7029
        MultiSelectItemHeader(id, &selected, &button_flags);
7030
        if (is_mouse_x_over_arrow)
7031
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
7032
    }
7033
    else
7034
    {
7035
        if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
7036
            button_flags |= ImGuiButtonFlags_NoKeyModsAllowed;
7037
    }
7038
7039
    bool hovered, held;
7040
    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
7041
    bool toggled = false;
7042
    if (!is_leaf)
7043
    {
7044
        if (pressed && g.DragDropHoldJustPressedId != id)
7045
        {
7046
            if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select))
7047
                toggled = true; // Single click
7048
            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
7049
                toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
7050
            if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
7051
                toggled = true; // Double click
7052
        }
7053
        else if (pressed && g.DragDropHoldJustPressedId == id)
7054
        {
7055
            IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
7056
            if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
7057
                toggled = true;
7058
            else
7059
                pressed = false; // Cancel press so it doesn't trigger selection.
7060
        }
7061
7062
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
7063
        {
7064
            toggled = true;
7065
            NavClearPreferredPosForAxis(ImGuiAxis_X);
7066
            NavMoveRequestCancel();
7067
        }
7068
        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?
7069
        {
7070
            toggled = true;
7071
            NavClearPreferredPosForAxis(ImGuiAxis_X);
7072
            NavMoveRequestCancel();
7073
        }
7074
7075
        if (toggled)
7076
        {
7077
            is_open = !is_open;
7078
            window->DC.StateStorage->SetInt(storage_id, is_open);
7079
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
7080
        }
7081
    }
7082
7083
    // Multi-selection support (footer)
7084
    if (is_multi_select)
7085
    {
7086
        bool pressed_copy = pressed && !toggled;
7087
        MultiSelectItemFooter(id, &selected, &pressed_copy);
7088
        if (pressed)
7089
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
7090
    }
7091
7092
    if (selected != was_selected)
7093
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
7094
7095
    // Render
7096
    {
7097
        const ImU32 text_col = GetColorU32(ImGuiCol_Text);
7098
        ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;
7099
        if (is_multi_select)
7100
            nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
7101
        if (display_frame)
7102
        {
7103
            // Framed type
7104
            const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
7105
            RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
7106
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
7107
            if (span_all_columns && !span_all_columns_label)
7108
                TablePopBackgroundChannel();
7109
            if (flags & ImGuiTreeNodeFlags_Bullet)
7110
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
7111
            else if (!is_leaf)
7112
                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);
7113
            else // Leaf without bullet, left-adjusted text
7114
                text_pos.x -= text_offset_x - padding.x;
7115
            if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
7116
                frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
7117
            if (g.LogEnabled)
7118
                LogSetNextTextDecoration("###", "###");
7119
        }
7120
        else
7121
        {
7122
            // Unframed typed for tree nodes
7123
            if (hovered || selected)
7124
            {
7125
                const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
7126
                RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false, 0.0f);
7127
            }
7128
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags, 0.0f);
7129
            if (span_all_columns && !span_all_columns_label)
7130
                TablePopBackgroundChannel();
7131
            if (flags & ImGuiTreeNodeFlags_Bullet)
7132
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
7133
            else if (!is_leaf)
7134
                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);
7135
            if (g.LogEnabled)
7136
                LogSetNextTextDecoration(">", NULL);
7137
        }
7138
7139
        if (draw_tree_lines)
7140
            TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f));
7141
7142
        // Label
7143
        if (display_frame)
7144
            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
7145
        else
7146
            RenderText(text_pos, label, label_end, false);
7147
7148
        if (span_all_columns_label)
7149
            TablePopBackgroundChannel();
7150
    }
7151
7152
    if (is_open && store_tree_node_stack_data)
7153
        TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
7154
    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
7155
        TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
7156
7157
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
7158
    return is_open;
7159
}
7160
7161
// Draw horizontal line from our parent node
7162
// This is only called for visible child nodes so we are not too fussy anymore about performances
7163
void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos)
7164
{
7165
    ImGuiContext& g = *GImGui;
7166
    ImGuiWindow* window = g.CurrentWindow;
7167
    if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0)
7168
        return;
7169
7170
    ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
7171
    float x1 = ImTrunc(parent_data->DrawLinesX1);
7172
    float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x);
7173
    float y = ImTrunc(target_pos.y);
7174
    float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f;
7175
    parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding);
7176
    if (x1 >= x2)
7177
        return;
7178
    if (rounding > 0.0f)
7179
    {
7180
        x1 += 0.5f + rounding;
7181
        window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3);
7182
        if (x1 < x2)
7183
            window->DrawList->PathLineTo(ImVec2(x2, y));
7184
        window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
7185
    }
7186
    else
7187
    {
7188
        window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
7189
    }
7190
}
7191
7192
// Draw vertical line of the hierarchy
7193
void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data)
7194
{
7195
    ImGuiContext& g = *GImGui;
7196
    ImGuiWindow* window = g.CurrentWindow;
7197
    float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y);
7198
    float y2 = data->DrawLinesToNodesY2;
7199
    if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull)
7200
    {
7201
        float y2_full = window->DC.CursorPos.y;
7202
        if (g.CurrentTable)
7203
            y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full);
7204
        y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f);
7205
        if (y2 + (g.Style.ItemSpacing.y + g.Style.TreeLinesRounding) < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y
7206
            y2 = y2_full;
7207
    }
7208
    y2 = ImMin(y2, window->ClipRect.Max.y);
7209
    if (y2 <= y1)
7210
        return;
7211
    float x = ImTrunc(data->DrawLinesX1);
7212
    if (data->DrawLinesTableColumn != -1)
7213
        TablePushColumnChannel(data->DrawLinesTableColumn);
7214
    window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
7215
    if (data->DrawLinesTableColumn != -1)
7216
        TablePopColumnChannel();
7217
}
7218
7219
void ImGui::TreePush(const char* str_id)
7220
{
7221
    ImGuiWindow* window = GetCurrentWindow();
7222
    Indent();
7223
    window->DC.TreeDepth++;
7224
    PushID(str_id);
7225
}
7226
7227
void ImGui::TreePush(const void* ptr_id)
7228
{
7229
    ImGuiWindow* window = GetCurrentWindow();
7230
    Indent();
7231
    window->DC.TreeDepth++;
7232
    PushID(ptr_id);
7233
}
7234
7235
void ImGui::TreePushOverrideID(ImGuiID id)
7236
{
7237
    ImGuiContext& g = *GImGui;
7238
    ImGuiWindow* window = g.CurrentWindow;
7239
    Indent();
7240
    window->DC.TreeDepth++;
7241
    PushOverrideID(id);
7242
}
7243
7244
void ImGui::TreePop()
7245
{
7246
    ImGuiContext& g = *GImGui;
7247
    ImGuiWindow* window = g.CurrentWindow;
7248
    Unindent();
7249
7250
    window->DC.TreeDepth--;
7251
    ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
7252
7253
    if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask)
7254
    {
7255
        const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
7256
        IM_ASSERT(data->ID == window->IDStack.back());
7257
7258
        // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsToParent is enabled)
7259
        if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsToParent)
7260
            if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
7261
                NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
7262
7263
        // Draw hierarchy lines
7264
        if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y)
7265
            TreeNodeDrawLineToTreePop(data);
7266
7267
        g.TreeNodeStack.pop_back();
7268
        window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
7269
        window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask;
7270
    }
7271
7272
    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.
7273
    PopID();
7274
}
7275
7276
// Horizontal distance preceding label when using TreeNode() or Bullet()
7277
float ImGui::GetTreeNodeToLabelSpacing()
7278
{
7279
    ImGuiContext& g = *GImGui;
7280
    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
7281
}
7282
7283
// Set next TreeNode/CollapsingHeader open state.
7284
void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
7285
{
7286
    ImGuiContext& g = *GImGui;
7287
    if (g.CurrentWindow->SkipItems)
7288
        return;
7289
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen;
7290
    g.NextItemData.OpenVal = is_open;
7291
    g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);
7292
}
7293
7294
// Set next TreeNode/CollapsingHeader storage id.
7295
void ImGui::SetNextItemStorageID(ImGuiID storage_id)
7296
{
7297
    ImGuiContext& g = *GImGui;
7298
    if (g.CurrentWindow->SkipItems)
7299
        return;
7300
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID;
7301
    g.NextItemData.StorageId = storage_id;
7302
}
7303
7304
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
7305
// 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().
7306
bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
7307
{
7308
    ImGuiWindow* window = GetCurrentWindow();
7309
    if (window->SkipItems)
7310
        return false;
7311
    ImGuiID id = window->GetID(label);
7312
    return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
7313
}
7314
7315
// p_visible == NULL                        : regular collapsing header
7316
// 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
7317
// p_visible != NULL && *p_visible == false : do not show the header at all
7318
// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
7319
bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
7320
{
7321
    ImGuiWindow* window = GetCurrentWindow();
7322
    if (window->SkipItems)
7323
        return false;
7324
7325
    if (p_visible && !*p_visible)
7326
        return false;
7327
7328
    ImGuiID id = window->GetID(label);
7329
    flags |= ImGuiTreeNodeFlags_CollapsingHeader;
7330
    if (p_visible)
7331
        flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
7332
    bool is_open = TreeNodeBehavior(id, flags, label);
7333
    if (p_visible != NULL)
7334
    {
7335
        // Create a small overlapping close button
7336
        // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
7337
        // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
7338
        ImGuiContext& g = *GImGui;
7339
        ImGuiLastItemData last_item_backup = g.LastItemData;
7340
        float button_size = g.FontSize;
7341
        float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
7342
        float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
7343
        ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
7344
        if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
7345
            *p_visible = false;
7346
        g.LastItemData = last_item_backup;
7347
    }
7348
7349
    return is_open;
7350
}
7351
7352
//-------------------------------------------------------------------------
7353
// [SECTION] Widgets: Selectable
7354
//-------------------------------------------------------------------------
7355
// - Selectable()
7356
//-------------------------------------------------------------------------
7357
7358
// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
7359
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
7360
// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
7361
// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
7362
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
7363
{
7364
    ImGuiWindow* window = GetCurrentWindow();
7365
    if (window->SkipItems)
7366
        return false;
7367
7368
    ImGuiContext& g = *GImGui;
7369
    const ImGuiStyle& style = g.Style;
7370
7371
    // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
7372
    ImGuiID id = window->GetID(label);
7373
    const char* label_end = FindRenderedTextEnd(label);
7374
    ImVec2 label_size = CalcTextSize(label, label_end, false);
7375
    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
7376
    ImVec2 pos = window->DC.CursorPos;
7377
    pos.y += window->DC.CurrLineTextBaseOffset;
7378
    ItemSize(size, 0.0f);
7379
7380
    // Fill horizontal space
7381
    // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
7382
    const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
7383
    const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
7384
    const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
7385
    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
7386
        size.x = ImMax(label_size.x, max_x - min_x);
7387
7388
    // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
7389
    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.
7390
    ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y);
7391
    if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
7392
    {
7393
        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
7394
        const float spacing_y = style.ItemSpacing.y;
7395
        const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
7396
        const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
7397
        bb.Min.x -= spacing_L;
7398
        bb.Min.y -= spacing_U;
7399
        bb.Max.x += (spacing_x - spacing_L);
7400
        bb.Max.y += (spacing_y - spacing_U);
7401
    }
7402
    //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
7403
7404
    const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
7405
    const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;
7406
    bool is_visible;
7407
    if (span_all_columns)
7408
    {
7409
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
7410
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
7411
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
7412
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
7413
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
7414
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
7415
        window->ClipRect.Min.x = backup_clip_rect_min_x;
7416
        window->ClipRect.Max.x = backup_clip_rect_max_x;
7417
    }
7418
    else
7419
    {
7420
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
7421
    }
7422
7423
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
7424
    if (!is_visible)
7425
        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)
7426
            return false;
7427
7428
    const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
7429
    if (disabled_item && !disabled_global) // Only testing this as an optimization
7430
        BeginDisabled();
7431
7432
    // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
7433
    // which would be advantageous since most selectable are not selected.
7434
    if (span_all_columns)
7435
    {
7436
        if (g.CurrentTable)
7437
            TablePushBackgroundChannel();
7438
        else if (window->DC.CurrentColumns)
7439
            PushColumnsBackground();
7440
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
7441
        g.LastItemData.ClipRect = window->ClipRect;
7442
    }
7443
7444
    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
7445
    ImGuiButtonFlags button_flags = 0;
7446
    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
7447
    if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
7448
    if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
7449
    if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
7450
    if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
7451
    if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
7452
7453
    // Multi-selection support (header)
7454
    const bool was_selected = selected;
7455
    if (is_multi_select)
7456
    {
7457
        // Handle multi-select + alter button flags for it
7458
        MultiSelectItemHeader(id, &selected, &button_flags);
7459
    }
7460
7461
    bool hovered, held;
7462
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
7463
    bool auto_selected = false;
7464
7465
    // Multi-selection support (footer)
7466
    if (is_multi_select)
7467
    {
7468
        MultiSelectItemFooter(id, &selected, &pressed);
7469
    }
7470
    else
7471
    {
7472
        // Auto-select when moved into
7473
        // - This will be more fully fleshed in the range-select branch
7474
        // - This is not exposed as it won't nicely work with some user side handling of shift/control
7475
        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
7476
        //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
7477
        //   - (2) usage will fail with clipped items
7478
        //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
7479
        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
7480
            if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0)
7481
                selected = pressed = auto_selected = true;
7482
    }
7483
7484
    // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad
7485
    if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
7486
    {
7487
        if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
7488
        {
7489
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
7490
            if (g.IO.ConfigNavCursorVisibleAuto)
7491
                g.NavCursorVisible = false;
7492
        }
7493
    }
7494
    if (pressed)
7495
        MarkItemEdited(id);
7496
7497
    if (selected != was_selected)
7498
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
7499
7500
    // Render
7501
    if (is_visible)
7502
    {
7503
        const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);
7504
        if (highlighted || selected)
7505
        {
7506
            // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106)
7507
            ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
7508
            RenderFrame(bb.Min, bb.Max, col, false, style.SelectableRounding);
7509
        }
7510
        if (g.NavId == id)
7511
        {
7512
            ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;
7513
            if (is_multi_select)
7514
                nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
7515
            RenderNavCursor(bb, id, nav_render_cursor_flags, style.SelectableRounding);
7516
        }
7517
    }
7518
7519
    if (span_all_columns)
7520
    {
7521
        if (g.CurrentTable)
7522
            TablePopBackgroundChannel();
7523
        else if (window->DC.CurrentColumns)
7524
            PopColumnsBackground();
7525
    }
7526
7527
    // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns.
7528
    if (is_visible)
7529
        RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, label_end, &label_size, style.SelectableTextAlign, &bb);
7530
7531
#ifdef IMGUI_DEBUG_BOXSELECT
7532
    if (g.BoxSelectState.UnclipMode) { GetForegroundDrawList()->AddText(pos, IM_COL32(255,255,0,200), label, label_end); }
7533
#endif
7534
7535
    // Automatically close popups
7536
    if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
7537
        CloseCurrentPopup();
7538
7539
    if (disabled_item && !disabled_global)
7540
        EndDisabled();
7541
7542
    // Selectable() always returns a pressed state!
7543
    // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve
7544
    // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect().
7545
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
7546
    return pressed; //-V1020
7547
}
7548
7549
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
7550
{
7551
    if (Selectable(label, *p_selected, flags, size_arg))
7552
    {
7553
        *p_selected = !*p_selected;
7554
        return true;
7555
    }
7556
    return false;
7557
}
7558
7559
7560
//-------------------------------------------------------------------------
7561
// [SECTION] Widgets: Typing-Select support
7562
//-------------------------------------------------------------------------
7563
7564
// [Experimental] Currently not exposed in public API.
7565
// Consume character inputs and return search request, if any.
7566
// This would typically only be called on the focused window or location you want to grab inputs for, e.g.
7567
//   if (ImGui::IsWindowFocused(...))
7568
//       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
7569
//           focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);
7570
// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).
7571
ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
7572
{
7573
    ImGuiContext& g = *GImGui;
7574
    ImGuiTypingSelectState* data = &g.TypingSelectState;
7575
    ImGuiTypingSelectRequest* out_request = &data->Request;
7576
7577
    // Clear buffer
7578
    const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.
7579
    const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times
7580
    if (data->SearchBuffer[0] != 0)
7581
    {
7582
        bool clear_buffer = false;
7583
        clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
7584
        clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
7585
        clear_buffer |= g.NavAnyRequest;
7586
        clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
7587
        clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
7588
        clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
7589
        //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
7590
        if (clear_buffer)
7591
            data->Clear();
7592
    }
7593
7594
    // Append to buffer
7595
    const int buffer_max_len = IM_COUNTOF(data->SearchBuffer) - 1;
7596
    int buffer_len = (int)ImStrlen(data->SearchBuffer);
7597
    bool select_request = false;
7598
    for (ImWchar w : g.IO.InputQueueCharacters)
7599
    {
7600
        const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
7601
        if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks
7602
            continue;
7603
        char w_buf[5];
7604
        ImTextCharToUtf8(w_buf, (unsigned int)w);
7605
        if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
7606
        {
7607
            select_request = true; // Same character: don't need to append to buffer.
7608
            continue;
7609
        }
7610
        if (data->SingleCharModeLock)
7611
        {
7612
            data->Clear(); // Different character: clear
7613
            buffer_len = 0;
7614
        }
7615
        memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
7616
        buffer_len += w_len;
7617
        select_request = true;
7618
    }
7619
    g.IO.InputQueueCharacters.resize(0);
7620
7621
    // Handle backspace
7622
    if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))
7623
    {
7624
        char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
7625
        *p = 0;
7626
        buffer_len = (int)(p - data->SearchBuffer);
7627
    }
7628
7629
    // Return request if any
7630
    if (buffer_len == 0)
7631
        return NULL;
7632
    if (select_request)
7633
    {
7634
        data->FocusScope = g.NavFocusScopeId;
7635
        data->LastRequestFrame = g.FrameCount;
7636
        data->LastRequestTime = (float)g.Time;
7637
    }
7638
    out_request->Flags = flags;
7639
    out_request->SearchBufferLen = buffer_len;
7640
    out_request->SearchBuffer = data->SearchBuffer;
7641
    out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
7642
    out_request->SingleCharMode = false;
7643
    out_request->SingleCharSize = 0;
7644
7645
    // Calculate if buffer contains the same character repeated.
7646
    // - This can be used to implement a special search mode on first character.
7647
    // - Performed on UTF-8 codepoint for correctness.
7648
    // - SingleCharMode is always set for first input character, because it usually leads to a "next".
7649
    if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
7650
    {
7651
        const char* buf_begin = out_request->SearchBuffer;
7652
        const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
7653
        const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
7654
        const char* p = buf_begin + c0_len;
7655
        for (; p < buf_end; p += c0_len)
7656
            if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
7657
                break;
7658
        const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
7659
        out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
7660
        out_request->SingleCharSize = (ImS8)c0_len;
7661
        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.
7662
    }
7663
7664
    return out_request;
7665
}
7666
7667
static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
7668
{
7669
    int match_len = 0;
7670
    while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
7671
        match_len++;
7672
    return match_len;
7673
}
7674
7675
// Default handler for finding a result for typing-select. You may implement your own.
7676
// You might want to display a tooltip to visualize the current request SearchBuffer
7677
// When SingleCharMode is set:
7678
// - it is better to NOT display a tooltip of other on-screen display indicator.
7679
// - the index of the currently focused item is required.
7680
//   if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.
7681
int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
7682
{
7683
    if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.
7684
        return -1;
7685
    int idx = -1;
7686
    if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
7687
        idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
7688
    else
7689
        idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
7690
    if (idx != -1)
7691
        SetNavCursorVisibleAfterMove();
7692
    return idx;
7693
}
7694
7695
// Special handling when a single character is repeated: perform search on a single letter and goes to next.
7696
int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
7697
{
7698
    // FIXME: Assume selection user data is index. Would be extremely practical.
7699
    //if (nav_item_idx == -1)
7700
    //    nav_item_idx = (int)g.NavLastValidSelectionUserData;
7701
7702
    int first_match_idx = -1;
7703
    bool return_next_match = false;
7704
    for (int idx = 0; idx < items_count; idx++)
7705
    {
7706
        const char* item_name = get_item_name_func(user_data, idx);
7707
        if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
7708
            continue;
7709
        if (return_next_match)                           // Return next matching item after current item.
7710
            return idx;
7711
        if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.
7712
            return idx;
7713
        if (first_match_idx == -1)                       // Record first match for wrapping.
7714
            first_match_idx = idx;
7715
        if (nav_item_idx == idx)                         // Record that we encountering nav_item so we can return next match.
7716
            return_next_match = true;
7717
    }
7718
    return first_match_idx; // First result
7719
}
7720
7721
int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)
7722
{
7723
    int longest_match_idx = -1;
7724
    int longest_match_len = 0;
7725
    for (int idx = 0; idx < items_count; idx++)
7726
    {
7727
        const char* item_name = get_item_name_func(user_data, idx);
7728
        const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
7729
        if (match_len <= longest_match_len)
7730
            continue;
7731
        longest_match_idx = idx;
7732
        longest_match_len = match_len;
7733
        if (match_len == req->SearchBufferLen)
7734
            break;
7735
    }
7736
    return longest_match_idx;
7737
}
7738
7739
void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
7740
{
7741
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
7742
    Text("SearchBuffer = \"%s\"", data->SearchBuffer);
7743
    Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
7744
    Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
7745
#else
7746
    IM_UNUSED(data);
7747
#endif
7748
}
7749
7750
//-------------------------------------------------------------------------
7751
// [SECTION] Widgets: Box-Select support
7752
// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.
7753
//-------------------------------------------------------------------------
7754
// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()
7755
//-------------------------------------------------------------------------
7756
// - BoxSelectPreStartDrag() [Internal]
7757
// - BoxSelectActivateDrag() [Internal]
7758
// - BoxSelectDeactivateDrag() [Internal]
7759
// - BoxSelectScrollWithMouseDrag() [Internal]
7760
// - BeginBoxSelect() [Internal]
7761
// - EndBoxSelect() [Internal]
7762
//-------------------------------------------------------------------------
7763
7764
// Call on the initial click.
7765
static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
7766
{
7767
    ImGuiContext& g = *GImGui;
7768
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7769
    bs->ID = id;
7770
    bs->IsStarting = true; // Consider starting box-select.
7771
    bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);
7772
    bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;
7773
    bs->KeyMods = g.IO.KeyMods;
7774
    bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
7775
    bs->ScrollAccum = ImVec2(0.0f, 0.0f);
7776
}
7777
7778
static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)
7779
{
7780
    ImGuiContext& g = *GImGui;
7781
    IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID);
7782
    bs->IsActive = true;
7783
    bs->Window = window;
7784
    bs->IsStarting = false;
7785
    ImGui::SetActiveID(bs->ID, window);
7786
    ImGui::SetActiveIdUsingAllKeyboardKeys();
7787
    if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
7788
        bs->RequestClear = true;
7789
}
7790
7791
static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)
7792
{
7793
    ImGuiContext& g = *GImGui;
7794
    bs->IsActive = bs->IsStarting = false;
7795
    if (g.ActiveId == bs->ID)
7796
    {
7797
        IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID);
7798
        ImGui::ClearActiveID();
7799
    }
7800
    bs->ID = 0;
7801
}
7802
7803
static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)
7804
{
7805
    ImGuiContext& g = *GImGui;
7806
    IM_ASSERT(bs->Window == window);
7807
    for (int n = 0; n < 2; n++) // each axis
7808
    {
7809
        const float mouse_pos = g.IO.MousePos[n];
7810
        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;
7811
        const float scroll_curr = window->Scroll[n];
7812
        if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))
7813
            continue;
7814
7815
        const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
7816
        const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;
7817
        bs->ScrollAccum[n] += scroll_step;
7818
7819
        // Accumulate into a stored value so we can handle high-framerate
7820
        const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);
7821
        if (scroll_step_i == 0.0f)
7822
            continue;
7823
        if (n == 0)
7824
            ImGui::SetScrollX(window, scroll_curr + scroll_step_i);
7825
        else
7826
            ImGui::SetScrollY(window, scroll_curr + scroll_step_i);
7827
        bs->ScrollAccum[n] -= scroll_step_i;
7828
    }
7829
}
7830
7831
bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
7832
{
7833
    ImGuiContext& g = *GImGui;
7834
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7835
    KeepAliveID(box_select_id);
7836
    if (bs->ID != box_select_id)
7837
        return false;
7838
7839
    // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
7840
    bs->UnclipMode = false;
7841
    bs->RequestClear = false;
7842
    if (bs->IsStarting && IsMouseDragPastThreshold(0))
7843
        BoxSelectActivateDrag(bs, window);
7844
    else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
7845
        BoxSelectDeactivateDrag(bs);
7846
    if (!bs->IsActive)
7847
        return false;
7848
7849
    // Current frame absolute prev/current rectangles are used to toggle selection.
7850
    // They are derived from positions relative to scrolling space, so "previous" rectangle is reprojected for current frame coordinates.
7851
    ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
7852
    ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
7853
    ImVec2 curr_end_pos_abs = g.IO.MousePos;
7854
    if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow
7855
        curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
7856
    bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
7857
    bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
7858
    bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
7859
    bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
7860
    //IMGUI_DEBUG_LOG("StartPosRel (%.2f,%.2f) EndPosRel (%.2f,%.2f) -> (%.2f,%.2f)\n", bs->StartPosRel.x, bs->StartPosRel.y, bs->EndPosRel.x, bs->EndPosRel.y, WindowPosAbsToRel(window, g.IO.MousePos).x, WindowPosAbsToRel(window, g.IO.MousePos).y);
7861
7862
    // Box-select 2D mode detects change of the rectangle.
7863
    // Storing unclip rects which will be tested by widgets supporting box-select. Always update rectangles when active (even if we don't use them).
7864
    // To facilitate understanding this: enable IMGUI_DEBUG_BOXSELECT and visualize all geometry.
7865
    if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7866
    {
7867
        // For both sides, compute the area differing between Prev and Curr rectangles.
7868
        bs->UnclipRects[0] = bs->UnclipRects[1] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
7869
        for (int side = 0; side < 2; side++)
7870
        {
7871
            ImVec2 d_min = (side == 0) ? ImMin(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMin(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
7872
            ImVec2 d_max = (side == 0) ? ImMax(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectPrev.Min) : ImMax(bs->BoxSelectRectCurr.Max, bs->BoxSelectRectPrev.Max);
7873
            if (d_min.x != d_max.x)
7874
            {
7875
                bs->UnclipRects[0].AddX(d_min.x);
7876
                bs->UnclipRects[0].AddX(d_max.x);
7877
            }
7878
            if (d_min.y != d_max.y)
7879
            {
7880
                bs->UnclipRects[1].AddY(d_min.y);
7881
                bs->UnclipRects[1].AddY(d_max.y);
7882
            }
7883
        }
7884
7885
        ImRect box_select_intersection = bs->BoxSelectRectPrev;
7886
        box_select_intersection.Add(bs->BoxSelectRectCurr);
7887
        if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
7888
            if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
7889
            {
7890
                bs->UnclipRects[0].AddY(box_select_intersection.Min.y);
7891
                bs->UnclipRects[0].AddY(box_select_intersection.Max.y);
7892
            }
7893
        if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7894
            if (bs->BoxSelectRectPrev.Min.y != bs->BoxSelectRectCurr.Min.y || bs->BoxSelectRectPrev.Max.y != bs->BoxSelectRectCurr.Max.y)
7895
            {
7896
                bs->UnclipRects[1].AddX(box_select_intersection.Min.x);
7897
                bs->UnclipRects[1].AddX(box_select_intersection.Max.x);
7898
            }
7899
7900
        // Merge both rectangles into one.
7901
        // FIXME-OPT: When UnclipRect.Area() is much larger than the sum of UnclipRects[0]/[1] Areas, widgets should
7902
        // ideally first use UnclipRect as a first coarse cull layer + the individual ones as a second validation.
7903
        bs->UnclipRect = bs->UnclipRects[0];
7904
        bs->UnclipRect.Add(bs->UnclipRects[1]);
7905
        if (!bs->UnclipRect.IsInverted() && (!window->ClipRect.Contains(bs->UnclipRect.Min) || !window->ClipRect.Contains(bs->UnclipRect.Max))) // !! Don't use Contains(ImRect)
7906
            bs->UnclipMode = true;
7907
        if (bs->UnclipMode && g.CurrentTable != NULL)
7908
            TableApplyExternalUnclipRect(g.CurrentTable, bs->UnclipRect); // No need submitting both
7909
    }
7910
7911
#ifdef IMGUI_DEBUG_BOXSELECT
7912
    //GetForegroundDrawList()->AddRect(scope_rect.Min, scope_rect.Max, IM_COL32(0, 255, 0, 200), 0.0f, 0, 4.0f);
7913
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
7914
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
7915
    if (ms_flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
7916
    {
7917
        for (ImRect& unclip_r : bs->UnclipRects)
7918
            if (!unclip_r.IsInverted())
7919
                GetForegroundDrawList()->AddRect(unclip_r.Min, unclip_r.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 4.0f);
7920
        GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, bs->UnclipMode ? IM_COL32(255, 255, 0, 200) : IM_COL32(255, 0, 0, 200), 0.0f, 0, 2.0f);
7921
    }
7922
#endif
7923
    return true;
7924
}
7925
7926
void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)
7927
{
7928
    ImGuiContext& g = *GImGui;
7929
    ImGuiWindow* window = g.CurrentWindow;
7930
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
7931
    IM_ASSERT(bs->IsActive);
7932
    bs->UnclipMode = false;
7933
7934
    // Render selection rectangle
7935
    bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
7936
    ImRect box_select_r = bs->BoxSelectRectCurr;
7937
    box_select_r.ClipWith(scope_rect);
7938
    ImGuiWindow* draw_window = FindFrontMostVisibleChildWindow(window);
7939
    draw_window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
7940
    draw_window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling
7941
7942
    // Scroll
7943
    const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
7944
    if (enable_scroll)
7945
    {
7946
        ImRect scroll_r = scope_rect;
7947
        scroll_r.Expand(-g.FontSize);
7948
        //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
7949
        if (!scroll_r.Contains(g.IO.MousePos))
7950
            BoxSelectScrollWithMouseDrag(bs, window, scroll_r);
7951
    }
7952
}
7953
7954
//-------------------------------------------------------------------------
7955
// [SECTION] Widgets: Multi-Select support
7956
//-------------------------------------------------------------------------
7957
// - DebugLogMultiSelectRequests() [Internal]
7958
// - CalcScopeRect() [Internal]
7959
// - BeginMultiSelect()
7960
// - EndMultiSelect()
7961
// - SetNextItemSelectionUserData()
7962
// - MultiSelectItemHeader() [Internal]
7963
// - MultiSelectItemFooter() [Internal]
7964
// - DebugNodeMultiSelectState() [Internal]
7965
//-------------------------------------------------------------------------
7966
7967
static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
7968
{
7969
    ImGuiContext& g = *GImGui;
7970
    IM_UNUSED(function);
7971
    for (const ImGuiSelectionRequest& req : io->Requests)
7972
    {
7973
        if (req.Type == ImGuiSelectionRequestType_SetAll)    IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear");
7974
        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);
7975
    }
7976
}
7977
7978
static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
7979
{
7980
    if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
7981
    {
7982
        // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
7983
        // This probably doesn't work inside a table as there are ample ambiguities related to exact time of calling BeginMultiSelect()/EndMultiSelect().
7984
        return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
7985
    }
7986
    else
7987
    {
7988
        //// When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)
7989
        ImRect scope_rect = window->InnerClipRect;
7990
        //if (g.CurrentTable != NULL)
7991
        //    scope_rect = g.CurrentTable->HostClipRect;
7992
7993
        // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
7994
        scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
7995
        return scope_rect;
7996
    }
7997
}
7998
7999
// Return ImGuiMultiSelectIO structure.
8000
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
8001
// Passing 'selection_size' and 'items_count' parameters is currently optional.
8002
// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0,
8003
//    allowing a first press to clear selection THEN the second press to leave child window and return to parent.
8004
// - '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).
8005
// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1.
8006
//   - 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.
8007
ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)
8008
{
8009
    ImGuiContext& g = *GImGui;
8010
    ImGuiWindow* window = g.CurrentWindow;
8011
8012
    if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)
8013
        g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());
8014
    ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];
8015
    IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.
8016
    g.CurrentMultiSelect = ms;
8017
    if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
8018
        flags |= ImGuiMultiSelectFlags_ScopeWindow;
8019
    if (flags & ImGuiMultiSelectFlags_SingleSelect)
8020
        flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);
8021
    if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
8022
        flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;
8023
8024
    // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250)
8025
    // They should perhaps be stacked properly?
8026
    if (ImGuiTable* table = g.CurrentTable)
8027
    {
8028
        if (!table->IsLayoutLocked)
8029
            TableUpdateLayout(table);
8030
        else if (table->CurrentColumn != -1)
8031
            TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it.
8032
    }
8033
8034
    // FIXME: BeginFocusScope()
8035
    const ImGuiID id = window->IDStack.back();
8036
    ms->Clear();
8037
    ms->FocusScopeId = id;
8038
    ms->Flags = flags;
8039
    ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
8040
    ms->ScopeRectMin = window->DC.CursorPos;
8041
    if (flags & ImGuiMultiSelectFlags_ScopeRect)
8042
        window->DC.CursorMaxPos = ms->ScopeRectMin; // CalcScopeRect() for ImGuiMultiSelectFlags_ScopeRect will measure in EndMultiSelect().
8043
    PushFocusScope(ms->FocusScopeId);
8044
    ms->IsFocused = IsInNavFocusRoute(g.CurrentFocusScopeId);
8045
    if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.
8046
        window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
8047
8048
    // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.
8049
    ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;
8050
    if (flags & ImGuiMultiSelectFlags_NoRangeSelect)
8051
        ms->KeyMods &= ~ImGuiMod_Shift;
8052
8053
    // Bind storage
8054
    ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);
8055
    storage->ID = id;
8056
    storage->LastFrameActive = g.FrameCount;
8057
    storage->LastSelectionSize = selection_size;
8058
    storage->Window = window;
8059
    ms->Storage = storage;
8060
8061
    // Output to user
8062
    ms->IO.Requests.resize(0);
8063
    ms->IO.RangeSrcItem = storage->RangeSrcItem;
8064
    ms->IO.NavIdItem = storage->NavIdItem;
8065
    ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
8066
    ms->IO.ItemsCount = items_count;
8067
8068
    // Clear when using Navigation to move within the scope
8069
    // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
8070
    bool request_clear = false;
8071
    bool request_select_all = false;
8072
    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
8073
    {
8074
        if (ms->KeyMods & ImGuiMod_Shift)
8075
            ms->IsKeyboardSetRange = true;
8076
        if (ms->IsKeyboardSetRange)
8077
            IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
8078
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
8079
            request_clear = true;
8080
    }
8081
    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
8082
    {
8083
        // Also clear on leaving scope (may be optional?)
8084
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
8085
            request_clear = true;
8086
    }
8087
8088
    // Box-select handling: update active state.
8089
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
8090
    if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
8091
    {
8092
        ms->BoxSelectId = GetID("##BoxSelect");
8093
        if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))
8094
            request_clear |= bs->RequestClear;
8095
    }
8096
8097
    if (ms->IsFocused)
8098
    {
8099
        // Shortcut: Clear selection (Escape)
8100
        // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window.
8101
        // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.
8102
        if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
8103
        {
8104
            if (selection_size != 0 || bs->IsActive)
8105
                if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))
8106
                {
8107
                    request_clear = true;
8108
                    if (bs->IsActive)
8109
                        BoxSelectDeactivateDrag(bs);
8110
                }
8111
        }
8112
8113
        // Shortcut: Select all (Ctrl+A)
8114
        if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
8115
            if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
8116
                request_select_all = true;
8117
    }
8118
8119
    if (request_clear || request_select_all)
8120
    {
8121
        MultiSelectAddSetAll(ms, request_select_all);
8122
        if (!request_select_all)
8123
            storage->LastSelectionSize = 0;
8124
    }
8125
    ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
8126
    //ms->PrevSubmittedItem = ImGuiSelectionUserData_Invalid;
8127
8128
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
8129
        DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
8130
8131
    return &ms->IO;
8132
}
8133
8134
// Return updated ImGuiMultiSelectIO structure.
8135
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
8136
ImGuiMultiSelectIO* ImGui::EndMultiSelect()
8137
{
8138
    ImGuiContext& g = *GImGui;
8139
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
8140
    ImGuiMultiSelectState* storage = ms->Storage;
8141
    ImGuiWindow* window = g.CurrentWindow;
8142
    IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!");
8143
    IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
8144
    IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);
8145
8146
    ImRect scope_rect = CalcScopeRect(ms, window);
8147
    if (ms->IsFocused)
8148
    {
8149
        // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
8150
        if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure)
8151
        {
8152
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
8153
            storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
8154
        }
8155
        if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)
8156
        {
8157
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n");
8158
            storage->NavIdItem = ImGuiSelectionUserData_Invalid;
8159
            storage->NavIdSelected = -1;
8160
        }
8161
8162
        if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))
8163
            EndBoxSelect(scope_rect, ms->Flags);
8164
    }
8165
8166
    if (ms->IsEndIO == false)
8167
        ms->IO.Requests.resize(0);
8168
8169
    // Clear selection when clicking void?
8170
    // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
8171
    // The InnerRect test is necessary for non-child/decorated windows.
8172
    bool scope_hovered = window->InnerRect.Contains(g.IO.MousePos) && IsWindowHovered(ImGuiHoveredFlags_ChildWindows);
8173
    if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
8174
        scope_hovered &= scope_rect.Contains(g.IO.MousePos);
8175
    if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
8176
    {
8177
        if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
8178
        {
8179
            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
8180
            {
8181
                BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
8182
                FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
8183
                SetHoveredID(ms->BoxSelectId);
8184
                if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
8185
                    SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select.
8186
            }
8187
        }
8188
8189
        if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
8190
            if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
8191
                MultiSelectAddSetAll(ms, false);
8192
    }
8193
8194
    // Courtesy nav wrapping helper flag
8195
    if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
8196
    {
8197
        IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
8198
        NavMoveRequestTryWrapping(GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
8199
    }
8200
8201
    // Unwind
8202
    if (ImGuiTable* table = g.CurrentTable)
8203
        if (table->IsInsideRow)
8204
            TableEndRow(table);
8205
    window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
8206
    PopFocusScope();
8207
8208
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
8209
        DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO);
8210
8211
    ms->FocusScopeId = 0;
8212
    ms->Flags = ImGuiMultiSelectFlags_None;
8213
    g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;
8214
8215
    return &ms->IO;
8216
}
8217
8218
void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
8219
{
8220
    // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
8221
    // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
8222
    ImGuiContext& g = *GImGui;
8223
    g.NextItemData.SelectionUserData = selection_user_data;
8224
    g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;
8225
8226
    if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
8227
    {
8228
        // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
8229
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
8230
        if (ms->IO.RangeSrcItem == selection_user_data)
8231
            ms->RangeSrcPassedBy = true;
8232
        //ms->PrevSubmittedItem = ms->CurrSubmittedItem; // Can't rely on previous g.NextItemData.SelectionUserData because NextItemData is not restored on nested multi-select.
8233
        //ms->CurrSubmittedItem = selection_user_data;
8234
    }
8235
    else
8236
    {
8237
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
8238
    }
8239
}
8240
8241
// In charge of:
8242
// - Applying SetAll for submitted items.
8243
// - Applying SetRange for submitted items and record end points.
8244
// - Altering button behavior flags to facilitate use with drag and drop.
8245
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)
8246
{
8247
    ImGuiContext& g = *GImGui;
8248
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
8249
8250
    bool selected = *p_selected;
8251
    if (ms->IsFocused)
8252
    {
8253
        ImGuiMultiSelectState* storage = ms->Storage;
8254
        ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
8255
        IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
8256
8257
        // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().
8258
        // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
8259
        // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()
8260
        if (ms->LoopRequestSetAll != -1)
8261
            selected = (ms->LoopRequestSetAll == 1);
8262
8263
        // 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)
8264
        // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function)
8265
        if (ms->IsKeyboardSetRange)
8266
        {
8267
            IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);
8268
            const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.
8269
            if (is_range_dst)
8270
                ms->RangeDstPassedBy = true;
8271
            if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst
8272
            {
8273
                storage->RangeSrcItem = item_data;
8274
                storage->RangeSelected = selected ? 1 : 0;
8275
            }
8276
            const bool is_range_src = storage->RangeSrcItem == item_data;
8277
            if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)
8278
            {
8279
                // Apply range-select value to visible items
8280
                IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);
8281
                selected = (storage->RangeSelected != 0);
8282
            }
8283
            else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
8284
            {
8285
                // Clear other items
8286
                selected = false;
8287
            }
8288
        }
8289
        *p_selected = selected;
8290
    }
8291
8292
    // Alter button behavior flags
8293
    // To handle drag and drop of multiple items we need to avoid clearing selection on click.
8294
    // 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.
8295
    if (p_button_flags != NULL)
8296
    {
8297
        ImGuiButtonFlags button_flags = *p_button_flags;
8298
        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
8299
        button_flags &= ~(ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease);
8300
        if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickAlways)
8301
            button_flags |= ImGuiButtonFlags_PressedOnClick;
8302
        else if (ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease)
8303
            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
8304
        else // ImGuiMultiSelectFlags_SelectOnAuto
8305
            button_flags |= (!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnClickRelease;
8306
        *p_button_flags = button_flags;
8307
    }
8308
}
8309
8310
// In charge of:
8311
// - Auto-select on navigation.
8312
// - Box-select toggle handling.
8313
// - Right-click handling.
8314
// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.
8315
// - Record current selection state for RangeSrc
8316
// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite.
8317
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed, ImGuiMultiSelectFlags extra_flags)
8318
{
8319
    ImGuiContext& g = *GImGui;
8320
    ImGuiWindow* window = g.CurrentWindow;
8321
8322
    bool selected = *p_selected;
8323
    bool pressed = *p_pressed;
8324
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
8325
    ImGuiMultiSelectState* storage = ms->Storage;
8326
    if (pressed)
8327
        ms->IsFocused = true;
8328
8329
    bool hovered = false;
8330
    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)
8331
        hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
8332
    if (!ms->IsFocused && !hovered)
8333
        return;
8334
8335
    ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
8336
8337
    ImGuiMultiSelectFlags flags = ms->Flags | extra_flags;
8338
    const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;
8339
    bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;
8340
    bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;
8341
8342
    bool apply_to_range_src = false;
8343
8344
    if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
8345
        apply_to_range_src = true;
8346
    if (ms->IsEndIO == false)
8347
    {
8348
        ms->IO.Requests.resize(0);
8349
        ms->IsEndIO = true;
8350
    }
8351
8352
    // Auto-select as you navigate a list
8353
    if (g.NavJustMovedToId == id)
8354
    {
8355
        if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
8356
        {
8357
            if (is_ctrl && is_shift)
8358
                pressed = true;
8359
            else if (!is_ctrl)
8360
                selected = pressed = true;
8361
        }
8362
        else
8363
        {
8364
            // With NoAutoSelect, using Shift+keyboard performs a write/copy
8365
            if (is_shift)
8366
                pressed = true;
8367
            else if (!is_ctrl)
8368
                apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this
8369
        }
8370
    }
8371
8372
    if (apply_to_range_src)
8373
    {
8374
        storage->RangeSrcItem = item_data;
8375
        storage->RangeSelected = selected; // Will be updated at the end of this function anyway.
8376
    }
8377
8378
    // Box-select toggle handling
8379
    if (ms->BoxSelectId != 0)
8380
        if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
8381
        {
8382
            ImRect item_rect = g.LastItemData.Rect;
8383
            if (!window->DC.NavIsScrollPushableX) // FIXME: Rename to be more generic.
8384
                if (ImGuiTable* table = g.CurrentTable)
8385
                    if (table->CurrentColumn != -1)
8386
                    {
8387
                        // FIXME: We cannot solely use current ClipRect as it includes HostClipRect.
8388
                        // However we account for ClipRect being larger than current column (e.g. when using SpanAllColumns)
8389
                        // A more generic version would be nice, but window->WorkRect.Min/Max exclude CellPadding. (#7994, #9383)
8390
                        ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
8391
                        float clip_min_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Min.x : window->ClipRect.Min.x;
8392
                        float clip_max_x = (g.LastItemData.ItemFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect.Max.x : window->ClipRect.Max.x;
8393
                        if (clip_min_x != clip_max_x) // When zero sized we expect that bounds have been clamped and thus are unreliable
8394
                        {
8395
                            item_rect.Min.x = ImMax(item_rect.Min.x, ImMin(column->MinX, clip_min_x));
8396
                            item_rect.Max.x = ImMin(item_rect.Max.x, ImMax(column->MaxX, clip_max_x));
8397
                        }
8398
                        else
8399
                        {
8400
                            item_rect.Min.x = ImMax(item_rect.Min.x, column->MinX);
8401
                            item_rect.Max.x = ImMin(item_rect.Max.x, column->MaxX);
8402
                        }
8403
                        //GetForegroundDrawList()->AddRect(item_rect.Min, item_rect.Max, IM_COL32(255, 0, 255, 255));
8404
                    }
8405
            const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(item_rect);
8406
            const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(item_rect);
8407
            if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
8408
            {
8409
                if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
8410
                {
8411
                    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)
8412
                    bs->IsStartedSetNavIdOnce = false;
8413
                }
8414
                else
8415
                {
8416
                    selected = !selected;
8417
                    MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
8418
#ifdef IMGUI_DEBUG_BOXSELECT
8419
                    GetForegroundDrawList()->AddRectFilled(g.LastItemData.Rect.Min, g.LastItemData.Rect.Max, selected ? IM_COL32(0, 255, 0, 200) : IM_COL32(255, 0, 0, 200));
8420
#endif
8421
                }
8422
                storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
8423
            }
8424
        }
8425
8426
    // Right-click handling.
8427
    // FIXME-MULTISELECT: Maybe should be moved to Selectable()? Also see #5816, #8200, #9015
8428
    if (hovered && IsMouseClicked(1) && (flags & (ImGuiMultiSelectFlags_NoAutoSelect | ImGuiMultiSelectFlags_NoSelectOnRightClick)) == 0)
8429
    {
8430
        if (g.ActiveId != 0 && g.ActiveId != id)
8431
            ClearActiveID();
8432
        SetFocusID(id, window);
8433
        if (!pressed && !selected)
8434
        {
8435
            pressed = true;
8436
            is_ctrl = is_shift = false;
8437
        }
8438
    }
8439
8440
    // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected.
8441
    // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something
8442
    // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional?
8443
    const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);
8444
8445
    // Alter selection
8446
    if (pressed && (!enter_pressed || !selected))
8447
    {
8448
        // Box-select
8449
        ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
8450
        if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
8451
            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
8452
                BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
8453
8454
        //----------------------------------------------------------------------------------------
8455
        // ACTION                      | Begin  | Pressed/Activated  | End
8456
        //----------------------------------------------------------------------------------------
8457
        // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1
8458
        // Keys Navigated: Ctrl        | n/a    | n/a
8459
        // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
8460
        // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst
8461
        // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1
8462
        // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1
8463
        // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1
8464
        //----------------------------------------------------------------------------------------
8465
        // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1
8466
        // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1
8467
        // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
8468
        // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst
8469
        //----------------------------------------------------------------------------------------
8470
8471
        if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
8472
        {
8473
            bool request_clear = false;
8474
            if (is_singleselect)
8475
                request_clear = true;
8476
            else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
8477
                request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;
8478
            else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
8479
                request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
8480
            if (request_clear)
8481
                MultiSelectAddSetAll(ms, false);
8482
        }
8483
8484
        int range_direction;
8485
        bool range_selected;
8486
        if (is_shift && !is_singleselect)
8487
        {
8488
            //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
8489
            if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
8490
                storage->RangeSrcItem = item_data;
8491
            if ((flags & ImGuiMultiSelectFlags_CheckboxMode_) == 0)
8492
            {
8493
                // Shift+Arrow always select
8494
                // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)
8495
                range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
8496
            }
8497
            else
8498
            {
8499
                // Shift+Arrow copy source selection state
8500
                // Shift+Click always copy from target selection state
8501
                if (ms->IsKeyboardSetRange)
8502
                    range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
8503
                else
8504
                    range_selected = !selected;
8505
            }
8506
            range_direction = ms->RangeSrcPassedBy ? +1 : -1;
8507
        }
8508
        else
8509
        {
8510
            // Ctrl inverts selection, otherwise always select
8511
            if ((flags & ImGuiMultiSelectFlags_CheckboxMode_) == 0)
8512
                selected = is_ctrl ? !selected : true;
8513
            else
8514
                selected = !selected;
8515
            storage->RangeSrcItem = item_data;
8516
            range_selected = selected;
8517
            range_direction = +1;
8518
        }
8519
        MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);
8520
    }
8521
8522
    // Update/store the selection state of the Source item (used by Ctrl+Shift, when Source is unselected we perform a range unselect)
8523
    if (storage->RangeSrcItem == item_data)
8524
        storage->RangeSelected = selected ? 1 : 0;
8525
8526
    // Update/store the selection state of focused item
8527
    if (g.NavId == id)
8528
    {
8529
        storage->NavIdItem = item_data;
8530
        storage->NavIdSelected = selected ? 1 : 0;
8531
    }
8532
    if (storage->NavIdItem == item_data)
8533
        ms->NavIdPassedBy = true;
8534
8535
    *p_selected = selected;
8536
    *p_pressed = pressed;
8537
}
8538
8539
void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
8540
{
8541
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };
8542
    ms->IO.Requests.resize(0);      // Can always clear previous requests
8543
    ms->IO.Requests.push_back(req); // Add new request
8544
}
8545
8546
void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
8547
{
8548
    // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
8549
    // FIXME-OPT: Disabled on 2026/04/09 as this would break with any form of coarse clipping that we don't know about (e.g. TableNextColumn() return value).
8550
    // The low-hanging fruit would be to know that ImGuiSelectionUserData are sequential indices, in which case we can trivially compare PrevSubmittedItem + RangeDir == FirstItem.
8551
    // User can always perform this merge if required.
8552
#if 0
8553
    if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
8554
    {
8555
        ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
8556
        if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->PrevSubmittedItem && prev->Selected == selected)
8557
        {
8558
            prev->RangeLastItem = last_item;
8559
            return;
8560
        }
8561
    }
8562
#endif
8563
8564
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };
8565
    ms->IO.Requests.push_back(req); // Add new request
8566
}
8567
8568
void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
8569
{
8570
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
8571
    const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
8572
    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
8573
    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*");
8574
    if (!is_active) { PopStyleColor(); }
8575
    if (!open)
8576
        return;
8577
    Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);
8578
    Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);
8579
    Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user
8580
    TreePop();
8581
#else
8582
    IM_UNUSED(storage);
8583
#endif
8584
}
8585
8586
//-------------------------------------------------------------------------
8587
// [SECTION] Widgets: Multi-Select helpers
8588
//-------------------------------------------------------------------------
8589
// - ImGuiSelectionBasicStorage
8590
// - ImGuiSelectionExternalStorage
8591
//-------------------------------------------------------------------------
8592
8593
ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()
8594
{
8595
    Size = 0;
8596
    PreserveOrder = false;
8597
    UserData = NULL;
8598
    AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };
8599
    _SelectionOrder = 1; // Always >0
8600
}
8601
8602
void ImGuiSelectionBasicStorage::Clear()
8603
{
8604
    Size = 0;
8605
    _SelectionOrder = 1; // Always >0
8606
    _Storage.Data.resize(0);
8607
}
8608
8609
void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)
8610
{
8611
    ImSwap(Size, r.Size);
8612
    ImSwap(_SelectionOrder, r._SelectionOrder);
8613
    _Storage.Data.swap(r._Storage.Data);
8614
}
8615
8616
bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const
8617
{
8618
    return _Storage.GetInt(id, 0) != 0;
8619
}
8620
8621
static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)
8622
{
8623
    int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;
8624
    int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;
8625
    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
8626
}
8627
8628
// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.
8629
// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector<ImGuiID> instead of ImGuiStorage...)
8630
bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)
8631
{
8632
    ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;
8633
    ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;
8634
    if (PreserveOrder && it == NULL && it_end != NULL)
8635
        ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()
8636
    if (it == NULL)
8637
        it = _Storage.Data.Data;
8638
    IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
8639
    if (it != it_end)
8640
        while (it->val_i == 0 && it < it_end)
8641
            it++;
8642
    const bool has_more = (it != it_end);
8643
    *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
8644
    *out_id = has_more ? it->key : 0;
8645
    if (PreserveOrder && !has_more)
8646
        _Storage.BuildSortByKey();
8647
    return has_more;
8648
}
8649
8650
void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
8651
{
8652
    int* p_int = _Storage.GetIntRef(id, 0);
8653
    if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }
8654
    else if (!selected && *p_int != 0) { *p_int = 0; Size--; }
8655
}
8656
8657
// Optimized for batch edits (with same value of 'selected')
8658
static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order)
8659
{
8660
    ImGuiStorage* storage = &selection->_Storage;
8661
    ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
8662
    const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);
8663
    if (selected == (is_contained && it->val_i != 0))
8664
        return;
8665
    if (selected && !is_contained)
8666
        storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()
8667
    else if (is_contained)
8668
        it->val_i = selected ? selection_order : 0; // Modify in-place.
8669
    selection->Size += selected ? +1 : -1;
8670
}
8671
8672
static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
8673
{
8674
    ImGuiStorage* storage = &selection->_Storage;
8675
    if (selected && selection->Size != size_before_amends)
8676
        storage->BuildSortByKey(); // When done selecting: sort everything
8677
}
8678
8679
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
8680
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
8681
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
8682
//   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
8683
//   - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform
8684
//     a lookup in order to have some way to iterate/interpolate between two items.
8685
// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices
8686
//   and constructing a view index <> object id/ptr data structure anyway.
8687
// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC.
8688
// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
8689
// The most simple implementation (using indices everywhere) would look like:
8690
//   for (ImGuiSelectionRequest& req : ms_io->Requests)
8691
//   {
8692
//      if (req.Type == ImGuiSelectionRequestType_SetAll)    { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } }
8693
//      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } }
8694
//   }
8695
void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
8696
{
8697
    // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.
8698
    // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().
8699
    // Other scheme may handle SetAll differently.
8700
    IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
8701
    IM_ASSERT(AdapterIndexToStorageId != NULL);
8702
8703
    // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)
8704
    // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
8705
    // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.
8706
    // - 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.
8707
    // - 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
8708
    //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.
8709
    // FIXME-OPT: For each block of consecutive SetRange request:
8710
    // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.
8711
    // - rewrite sorted storage a single time.
8712
    for (ImGuiSelectionRequest& req : ms_io->Requests)
8713
    {
8714
        if (req.Type == ImGuiSelectionRequestType_SetAll)
8715
        {
8716
            Clear();
8717
            if (req.Selected)
8718
            {
8719
                _Storage.Data.reserve(ms_io->ItemsCount);
8720
                const int size_before_amends = _Storage.Data.Size;
8721
                for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)
8722
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);
8723
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
8724
            }
8725
        }
8726
        else if (req.Type == ImGuiSelectionRequestType_SetRange)
8727
        {
8728
            const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;
8729
            //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);
8730
            if (selection_changes == 1 || (selection_changes < Size / 100))
8731
            {
8732
                // Multiple sorted insertion + copy likely to be faster.
8733
                // Technically we could do a single copy with a little more work (sort sequential SetRange requests)
8734
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
8735
                    SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
8736
            }
8737
            else
8738
            {
8739
                // Append insertion + single sort likely be faster.
8740
                // Use req.RangeDirection to set order field so that Shift+Clicking from 1 to 5 is different than Shift+Clicking from 5 to 1
8741
                const int size_before_amends = _Storage.Data.Size;
8742
                int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);
8743
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)
8744
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);
8745
                if (req.Selected)
8746
                    _SelectionOrder += selection_changes;
8747
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
8748
            }
8749
        }
8750
    }
8751
}
8752
8753
//-------------------------------------------------------------------------
8754
8755
ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()
8756
{
8757
    UserData = NULL;
8758
    AdapterSetItemSelected = NULL;
8759
}
8760
8761
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
8762
// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage
8763
// This makes no assumption about underlying storage.
8764
void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
8765
{
8766
    IM_ASSERT(AdapterSetItemSelected);
8767
    for (ImGuiSelectionRequest& req : ms_io->Requests)
8768
    {
8769
        if (req.Type == ImGuiSelectionRequestType_SetAll)
8770
            for (int idx = 0; idx < ms_io->ItemsCount; idx++)
8771
                AdapterSetItemSelected(this, idx, req.Selected);
8772
        if (req.Type == ImGuiSelectionRequestType_SetRange)
8773
            for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
8774
                AdapterSetItemSelected(this, idx, req.Selected);
8775
    }
8776
}
8777
8778
//-------------------------------------------------------------------------
8779
// [SECTION] Widgets: ListBox
8780
//-------------------------------------------------------------------------
8781
// - BeginListBox()
8782
// - EndListBox()
8783
// - ListBox()
8784
//-------------------------------------------------------------------------
8785
8786
// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
8787
// This handle some subtleties with capturing info from the label.
8788
// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle.
8789
// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
8790
// 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.5f * item_height).
8791
bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
8792
{
8793
    ImGuiContext& g = *GImGui;
8794
    ImGuiWindow* window = GetCurrentWindow();
8795
    if (window->SkipItems)
8796
        return false;
8797
8798
    const ImGuiStyle& style = g.Style;
8799
    const ImGuiID id = GetID(label);
8800
    const char* label_end = FindRenderedTextEnd(label);
8801
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
8802
8803
    // Size default to hold ~7.25 items.
8804
    // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
8805
    ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
8806
    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
8807
    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
8808
    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
8809
    g.NextItemData.ClearFlags();
8810
8811
    if (!IsRectVisible(bb.Min, bb.Max))
8812
    {
8813
        ItemSize(bb.GetSize(), style.FramePadding.y);
8814
        ItemAdd(bb, 0, &frame_bb);
8815
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8816
        return false;
8817
    }
8818
8819
    // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.
8820
    BeginGroup();
8821
    if (label_size.x > 0.0f)
8822
    {
8823
        ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
8824
        RenderText(label_pos, label, label_end, false);
8825
        window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
8826
        AlignTextToFramePadding();
8827
    }
8828
8829
    BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
8830
    return true;
8831
}
8832
8833
void ImGui::EndListBox()
8834
{
8835
    ImGuiContext& g = *GImGui;
8836
    ImGuiWindow* window = g.CurrentWindow;
8837
    IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
8838
    IM_UNUSED(window);
8839
8840
    EndChild();
8841
    EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
8842
}
8843
8844
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
8845
{
8846
    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
8847
    return value_changed;
8848
}
8849
8850
// This is merely a helper around BeginListBox(), EndListBox().
8851
// Considering using those directly to submit custom data or store selection differently.
8852
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)
8853
{
8854
    ImGuiContext& g = *GImGui;
8855
8856
    // Calculate size from "height_in_items"
8857
    if (height_in_items < 0)
8858
        height_in_items = ImMin(items_count, 7);
8859
    float height_in_items_f = height_in_items + 0.25f;
8860
    ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
8861
8862
    if (!BeginListBox(label, size))
8863
        return false;
8864
8865
    // Assume all items have even height (= 1 line of text). If you need items of different height,
8866
    // you can create a custom version of ListBox() in your code without using the clipper.
8867
    bool value_changed = false;
8868
    ImGuiListClipper clipper;
8869
    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.
8870
    clipper.IncludeItemByIndex(*current_item);
8871
    while (clipper.Step())
8872
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
8873
        {
8874
            const char* item_text = getter(user_data, i);
8875
            if (item_text == NULL)
8876
                item_text = "*Unknown item*";
8877
8878
            PushID(i);
8879
            const bool item_selected = (i == *current_item);
8880
            if (Selectable(item_text, item_selected))
8881
            {
8882
                *current_item = i;
8883
                value_changed = true;
8884
            }
8885
            if (item_selected)
8886
                SetItemDefaultFocus();
8887
            PopID();
8888
        }
8889
    EndListBox();
8890
8891
    if (value_changed)
8892
        MarkItemEdited(g.LastItemData.ID);
8893
8894
    return value_changed;
8895
}
8896
8897
//-------------------------------------------------------------------------
8898
// [SECTION] Widgets: PlotLines, PlotHistogram
8899
//-------------------------------------------------------------------------
8900
// - PlotEx() [Internal]
8901
// - PlotLines()
8902
// - PlotHistogram()
8903
//-------------------------------------------------------------------------
8904
// Plot/Graph widgets are not very good.
8905
// Consider using ImPlot (https://github.com/epezent/implot) which is much better!
8906
//-------------------------------------------------------------------------
8907
8908
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)
8909
{
8910
    ImGuiContext& g = *GImGui;
8911
    ImGuiWindow* window = GetCurrentWindow();
8912
    if (window->SkipItems)
8913
        return -1;
8914
8915
    const ImGuiStyle& style = g.Style;
8916
    const ImGuiID id = window->GetID(label);
8917
8918
    const char* label_end = FindRenderedTextEnd(label);
8919
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
8920
    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
8921
8922
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
8923
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
8924
    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));
8925
    ItemSize(total_bb, style.FramePadding.y);
8926
    if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav))
8927
        return -1;
8928
    bool hovered;
8929
    ButtonBehavior(frame_bb, id, &hovered, NULL);
8930
8931
    // Determine scale from values if not specified
8932
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
8933
    {
8934
        float v_min = FLT_MAX;
8935
        float v_max = -FLT_MAX;
8936
        for (int i = 0; i < values_count; i++)
8937
        {
8938
            const float v = values_getter(data, i);
8939
            if (v != v) // Ignore NaN values
8940
                continue;
8941
            v_min = ImMin(v_min, v);
8942
            v_max = ImMax(v_max, v);
8943
        }
8944
        if (scale_min == FLT_MAX)
8945
            scale_min = v_min;
8946
        if (scale_max == FLT_MAX)
8947
            scale_max = v_max;
8948
    }
8949
8950
    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
8951
8952
    const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
8953
    int idx_hovered = -1;
8954
    if (values_count >= values_count_min)
8955
    {
8956
        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
8957
        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
8958
8959
        // Tooltip on hover
8960
        if (hovered && inner_bb.Contains(g.IO.MousePos))
8961
        {
8962
            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.999f);
8963
            const int v_idx = (int)(t * item_count);
8964
            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
8965
8966
            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
8967
            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
8968
            if (plot_type == ImGuiPlotType_Lines)
8969
                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
8970
            else if (plot_type == ImGuiPlotType_Histogram)
8971
                SetTooltip("%d: %8.4g", v_idx, v0);
8972
            idx_hovered = v_idx;
8973
        }
8974
8975
        const float t_step = 1.0f / (float)res_w;
8976
        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
8977
8978
        float v0 = values_getter(data, (0 + values_offset) % values_count);
8979
        float t0 = 0.0f;
8980
        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
8981
        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
8982
8983
        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
8984
        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
8985
8986
        for (int n = 0; n < res_w; n++)
8987
        {
8988
            const float t1 = t0 + t_step;
8989
            const int v1_idx = (int)(t0 * item_count + 0.5f);
8990
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
8991
            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
8992
            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
8993
8994
            // 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.
8995
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
8996
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
8997
            if (plot_type == ImGuiPlotType_Lines)
8998
            {
8999
                window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
9000
            }
9001
            else if (plot_type == ImGuiPlotType_Histogram)
9002
            {
9003
                if (pos1.x >= pos0.x + 2.0f)
9004
                    pos1.x -= 1.0f;
9005
                window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
9006
            }
9007
9008
            t0 = t1;
9009
            tp0 = tp1;
9010
        }
9011
    }
9012
9013
    // Text overlay
9014
    if (overlay_text)
9015
        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));
9016
9017
    if (label_size.x > 0.0f)
9018
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label, label_end, false);
9019
9020
    // Return hovered index or -1 if none are hovered.
9021
    // 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().
9022
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
9023
    return idx_hovered;
9024
}
9025
9026
struct ImGuiPlotArrayGetterData
9027
{
9028
    const float* Values;
9029
    int Stride;
9030
9031
    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
9032
};
9033
9034
static float Plot_ArrayGetter(void* data, int idx)
9035
{
9036
    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
9037
    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
9038
    return v;
9039
}
9040
9041
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)
9042
{
9043
    ImGuiPlotArrayGetterData data(values, stride);
9044
    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9045
}
9046
9047
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)
9048
{
9049
    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9050
}
9051
9052
// Plot Histogram (the data provided _is_ histogram data. it doesn't compute the histogram of your data)
9053
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)
9054
{
9055
    ImGuiPlotArrayGetterData data(values, stride);
9056
    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9057
}
9058
9059
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)
9060
{
9061
    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9062
}
9063
9064
//-------------------------------------------------------------------------
9065
// [SECTION] Widgets: Value helpers
9066
// Those is not very useful, legacy API.
9067
//-------------------------------------------------------------------------
9068
// - Value()
9069
//-------------------------------------------------------------------------
9070
9071
void ImGui::Value(const char* prefix, bool b)
9072
{
9073
    Text("%s: %s", prefix, (b ? "true" : "false"));
9074
}
9075
9076
void ImGui::Value(const char* prefix, int v)
9077
{
9078
    Text("%s: %d", prefix, v);
9079
}
9080
9081
void ImGui::Value(const char* prefix, unsigned int v)
9082
{
9083
    Text("%s: %d", prefix, v);
9084
}
9085
9086
void ImGui::Value(const char* prefix, float v, const char* float_format)
9087
{
9088
    if (float_format)
9089
    {
9090
        char fmt[64];
9091
        ImFormatString(fmt, IM_COUNTOF(fmt), "%%s: %s", float_format);
9092
        Text(fmt, prefix, v);
9093
    }
9094
    else
9095
    {
9096
        Text("%s: %.3f", prefix, v);
9097
    }
9098
}
9099
9100
//-------------------------------------------------------------------------
9101
// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
9102
//-------------------------------------------------------------------------
9103
// - ImGuiMenuColumns [Internal]
9104
// - BeginMenuBar()
9105
// - EndMenuBar()
9106
// - BeginMainMenuBar()
9107
// - EndMainMenuBar()
9108
// - BeginMenu()
9109
// - EndMenu()
9110
// - MenuItemEx() [Internal]
9111
// - MenuItem()
9112
//-------------------------------------------------------------------------
9113
9114
// Helpers for internal use
9115
void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
9116
{
9117
    if (window_reappearing)
9118
        memset(Widths, 0, sizeof(Widths));
9119
    Spacing = (ImU16)spacing;
9120
    CalcNextTotalWidth(true);
9121
    memset(Widths, 0, sizeof(Widths));
9122
    TotalWidth = NextTotalWidth;
9123
    NextTotalWidth = 0;
9124
}
9125
9126
void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
9127
{
9128
    ImU16 offset = 0;
9129
    bool want_spacing = false;
9130
    for (int i = 0; i < IM_COUNTOF(Widths); i++)
9131
    {
9132
        ImU16 width = Widths[i];
9133
        if (want_spacing && width > 0)
9134
            offset += Spacing;
9135
        want_spacing |= (width > 0);
9136
        if (update_offsets)
9137
        {
9138
            if (i == 1) { OffsetLabel = offset; }
9139
            if (i == 2) { OffsetShortcut = offset; }
9140
            if (i == 3) { OffsetMark = offset; }
9141
        }
9142
        offset += width;
9143
    }
9144
    NextTotalWidth = offset;
9145
}
9146
9147
float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
9148
{
9149
    Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
9150
    Widths[1] = ImMax(Widths[1], (ImU16)w_label);
9151
    Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
9152
    Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
9153
    CalcNextTotalWidth(false);
9154
    return (float)ImMax(TotalWidth, NextTotalWidth);
9155
}
9156
9157
// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
9158
// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
9159
// 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.
9160
// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
9161
bool ImGui::BeginMenuBar()
9162
{
9163
    ImGuiWindow* window = GetCurrentWindow();
9164
    if (window->SkipItems)
9165
        return false;
9166
    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
9167
        return false;
9168
9169
    IM_ASSERT(!window->DC.MenuBarAppending);
9170
    BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
9171
    PushID("##MenuBar");
9172
9173
    // 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.
9174
    // 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.
9175
    const float border_top = ImMax(IM_ROUND(window->WindowBorderSize * 0.5f - window->TitleBarHeight), 0.0f);
9176
    const float border_half = IM_ROUND(window->WindowBorderSize * 0.5f);
9177
    ImRect bar_rect = window->MenuBarRect();
9178
    ImRect clip_rect(ImFloor(bar_rect.Min.x + border_half), ImFloor(bar_rect.Min.y + border_top), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - ImMax(window->WindowRounding, border_half))), ImFloor(bar_rect.Max.y));
9179
    clip_rect.ClipWith(window->OuterRectClipped);
9180
    PushClipRect(clip_rect.Min, clip_rect.Max, false);
9181
9182
    // 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).
9183
    window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
9184
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
9185
    window->DC.IsSameLine = false;
9186
    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
9187
    window->DC.MenuBarAppending = true;
9188
    AlignTextToFramePadding();
9189
    return true;
9190
}
9191
9192
void ImGui::EndMenuBar()
9193
{
9194
    ImGuiWindow* window = GetCurrentWindow();
9195
    if (window->SkipItems)
9196
        return;
9197
    ImGuiContext& g = *GImGui;
9198
9199
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
9200
    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
9201
    IM_ASSERT(window->DC.MenuBarAppending);
9202
9203
    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
9204
    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
9205
    {
9206
        // Try to find out if the request is for one of our child menu
9207
        ImGuiWindow* nav_earliest_child = g.NavWindow;
9208
        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
9209
            nav_earliest_child = nav_earliest_child->ParentWindow;
9210
        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
9211
        {
9212
            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
9213
            // 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)
9214
            const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
9215
            IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
9216
            FocusWindow(window);
9217
            SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
9218
            // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto?
9219
            if (g.NavCursorVisible)
9220
            {
9221
                g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again
9222
                g.NavCursorHideFrames = 2;
9223
            }
9224
            g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;
9225
            NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
9226
        }
9227
    }
9228
    else
9229
    {
9230
        NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_WrapX);
9231
    }
9232
9233
    PopClipRect();
9234
    PopID();
9235
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
9236
    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.
9237
9238
    // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.
9239
    ImGuiGroupData& group_data = g.GroupStack.back();
9240
    group_data.EmitItem = false;
9241
    ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
9242
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.
9243
    EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
9244
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
9245
    window->DC.IsSameLine = false;
9246
    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
9247
    window->DC.MenuBarAppending = false;
9248
    window->DC.CursorMaxPos = restore_cursor_max_pos;
9249
}
9250
9251
// Important: calling order matters!
9252
// FIXME: Somehow overlapping with docking tech.
9253
// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
9254
bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
9255
{
9256
    IM_ASSERT(dir != ImGuiDir_None);
9257
9258
    ImGuiWindow* bar_window = FindWindowByName(name);
9259
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
9260
    if (bar_window == NULL || bar_window->BeginCount == 0)
9261
    {
9262
        // Calculate and set window size/position
9263
        ImRect avail_rect = viewport->GetBuildWorkRect();
9264
        ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
9265
        ImVec2 pos = avail_rect.Min;
9266
        if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
9267
            pos[axis] = avail_rect.Max[axis] - axis_size;
9268
        ImVec2 size = avail_rect.GetSize();
9269
        size[axis] = axis_size;
9270
        SetNextWindowPos(pos);
9271
        SetNextWindowSize(size);
9272
9273
        // Report our size into work area (for next frame) using actual window size
9274
        if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
9275
            viewport->BuildWorkInsetMin[axis] += axis_size;
9276
        else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
9277
            viewport->BuildWorkInsetMax[axis] += axis_size;
9278
    }
9279
9280
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
9281
    SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
9282
    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
9283
    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
9284
    bool is_open = Begin(name, NULL, window_flags);
9285
    PopStyleVar(2);
9286
9287
    return is_open;
9288
}
9289
9290
bool ImGui::BeginMainMenuBar()
9291
{
9292
    ImGuiContext& g = *GImGui;
9293
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
9294
9295
    // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
9296
    SetCurrentViewport(NULL, viewport);
9297
9298
    // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
9299
    // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
9300
    // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
9301
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
9302
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
9303
    float height = GetFrameHeight();
9304
    bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
9305
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
9306
    if (!is_open)
9307
    {
9308
        End();
9309
        return false;
9310
    }
9311
9312
    // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356)
9313
    g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings;
9314
    BeginMenuBar();
9315
    return is_open;
9316
}
9317
9318
void ImGui::EndMainMenuBar()
9319
{
9320
    ImGuiContext& g = *GImGui;
9321
    IM_ASSERT_USER_ERROR_RET(g.CurrentWindow->DC.MenuBarAppending, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar
9322
9323
    EndMenuBar();
9324
    g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356)
9325
9326
    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
9327
    // FIXME: With this strategy we won't be able to restore a NULL focus.
9328
    if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0)
9329
        FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
9330
9331
    End();
9332
}
9333
9334
static bool IsRootOfOpenMenuSet()
9335
{
9336
    ImGuiContext& g = *GImGui;
9337
    ImGuiWindow* window = g.CurrentWindow;
9338
    if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
9339
        return false;
9340
9341
    // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
9342
    // (e.g. inside menu bar vs loose menu items) based on parent ID.
9343
    // This would however prevent the use of e.g. PushID() user code submitting menus.
9344
    // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
9345
    // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
9346
    // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
9347
    // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
9348
    // 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.
9349
    // 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
9350
    // 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
9351
    // it likely won't be a problem anyone runs into.
9352
    const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
9353
    if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
9354
        return false;
9355
    return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false);
9356
}
9357
9358
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
9359
{
9360
    ImGuiWindow* window = GetCurrentWindow();
9361
    if (window->SkipItems)
9362
        return false;
9363
9364
    ImGuiContext& g = *GImGui;
9365
    ImGuiStyle& style = g.Style;
9366
    const ImGuiID id = window->GetID(label);
9367
    bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
9368
9369
    // 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)
9370
    // 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.
9371
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
9372
    if (window->Flags & ImGuiWindowFlags_ChildMenu)
9373
        window_flags |= ImGuiWindowFlags_ChildWindow;
9374
9375
    // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
9376
    // 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.
9377
    // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
9378
    if (g.MenusIdSubmittedThisFrame.contains(id))
9379
    {
9380
        if (menu_is_open)
9381
            menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
9382
        else
9383
            g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
9384
        return menu_is_open;
9385
    }
9386
9387
    // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
9388
    g.MenusIdSubmittedThisFrame.push_back(id);
9389
9390
    const char* label_end = FindRenderedTextEnd(label);
9391
    ImVec2 label_size = CalcTextSize(label, label_end, false);
9392
9393
    // 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)
9394
    // This is only done for items for the menu set and not the full parent window.
9395
    const bool menuset_is_open = IsRootOfOpenMenuSet();
9396
    if (menuset_is_open)
9397
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
9398
9399
    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
9400
    // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
9401
    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
9402
    ImVec2 popup_pos;
9403
    ImVec2 pos = window->DC.CursorPos;
9404
    PushID(label);
9405
    if (!enabled)
9406
        BeginDisabled();
9407
9408
    bool pressed;
9409
9410
    const float backup_rounding = style.SelectableRounding;
9411
    style.SelectableRounding = style.MenuItemRounding;
9412
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoAutoClosePopups | (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnClick;
9413
    ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
9414
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
9415
    {
9416
        // Menu inside a horizontal menu bar
9417
        // Selectable extend their highlight by half ItemSpacing in each direction.
9418
        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
9419
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
9420
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
9421
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, pos.y + window->DC.CurrLineTextBaseOffset);
9422
        pressed = Selectable("", menu_is_open, selectable_flags, label_size);
9423
        LogSetNextTextDecoration("[", "]");
9424
        RenderText(text_pos, label, label_end, false);
9425
        PopStyleVar();
9426
        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().
9427
        popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), text_pos.y - style.FramePadding.y + window->MenuBarHeight);
9428
    }
9429
    else
9430
    {
9431
        // Menu inside a regular/vertical menu
9432
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
9433
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.)
9434
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
9435
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
9436
        float min_w = offsets->DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
9437
        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
9438
        ImVec2 text_pos(window->DC.CursorPos.x, pos.y + window->DC.CurrLineTextBaseOffset);
9439
        pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
9440
        LogSetNextTextDecoration("", ">");
9441
        RenderText(ImVec2(text_pos.x + offsets->OffsetLabel, text_pos.y), label, label_end, false);
9442
        if (icon_w > 0.0f)
9443
            RenderText(ImVec2(text_pos.x + offsets->OffsetIcon, text_pos.y), icon);
9444
        RenderArrow(window->DrawList, ImVec2(text_pos.x + offsets->OffsetMark + extra_w + g.FontSize * 0.30f, text_pos.y), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
9445
        popup_pos = ImVec2(pos.x, text_pos.y - style.WindowPadding.y);
9446
    }
9447
    style.SelectableRounding = backup_rounding;
9448
    if (!enabled)
9449
        EndDisabled();
9450
9451
    // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
9452
    // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
9453
    if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
9454
    {
9455
        ClearActiveID();
9456
        SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
9457
    }
9458
9459
    const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
9460
    if (menuset_is_open)
9461
        PopItemFlag();
9462
9463
    bool want_open = false;
9464
    bool want_open_nav_init = false;
9465
    bool want_close = false;
9466
    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
9467
    {
9468
        // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
9469
        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
9470
        bool moving_toward_child_menu = false;
9471
        ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
9472
        ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
9473
        if (g.HoveredWindow == window && child_menu_window != NULL)
9474
        {
9475
            const float ref_unit = g.FontSize; // FIXME-DPI
9476
            const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
9477
            const ImRect next_window_rect = child_menu_window->Rect();
9478
            ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
9479
            ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
9480
            ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
9481
            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.
9482
            ta.x += child_dir * -0.5f;
9483
            tb.x += child_dir * ref_unit;
9484
            tc.x += child_dir * ref_unit;
9485
            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
9486
            tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
9487
            moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
9488
            //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
9489
        }
9490
9491
        // 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)
9492
        // 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.
9493
        // (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.)
9494
        if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0)
9495
            want_close = true;
9496
9497
        // Open
9498
        // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
9499
        if (!menu_is_open && pressed) // Click/activate to open
9500
            want_open = true;
9501
        else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
9502
            want_open = true;
9503
        else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)
9504
            want_open = true;
9505
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
9506
        {
9507
            want_open = want_open_nav_init = true;
9508
            NavMoveRequestCancel();
9509
            SetNavCursorVisibleAfterMove();
9510
        }
9511
    }
9512
    else
9513
    {
9514
        // Menu bar
9515
        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
9516
        {
9517
            want_close = true;
9518
            want_open = menu_is_open = false;
9519
        }
9520
        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
9521
        {
9522
            want_open = true;
9523
        }
9524
        else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
9525
        {
9526
            want_open = true;
9527
            NavMoveRequestCancel();
9528
        }
9529
    }
9530
9531
    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.. }'
9532
        want_close = true;
9533
    if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
9534
        ClosePopupToLevel(g.BeginPopupStack.Size, true);
9535
9536
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
9537
    PopID();
9538
9539
    if (g.ActiveId == id && want_open)
9540
        g.ActiveIdNoClearOnFocusLoss = true;
9541
9542
    if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
9543
    {
9544
        // 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.
9545
        OpenPopup(label);
9546
    }
9547
    else if (want_open)
9548
    {
9549
        menu_is_open = true;
9550
        OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
9551
    }
9552
9553
    if (menu_is_open)
9554
    {
9555
        ImGuiLastItemData last_item_in_parent = g.LastItemData;
9556
        SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
9557
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
9558
        menu_is_open = BeginPopupMenuEx(id, label, window_flags); // menu_is_open may be 'false' when the popup is completely clipped (e.g. zero size display)
9559
        PopStyleVar();
9560
        if (menu_is_open)
9561
        {
9562
            // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
9563
            // Perform an init request in the case the popup was already open (via a previous mouse hover)
9564
            if (want_open && want_open_nav_init && !g.NavInitRequest)
9565
            {
9566
                FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
9567
                NavInitWindow(g.CurrentWindow, false);
9568
            }
9569
9570
            // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
9571
            // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
9572
            g.LastItemData = last_item_in_parent;
9573
            if (g.HoveredWindow == window)
9574
                g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
9575
        }
9576
    }
9577
    else
9578
    {
9579
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
9580
    }
9581
9582
    return menu_is_open;
9583
}
9584
9585
bool ImGui::BeginMenu(const char* label, bool enabled)
9586
{
9587
    return BeginMenuEx(label, NULL, enabled);
9588
}
9589
9590
void ImGui::EndMenu()
9591
{
9592
    // Nav: When a left move request our menu failed, close ourselves.
9593
    ImGuiContext& g = *GImGui;
9594
    ImGuiWindow* window = g.CurrentWindow;
9595
    IM_ASSERT_USER_ERROR_RET((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu), "Calling EndMenu() in wrong window!");
9596
9597
    ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.
9598
    if (window->BeginCount == window->BeginCountPreviousFrame)
9599
        if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
9600
            if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
9601
            {
9602
                ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
9603
                NavMoveRequestCancel();
9604
            }
9605
9606
    EndPopup();
9607
}
9608
9609
bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
9610
{
9611
    ImGuiWindow* window = GetCurrentWindow();
9612
    if (window->SkipItems)
9613
        return false;
9614
9615
    ImGuiContext& g = *GImGui;
9616
    ImGuiStyle& style = g.Style;
9617
    ImVec2 pos = window->DC.CursorPos;
9618
    const char* label_end = FindRenderedTextEnd(label);
9619
    ImVec2 label_size = CalcTextSize(label, label_end, false);
9620
9621
    // See BeginMenuEx() for comments about this.
9622
    const bool menuset_is_open = IsRootOfOpenMenuSet();
9623
    if (menuset_is_open)
9624
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
9625
9626
    // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
9627
    // 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.
9628
    bool pressed;
9629
    PushID(label);
9630
    if (!enabled)
9631
        BeginDisabled();
9632
9633
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
9634
    const float backup_rounding = style.SelectableRounding;
9635
    style.SelectableRounding = style.MenuItemRounding;
9636
    const ImGuiSelectableFlags selectable_flags = (ImGuiSelectableFlags)ImGuiSelectableFlags_SelectOnRelease | (ImGuiSelectableFlags)ImGuiSelectableFlags_SetNavIdOnHover;
9637
    ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
9638
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
9639
    {
9640
        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
9641
        // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
9642
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
9643
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
9644
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
9645
        pressed = Selectable("", selected, selectable_flags, ImVec2(label_size.x, 0.0f));
9646
        PopStyleVar();
9647
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
9648
            RenderText(text_pos, label, label_end, false);
9649
        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().
9650
    }
9651
    else
9652
    {
9653
        // Menu item inside a vertical menu
9654
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
9655
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.)
9656
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
9657
        float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
9658
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f); // FIXME: Always accounted for, even in a menu with none, because 'selected' has no neutral setting.
9659
        float min_w = offsets->DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
9660
        float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
9661
        ImVec2 text_pos(pos.x, pos.y + window->DC.CurrLineTextBaseOffset);
9662
        pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
9663
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
9664
        {
9665
            RenderText(text_pos + ImVec2(offsets->OffsetLabel, 0.0f), label, label_end, false);
9666
            if (icon_w > 0.0f)
9667
                RenderText(text_pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
9668
            if (shortcut_w > 0.0f)
9669
            {
9670
                PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
9671
                LogSetNextTextDecoration("(", ")");
9672
                RenderText(text_pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
9673
                PopStyleColor();
9674
            }
9675
            if (selected)
9676
                RenderCheckMark(window->DrawList, text_pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
9677
        }
9678
    }
9679
    style.SelectableRounding = backup_rounding;
9680
9681
    // Once dragged, release ActiveId + key ownership. This is to allow the idiom of mouse down a menu, dragging elsewhere, up on some other MenuItem(). (#8233, #9394)
9682
    // Could move logic into lower-level ImGuiButtonFlags_AutoReleaseActiveId + ImGuiButtonFlags_AutoReleaseKeyOwner? Easier once we get rid of the Selectable() middle-man here.
9683
    const ImGuiID id = g.LastItemData.ID;
9684
    if (g.ActiveId == id && g.HoveredId != id && g.ActiveIdSource == ImGuiInputSource_Mouse && IsMouseDragging(0))
9685
    {
9686
        ClearActiveID();
9687
        SetKeyOwner(ImGuiKey_MouseLeft, ImGuiKeyOwner_NoOwner);
9688
    }
9689
9690
9691
    IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
9692
    if (!enabled)
9693
        EndDisabled();
9694
    PopID();
9695
    if (menuset_is_open)
9696
        PopItemFlag();
9697
9698
    return pressed;
9699
}
9700
9701
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
9702
{
9703
    return MenuItemEx(label, NULL, shortcut, selected, enabled);
9704
}
9705
9706
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
9707
{
9708
    if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
9709
    {
9710
        if (p_selected)
9711
            *p_selected = !*p_selected;
9712
        return true;
9713
    }
9714
    return false;
9715
}
9716
9717
//-------------------------------------------------------------------------
9718
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
9719
//-------------------------------------------------------------------------
9720
// - BeginTabBar()
9721
// - BeginTabBarEx() [Internal]
9722
// - EndTabBar()
9723
// - TabBarLayout() [Internal]
9724
// - TabBarCalcTabID() [Internal]
9725
// - TabBarCalcMaxTabWidth() [Internal]
9726
// - TabBarFindTabById() [Internal]
9727
// - TabBarFindTabByOrder() [Internal]
9728
// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal]
9729
// - TabBarGetCurrentTab() [Internal]
9730
// - TabBarGetTabName() [Internal]
9731
// - TabBarAddTab() [Internal]
9732
// - TabBarRemoveTab() [Internal]
9733
// - TabBarCloseTab() [Internal]
9734
// - TabBarScrollClamp() [Internal]
9735
// - TabBarScrollToTab() [Internal]
9736
// - TabBarQueueFocus() [Internal]
9737
// - TabBarQueueReorder() [Internal]
9738
// - TabBarProcessReorderFromMousePos() [Internal]
9739
// - TabBarProcessReorder() [Internal]
9740
// - TabBarScrollingButtons() [Internal]
9741
// - TabBarTabListPopupButton() [Internal]
9742
//-------------------------------------------------------------------------
9743
9744
struct ImGuiTabBarSection
9745
{
9746
    int                 TabCount;               // Number of tabs in this section.
9747
    float               Width;                  // Sum of width of tabs in this section (after shrinking down)
9748
    float               WidthAfterShrinkMinWidth;
9749
    float               Spacing;                // Horizontal spacing at the end of the section.
9750
9751
    ImGuiTabBarSection() { memset((void*)this, 0, sizeof(*this)); }
9752
};
9753
9754
namespace ImGui
9755
{
9756
    static void             TabBarLayout(ImGuiTabBar* tab_bar);
9757
    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
9758
    static float            TabBarCalcMaxTabWidth();
9759
    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
9760
    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
9761
    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
9762
    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
9763
}
9764
9765
ImGuiTabBar::ImGuiTabBar()
9766
{
9767
    memset((void*)this, 0, sizeof(*this));
9768
    CurrFrameVisible = PrevFrameVisible = -1;
9769
    LastTabItemIdx = -1;
9770
}
9771
9772
static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
9773
{
9774
    return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
9775
}
9776
9777
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
9778
{
9779
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
9780
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
9781
    const int a_section = TabItemGetSectionIdx(a);
9782
    const int b_section = TabItemGetSectionIdx(b);
9783
    if (a_section != b_section)
9784
        return a_section - b_section;
9785
    return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
9786
}
9787
9788
static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
9789
{
9790
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
9791
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
9792
    return (int)(a->BeginOrder - b->BeginOrder);
9793
}
9794
9795
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
9796
{
9797
    ImGuiContext& g = *GImGui;
9798
    return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
9799
}
9800
9801
static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
9802
{
9803
    ImGuiContext& g = *GImGui;
9804
    if (g.TabBars.Contains(tab_bar))
9805
        return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
9806
    return ImGuiPtrOrIndex(tab_bar);
9807
}
9808
9809
ImGuiTabBar* ImGui::TabBarFindByID(ImGuiID id)
9810
{
9811
    ImGuiContext& g = *GImGui;
9812
    return g.TabBars.GetByKey(id);
9813
}
9814
9815
// Remove TabBar data (currently only used by TestEngine)
9816
void    ImGui::TabBarRemove(ImGuiTabBar* tab_bar)
9817
{
9818
    ImGuiContext& g = *GImGui;
9819
    g.TabBars.Remove(tab_bar->ID, tab_bar);
9820
}
9821
9822
bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
9823
{
9824
    ImGuiContext& g = *GImGui;
9825
    ImGuiWindow* window = g.CurrentWindow;
9826
    if (window->SkipItems)
9827
        return false;
9828
9829
    ImGuiID id = window->GetID(str_id);
9830
    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
9831
    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);
9832
    tab_bar->ID = id;
9833
    tab_bar->SeparatorMinX = tab_bar_bb.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
9834
    tab_bar->SeparatorMaxX = tab_bar_bb.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
9835
    //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))
9836
    flags |= ImGuiTabBarFlags_IsFocused;
9837
    return BeginTabBarEx(tab_bar, tab_bar_bb, flags);
9838
}
9839
9840
bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
9841
{
9842
    ImGuiContext& g = *GImGui;
9843
    ImGuiWindow* window = g.CurrentWindow;
9844
    if (window->SkipItems)
9845
        return false;
9846
9847
    IM_ASSERT(tab_bar->ID != 0);
9848
    if ((flags & ImGuiTabBarFlags_DockNode) == 0) // Already done
9849
        PushOverrideID(tab_bar->ID);
9850
9851
    // Add to stack
9852
    g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
9853
    g.CurrentTabBar = tab_bar;
9854
    tab_bar->Window = window;
9855
9856
    // Append with multiple BeginTabBar()/EndTabBar() pairs.
9857
    tab_bar->BackupCursorPos = window->DC.CursorPos;
9858
    if (tab_bar->CurrFrameVisible == g.FrameCount)
9859
    {
9860
        window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
9861
        tab_bar->BeginCount++;
9862
        return true;
9863
    }
9864
9865
    // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
9866
    if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
9867
        if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
9868
            ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
9869
    tab_bar->TabsAddedNew = false;
9870
9871
    // Flags
9872
    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
9873
        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
9874
9875
    tab_bar->Flags = flags;
9876
    tab_bar->BarRect = tab_bar_bb;
9877
    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
9878
    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
9879
    tab_bar->CurrFrameVisible = g.FrameCount;
9880
    tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
9881
    tab_bar->CurrTabsContentsHeight = 0.0f;
9882
    tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
9883
    tab_bar->FramePadding = g.Style.FramePadding;
9884
    tab_bar->TabsActiveCount = 0;
9885
    tab_bar->LastTabItemIdx = -1;
9886
    tab_bar->BeginCount = 1;
9887
9888
    // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
9889
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
9890
9891
    // Draw separator
9892
    // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)
9893
    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);
9894
    if (g.Style.TabBarBorderSize > 0.0f)
9895
    {
9896
        const float y = tab_bar->BarRect.Max.y;
9897
        window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);
9898
    }
9899
    return true;
9900
}
9901
9902
void    ImGui::EndTabBar()
9903
{
9904
    ImGuiContext& g = *GImGui;
9905
    ImGuiWindow* window = g.CurrentWindow;
9906
    if (window->SkipItems)
9907
        return;
9908
9909
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
9910
    IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
9911
9912
    // Fallback in case no TabItem have been submitted
9913
    if (tab_bar->WantLayout)
9914
        TabBarLayout(tab_bar);
9915
9916
    // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
9917
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
9918
    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
9919
    {
9920
        tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
9921
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
9922
    }
9923
    else
9924
    {
9925
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
9926
    }
9927
    if (tab_bar->BeginCount > 1)
9928
        window->DC.CursorPos = tab_bar->BackupCursorPos;
9929
9930
    tab_bar->LastTabItemIdx = -1;
9931
    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) // Already done
9932
        PopID();
9933
9934
    g.CurrentTabBarStack.pop_back();
9935
    g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
9936
}
9937
9938
// Scrolling happens only in the central section (leading/trailing sections are not scrolling)
9939
static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
9940
{
9941
    return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
9942
}
9943
9944
// This is called only once a frame before by the first call to ItemTab()
9945
// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
9946
static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
9947
{
9948
    ImGuiContext& g = *GImGui;
9949
    tab_bar->WantLayout = false;
9950
9951
    // Track selected tab when resizing our parent down
9952
    const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth());
9953
    tab_bar->BarRectPrevWidth = tab_bar->BarRect.GetWidth();
9954
9955
    // Garbage collect by compacting list
9956
    // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
9957
    int tab_dst_n = 0;
9958
    bool need_sort_by_section = false;
9959
    ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
9960
    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
9961
    {
9962
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
9963
        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
9964
        {
9965
            // Remove tab
9966
            if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
9967
            if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
9968
            if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
9969
            continue;
9970
        }
9971
        if (tab_dst_n != tab_src_n)
9972
            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
9973
9974
        tab = &tab_bar->Tabs[tab_dst_n];
9975
        tab->IndexDuringLayout = (ImS16)tab_dst_n;
9976
9977
        // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
9978
        int curr_tab_section_n = TabItemGetSectionIdx(tab);
9979
        if (tab_dst_n > 0)
9980
        {
9981
            ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
9982
            int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
9983
            if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
9984
                need_sort_by_section = true;
9985
            if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
9986
                need_sort_by_section = true;
9987
        }
9988
9989
        sections[curr_tab_section_n].TabCount++;
9990
        tab_dst_n++;
9991
    }
9992
    if (tab_bar->Tabs.Size != tab_dst_n)
9993
        tab_bar->Tabs.resize(tab_dst_n);
9994
9995
    if (need_sort_by_section)
9996
        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
9997
9998
    // Calculate spacing between sections
9999
    const float tab_spacing = g.Style.ItemInnerSpacing.x;
10000
    sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? tab_spacing : 0.0f;
10001
    sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? tab_spacing : 0.0f;
10002
10003
    // Setup next selected tab
10004
    ImGuiID scroll_to_tab_id = 0;
10005
    if (tab_bar->NextScrollToTabId)
10006
    {
10007
        scroll_to_tab_id = tab_bar->NextScrollToTabId;
10008
        tab_bar->NextScrollToTabId = 0;
10009
    }
10010
    if (tab_bar->NextSelectedTabId)
10011
    {
10012
        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
10013
        tab_bar->NextSelectedTabId = 0;
10014
        scroll_to_tab_id = tab_bar->SelectedTabId;
10015
    }
10016
10017
    // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
10018
    if (tab_bar->ReorderRequestTabId != 0)
10019
    {
10020
        if (TabBarProcessReorder(tab_bar))
10021
            if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
10022
                scroll_to_tab_id = tab_bar->ReorderRequestTabId;
10023
        tab_bar->ReorderRequestTabId = 0;
10024
    }
10025
10026
    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
10027
    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
10028
    if (tab_list_popup_button)
10029
        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
10030
            scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
10031
10032
    // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
10033
    // (whereas our tabs are stored as: leading, central, trailing)
10034
    int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
10035
    g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
10036
10037
    // Minimum shrink width
10038
    const float shrink_min_width = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed) ? g.Style.TabMinWidthShrink : 1.0f;
10039
10040
    // Compute ideal tabs widths + store them into shrink buffer
10041
    ImGuiTabItem* most_recently_selected_tab = NULL;
10042
    int curr_section_n = -1;
10043
    bool found_selected_tab_id = false;
10044
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
10045
    {
10046
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
10047
        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
10048
10049
        if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
10050
            most_recently_selected_tab = tab;
10051
        if (tab->ID == tab_bar->SelectedTabId)
10052
            found_selected_tab_id = true;
10053
        if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
10054
            scroll_to_tab_id = tab->ID;
10055
10056
        // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
10057
        // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
10058
        // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
10059
        const char* tab_name = TabBarGetTabName(tab_bar, tab);
10060
        const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
10061
        tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
10062
        if ((tab->Flags & ImGuiTabItemFlags_Button) == 0)
10063
            tab->ContentWidth = ImMax(tab->ContentWidth, g.Style.TabMinWidthBase);
10064
10065
        int section_n = TabItemGetSectionIdx(tab);
10066
        ImGuiTabBarSection* section = &sections[section_n];
10067
        section->Width += tab->ContentWidth + (section_n == curr_section_n ? tab_spacing : 0.0f);
10068
        section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? tab_spacing : 0.0f);
10069
        curr_section_n = section_n;
10070
10071
        // Store data so we can build an array sorted by width if we need to shrink tabs down
10072
        IM_MSVC_WARNING_SUPPRESS(6385);
10073
        ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
10074
        shrink_width_item->Index = tab_n;
10075
        shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
10076
        tab->Width = ImMax(tab->ContentWidth, 1.0f);
10077
    }
10078
10079
    // Compute total ideal width (used for e.g. auto-resizing a window)
10080
    float width_all_tabs_after_min_width_shrink = 0.0f;
10081
    tab_bar->WidthAllTabsIdeal = 0.0f;
10082
    for (int section_n = 0; section_n < 3; section_n++)
10083
    {
10084
        tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
10085
        width_all_tabs_after_min_width_shrink += sections[section_n].WidthAfterShrinkMinWidth + sections[section_n].Spacing;
10086
    }
10087
10088
    // Horizontal scrolling buttons
10089
    // Important: note that TabBarScrollButtons() will alter BarRect.Max.x.
10090
    const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
10091
    const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink;
10092
    tab_bar->ScrollButtonEnabled = ((width_all_tabs_to_use_for_scroll > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && can_scroll);
10093
    if (tab_bar->ScrollButtonEnabled)
10094
        if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
10095
        {
10096
            scroll_to_tab_id = scroll_and_select_tab->ID;
10097
            if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
10098
                tab_bar->SelectedTabId = scroll_to_tab_id;
10099
        }
10100
    if (scroll_to_tab_id == 0 && scroll_to_selected_tab)
10101
        scroll_to_tab_id = tab_bar->SelectedTabId;
10102
10103
    // Shrink widths if full tabs don't fit in their allocated space
10104
    float section_0_w = sections[0].Width + sections[0].Spacing;
10105
    float section_1_w = sections[1].Width + sections[1].Spacing;
10106
    float section_2_w = sections[2].Width + sections[2].Spacing;
10107
    bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
10108
    float width_excess;
10109
    if (central_section_is_visible)
10110
        width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
10111
    else
10112
        width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
10113
10114
    // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
10115
    const bool can_shrink = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
10116
    if (width_excess >= 1.0f && (can_shrink || !central_section_is_visible))
10117
    {
10118
        int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
10119
        int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
10120
        ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess, shrink_min_width);
10121
10122
        // Apply shrunk values into tabs and sections
10123
        for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
10124
        {
10125
            ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
10126
            float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
10127
            if (shrinked_width < 0.0f)
10128
                continue;
10129
10130
            shrinked_width = ImMax(1.0f, shrinked_width);
10131
            int section_n = TabItemGetSectionIdx(tab);
10132
            sections[section_n].Width -= (tab->Width - shrinked_width);
10133
            tab->Width = shrinked_width;
10134
        }
10135
    }
10136
10137
    // Layout all active tabs
10138
    int section_tab_index = 0;
10139
    float tab_offset = 0.0f;
10140
    tab_bar->WidthAllTabs = 0.0f;
10141
    for (int section_n = 0; section_n < 3; section_n++)
10142
    {
10143
        ImGuiTabBarSection* section = &sections[section_n];
10144
        if (section_n == 2)
10145
            tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
10146
10147
        for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
10148
        {
10149
            ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
10150
            tab->Offset = tab_offset;
10151
            tab->NameOffset = -1;
10152
            tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
10153
        }
10154
        tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
10155
        tab_offset += section->Spacing;
10156
        section_tab_index += section->TabCount;
10157
    }
10158
10159
    // Clear name buffers
10160
    tab_bar->TabsNames.Buf.resize(0);
10161
10162
    // If we have lost the selected tab, select the next most recently active one
10163
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
10164
    if (found_selected_tab_id == false && !tab_bar_appearing)
10165
        tab_bar->SelectedTabId = 0;
10166
    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
10167
        scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
10168
10169
    // Lock in visible tab
10170
    tab_bar->VisibleTabId = tab_bar->SelectedTabId;
10171
    tab_bar->VisibleTabWasSubmitted = false;
10172
10173
    // CTRL+TAB can override visible tab temporarily
10174
    if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
10175
        tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
10176
10177
    // Apply request requests
10178
    if (scroll_to_tab_id != 0)
10179
        TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
10180
    else if (tab_bar->ScrollButtonEnabled && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
10181
    {
10182
        const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
10183
        const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
10184
        if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
10185
        {
10186
            const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
10187
            tab_bar->ScrollingTargetDistToVisibility = 0.0f;
10188
            tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
10189
        }
10190
        SetKeyOwner(wheel_key, tab_bar->ID);
10191
    }
10192
10193
    // Update scrolling
10194
    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
10195
    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
10196
    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
10197
    {
10198
        // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
10199
        // Teleport if we are aiming far off the visible line
10200
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
10201
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
10202
        const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
10203
        tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
10204
    }
10205
    else
10206
    {
10207
        tab_bar->ScrollingSpeed = 0.0f;
10208
    }
10209
    tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
10210
    tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
10211
10212
    // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
10213
    ImGuiWindow* window = g.CurrentWindow;
10214
    window->DC.CursorPos = tab_bar->BarRect.Min;
10215
    ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
10216
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
10217
}
10218
10219
// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
10220
static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
10221
{
10222
    if (docked_window != NULL)
10223
    {
10224
        IM_UNUSED(tab_bar);
10225
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
10226
        ImGuiID id = docked_window->TabId;
10227
        KeepAliveID(id);
10228
        return id;
10229
    }
10230
    else
10231
    {
10232
        ImGuiWindow* window = GImGui->CurrentWindow;
10233
        return window->GetID(label);
10234
    }
10235
}
10236
10237
static float ImGui::TabBarCalcMaxTabWidth()
10238
{
10239
    ImGuiContext& g = *GImGui;
10240
    return g.FontSize * 20.0f;
10241
}
10242
10243
ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
10244
{
10245
    if (tab_id != 0)
10246
        for (int n = 0; n < tab_bar->Tabs.Size; n++)
10247
            if (tab_bar->Tabs[n].ID == tab_id)
10248
                return &tab_bar->Tabs[n];
10249
    return NULL;
10250
}
10251
10252
// Order = visible order, not submission order! (which is tab->BeginOrder)
10253
ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
10254
{
10255
    if (order < 0 || order >= tab_bar->Tabs.Size)
10256
        return NULL;
10257
    return &tab_bar->Tabs[order];
10258
}
10259
10260
// FIXME: See references to #2304 in TODO.txt
10261
ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
10262
{
10263
    ImGuiTabItem* most_recently_selected_tab = NULL;
10264
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
10265
    {
10266
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
10267
        if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
10268
            if (tab->Window && tab->Window->WasActive)
10269
                most_recently_selected_tab = tab;
10270
    }
10271
    return most_recently_selected_tab;
10272
}
10273
10274
ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
10275
{
10276
    if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
10277
        return NULL;
10278
    return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
10279
}
10280
10281
const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
10282
{
10283
    if (tab->Window)
10284
        return tab->Window->Name;
10285
    if (tab->NameOffset == -1)
10286
        return "N/A";
10287
    IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
10288
    return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
10289
}
10290
10291
// The purpose of this call is to register tab in advance so we can control their order at the time they appear.
10292
// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
10293
void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
10294
{
10295
    ImGuiContext& g = *GImGui;
10296
    IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
10297
    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)
10298
10299
    if (!window->HasCloseButton)
10300
        tab_flags |= ImGuiTabItemFlags_NoCloseButton;       // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
10301
10302
    ImGuiTabItem new_tab;
10303
    new_tab.ID = window->TabId;
10304
    new_tab.Flags = tab_flags;
10305
    new_tab.LastFrameVisible = tab_bar->CurrFrameVisible;   // Required so BeginTabBar() doesn't ditch the tab
10306
    if (new_tab.LastFrameVisible == -1)
10307
        new_tab.LastFrameVisible = g.FrameCount - 1;
10308
    new_tab.Window = window;                                // Required so tab bar layout can compute the tab width before tab submission
10309
    tab_bar->Tabs.push_back(new_tab);
10310
}
10311
10312
// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
10313
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
10314
{
10315
    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
10316
        tab_bar->Tabs.erase(tab);
10317
    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
10318
    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
10319
    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
10320
}
10321
10322
// Called on manual closure attempt
10323
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
10324
{
10325
    if (tab->Flags & ImGuiTabItemFlags_Button)
10326
        return; // A button appended with TabItemButton().
10327
10328
    if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
10329
    {
10330
        // This will remove a frame of lag for selecting another tab on closure.
10331
        // 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
10332
        tab->WantClose = true;
10333
        if (tab_bar->VisibleTabId == tab->ID)
10334
        {
10335
            tab->LastFrameVisible = -1;
10336
            tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
10337
        }
10338
    }
10339
    else
10340
    {
10341
        // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
10342
        if (tab_bar->VisibleTabId != tab->ID)
10343
            TabBarQueueFocus(tab_bar, tab);
10344
    }
10345
}
10346
10347
static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
10348
{
10349
    scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
10350
    return ImMax(scrolling, 0.0f);
10351
}
10352
10353
// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
10354
static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
10355
{
10356
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
10357
    if (tab == NULL)
10358
        return;
10359
    if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
10360
        return;
10361
10362
    ImGuiContext& g = *GImGui;
10363
    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)
10364
    int order = TabBarGetTabOrder(tab_bar, tab);
10365
10366
    // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
10367
    float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
10368
10369
    // We make all tabs positions all relative Sections[0].Width to make code simpler
10370
    float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
10371
    float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
10372
    tab_bar->ScrollingTargetDistToVisibility = 0.0f;
10373
    if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
10374
    {
10375
        // Scroll to the left
10376
        tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
10377
        tab_bar->ScrollingTarget = tab_x1;
10378
    }
10379
    else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
10380
    {
10381
        // Scroll to the right
10382
        tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
10383
        tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
10384
    }
10385
}
10386
10387
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
10388
{
10389
    tab_bar->NextSelectedTabId = tab->ID;
10390
}
10391
10392
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name)
10393
{
10394
    IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars
10395
    ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL);
10396
    tab_bar->NextSelectedTabId = tab_id;
10397
}
10398
10399
void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
10400
{
10401
    IM_ASSERT(offset != 0);
10402
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
10403
    tab_bar->ReorderRequestTabId = tab->ID;
10404
    tab_bar->ReorderRequestOffset = (ImS16)offset;
10405
}
10406
10407
void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
10408
{
10409
    ImGuiContext& g = *GImGui;
10410
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
10411
    if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
10412
        return;
10413
10414
    const float tab_spacing = g.Style.ItemInnerSpacing.x;
10415
    const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
10416
    const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
10417
10418
    // Count number of contiguous tabs we are crossing over
10419
    const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
10420
    const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
10421
    int dst_idx = src_idx;
10422
    for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
10423
    {
10424
        // Reordered tabs must share the same section
10425
        const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
10426
        if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
10427
            break;
10428
        if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
10429
            break;
10430
        dst_idx = i;
10431
10432
        // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
10433
        const float x1 = bar_offset + dst_tab->Offset - tab_spacing;
10434
        const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + tab_spacing;
10435
        //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
10436
        if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
10437
            break;
10438
    }
10439
10440
    if (dst_idx != src_idx)
10441
        TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
10442
}
10443
10444
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
10445
{
10446
    ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
10447
    if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
10448
        return false;
10449
10450
    //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
10451
    int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
10452
    if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
10453
        return false;
10454
10455
    // Reordered tabs must share the same section
10456
    // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
10457
    ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
10458
    if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
10459
        return false;
10460
    if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
10461
        return false;
10462
10463
    ImGuiTabItem item_tmp = *tab1;
10464
    ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
10465
    ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
10466
    const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
10467
    memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
10468
    *tab2 = item_tmp;
10469
10470
    if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
10471
        MarkIniSettingsDirty();
10472
    return true;
10473
}
10474
10475
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
10476
{
10477
    ImGuiContext& g = *GImGui;
10478
    ImGuiWindow* window = g.CurrentWindow;
10479
10480
    const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
10481
    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
10482
10483
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
10484
    //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));
10485
10486
    int select_dir = 0;
10487
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
10488
    arrow_col.w *= 0.5f;
10489
10490
    PushStyleColor(ImGuiCol_Text, arrow_col);
10491
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
10492
    PushItemFlag(ImGuiItemFlags_ButtonRepeat | ImGuiItemFlags_NoNav, true);
10493
    const float backup_repeat_delay = g.IO.KeyRepeatDelay;
10494
    const float backup_repeat_rate = g.IO.KeyRepeatRate;
10495
    g.IO.KeyRepeatDelay = 0.250f;
10496
    g.IO.KeyRepeatRate = 0.200f;
10497
    float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
10498
    window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
10499
    if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
10500
        select_dir = -1;
10501
    window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
10502
    if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
10503
        select_dir = +1;
10504
    PopItemFlag();
10505
    PopStyleColor(2);
10506
    g.IO.KeyRepeatRate = backup_repeat_rate;
10507
    g.IO.KeyRepeatDelay = backup_repeat_delay;
10508
10509
    ImGuiTabItem* tab_to_scroll_to = NULL;
10510
    if (select_dir != 0)
10511
        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
10512
        {
10513
            int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
10514
            int target_order = selected_order + select_dir;
10515
10516
            // Skip tab item buttons until another tab item is found or end is reached
10517
            while (tab_to_scroll_to == NULL)
10518
            {
10519
                // If we are at the end of the list, still scroll to make our tab visible
10520
                tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
10521
10522
                // Cross through buttons
10523
                // (even if first/last item is a button, return it so we can update the scroll)
10524
                if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
10525
                {
10526
                    target_order += select_dir;
10527
                    selected_order += select_dir;
10528
                    tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
10529
                }
10530
            }
10531
        }
10532
    window->DC.CursorPos = backup_cursor_pos;
10533
    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
10534
10535
    return tab_to_scroll_to;
10536
}
10537
10538
static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
10539
{
10540
    ImGuiContext& g = *GImGui;
10541
    ImGuiWindow* window = g.CurrentWindow;
10542
10543
    // We use g.Style.FramePadding.y to match the square ArrowButton size
10544
    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
10545
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
10546
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
10547
    tab_bar->BarRect.Min.x += tab_list_popup_button_width;
10548
10549
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
10550
    arrow_col.w *= 0.5f;
10551
    PushStyleColor(ImGuiCol_Text, arrow_col);
10552
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
10553
    bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
10554
    PopStyleColor(2);
10555
10556
    ImGuiTabItem* tab_to_select = NULL;
10557
    if (open)
10558
    {
10559
        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
10560
        {
10561
            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
10562
            if (tab->Flags & ImGuiTabItemFlags_Button)
10563
                continue;
10564
10565
            const char* tab_name = TabBarGetTabName(tab_bar, tab);
10566
            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
10567
                tab_to_select = tab;
10568
        }
10569
        EndCombo();
10570
    }
10571
10572
    window->DC.CursorPos = backup_cursor_pos;
10573
    return tab_to_select;
10574
}
10575
10576
//-------------------------------------------------------------------------
10577
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
10578
//-------------------------------------------------------------------------
10579
// - BeginTabItem()
10580
// - EndTabItem()
10581
// - TabItemButton()
10582
// - TabItemEx() [Internal]
10583
// - SetTabItemClosed()
10584
// - TabItemCalcSize() [Internal]
10585
// - TabItemBackground() [Internal]
10586
// - TabItemLabelAndCloseButton() [Internal]
10587
//-------------------------------------------------------------------------
10588
10589
bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
10590
{
10591
    ImGuiContext& g = *GImGui;
10592
    ImGuiWindow* window = g.CurrentWindow;
10593
    if (window->SkipItems)
10594
        return false;
10595
10596
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
10597
    IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!");
10598
    IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
10599
10600
    bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
10601
    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
10602
    {
10603
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
10604
        PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
10605
    }
10606
    return ret;
10607
}
10608
10609
void    ImGui::EndTabItem()
10610
{
10611
    ImGuiContext& g = *GImGui;
10612
    ImGuiWindow* window = g.CurrentWindow;
10613
    if (window->SkipItems)
10614
        return;
10615
10616
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
10617
    IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
10618
    IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
10619
    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
10620
    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
10621
        PopID();
10622
}
10623
10624
bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
10625
{
10626
    ImGuiContext& g = *GImGui;
10627
    ImGuiWindow* window = g.CurrentWindow;
10628
    if (window->SkipItems)
10629
        return false;
10630
10631
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
10632
    IM_ASSERT_USER_ERROR_RETV(tab_bar != NULL, false, "Needs to be called between BeginTabBar() and EndTabBar()!");
10633
    return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
10634
}
10635
10636
void    ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width)
10637
{
10638
    ImGuiContext& g = *GImGui;
10639
    ImGuiWindow* window = g.CurrentWindow;
10640
    if (window->SkipItems)
10641
        return;
10642
10643
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
10644
    IM_ASSERT_USER_ERROR_RET(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
10645
    SetNextItemWidth(width);
10646
    TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL);
10647
}
10648
10649
bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
10650
{
10651
    // Layout whole tab bar if not already done
10652
    ImGuiContext& g = *GImGui;
10653
    if (tab_bar->WantLayout)
10654
    {
10655
        ImGuiNextItemData backup_next_item_data = g.NextItemData;
10656
        TabBarLayout(tab_bar);
10657
        g.NextItemData = backup_next_item_data;
10658
    }
10659
    ImGuiWindow* window = g.CurrentWindow;
10660
    if (window->SkipItems)
10661
        return false;
10662
10663
    const ImGuiStyle& style = g.Style;
10664
    const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
10665
10666
    // If the user called us with *p_open == false, we early out and don't render.
10667
    // 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.
10668
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
10669
    if (p_open && !*p_open)
10670
    {
10671
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
10672
        return false;
10673
    }
10674
10675
    IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
10676
    IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
10677
10678
    // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
10679
    if (flags & ImGuiTabItemFlags_NoCloseButton)
10680
        p_open = NULL;
10681
    else if (p_open == NULL)
10682
        flags |= ImGuiTabItemFlags_NoCloseButton;
10683
10684
    // Acquire tab data
10685
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
10686
    bool tab_is_new = false;
10687
    if (tab == NULL)
10688
    {
10689
        tab_bar->Tabs.push_back(ImGuiTabItem());
10690
        tab = &tab_bar->Tabs.back();
10691
        tab->ID = id;
10692
        tab_bar->TabsAddedNew = tab_is_new = true;
10693
    }
10694
    tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
10695
10696
    // Calculate tab contents size
10697
    ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
10698
    tab->RequestedWidth = -1.0f;
10699
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)
10700
        size.x = tab->RequestedWidth = g.NextItemData.Width;
10701
    if (tab_is_new)
10702
        tab->Width = ImMax(1.0f, size.x);
10703
    tab->ContentWidth = size.x;
10704
    tab->BeginOrder = tab_bar->TabsActiveCount++;
10705
10706
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
10707
    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
10708
    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
10709
    const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
10710
    const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
10711
    tab->LastFrameVisible = g.FrameCount;
10712
    tab->Flags = flags;
10713
    tab->Window = docked_window;
10714
10715
    // Append name _WITH_ the zero-terminator
10716
    // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
10717
    if (docked_window != NULL)
10718
    {
10719
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
10720
        tab->NameOffset = -1;
10721
    }
10722
    else
10723
    {
10724
        tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
10725
        tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1);
10726
    }
10727
10728
    // Update selected tab
10729
    if (!is_tab_button)
10730
    {
10731
        if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
10732
            if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
10733
                TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
10734
        if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
10735
            TabBarQueueFocus(tab_bar, tab);
10736
    }
10737
10738
    // Lock visibility
10739
    // (Note: tab_contents_visible != tab_selected... because Ctrl+Tab operations may preview some tabs without selecting them!)
10740
    bool tab_contents_visible = (tab_bar->VisibleTabId == id);
10741
    if (tab_contents_visible)
10742
        tab_bar->VisibleTabWasSubmitted = true;
10743
10744
    // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
10745
    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
10746
        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
10747
            tab_contents_visible = true;
10748
10749
    // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
10750
    // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
10751
    if (tab_appearing && (!tab_bar_appearing || tab_is_new))
10752
    {
10753
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
10754
        if (is_tab_button)
10755
            return false;
10756
        return tab_contents_visible;
10757
    }
10758
10759
    if (tab_bar->SelectedTabId == id)
10760
        tab->LastFrameSelected = g.FrameCount;
10761
10762
    // Backup current layout position
10763
    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
10764
10765
    // Layout
10766
    const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
10767
    size.x = tab->Width;
10768
    if (is_central_section)
10769
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
10770
    else
10771
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
10772
    ImVec2 pos = window->DC.CursorPos;
10773
    ImRect bb(pos, pos + size);
10774
10775
    // 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)
10776
    const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
10777
    if (want_clip_rect)
10778
        PushClipRect(ImVec2(ImClamp(bb.Min.x, tab_bar->ScrollingRectMinX, tab_bar->ScrollingRectMaxX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
10779
10780
    ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
10781
    ItemSize(bb.GetSize(), style.FramePadding.y);
10782
    window->DC.CursorMaxPos = backup_cursor_max_pos;
10783
10784
    if (!ItemAdd(bb, id))
10785
    {
10786
        if (want_clip_rect)
10787
            PopClipRect();
10788
        window->DC.CursorPos = backup_main_cursor_pos;
10789
        return tab_contents_visible;
10790
    }
10791
10792
    // Click to Select a tab
10793
    ImGuiButtonFlags button_flags = ((ImGuiButtonFlags)(is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
10794
    if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
10795
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
10796
    bool hovered, held, pressed;
10797
    if (flags & ImGuiTabItemFlags_Invisible)
10798
        hovered = held = pressed = false;
10799
    else
10800
        pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
10801
    if (pressed && !is_tab_button)
10802
        TabBarQueueFocus(tab_bar, tab);
10803
10804
    // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
10805
    // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
10806
    if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
10807
        g.ActiveIdWindow = docked_window;
10808
10809
    // Drag and drop a single floating window node moves it
10810
    ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
10811
    const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
10812
    if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
10813
    {
10814
        // Move
10815
        StartMouseMovingWindow(docked_window);
10816
    }
10817
    else if (held && !tab_appearing && IsMouseDragging(0))
10818
    {
10819
        // Drag and drop: re-order tabs
10820
        int drag_dir = 0;
10821
        float drag_distance_from_edge_x = 0.0f;
10822
        if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
10823
        {
10824
            // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
10825
            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
10826
            {
10827
                drag_dir = -1;
10828
                drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
10829
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
10830
            }
10831
            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
10832
            {
10833
                drag_dir = +1;
10834
                drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
10835
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
10836
            }
10837
        }
10838
10839
        // Extract a Dockable window out of it's tab bar
10840
        const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking);
10841
        if (can_undock)
10842
        {
10843
            // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
10844
            bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
10845
            if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
10846
            {
10847
                float threshold_base = g.FontSize;
10848
                float threshold_x = (threshold_base * 2.2f);
10849
                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);
10850
                //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]
10851
10852
                float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
10853
                if (distance_from_edge_y >= threshold_y)
10854
                    undocking_tab = true;
10855
                if (drag_distance_from_edge_x > threshold_x)
10856
                    if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1))
10857
                        undocking_tab = true;
10858
            }
10859
10860
            if (undocking_tab)
10861
            {
10862
                // Undock
10863
                // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
10864
                DockContextQueueUndockWindow(&g, docked_window);
10865
                g.MovingWindow = docked_window;
10866
                SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
10867
                g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
10868
                g.ActiveIdNoClearOnFocusLoss = true;
10869
                SetActiveIdUsingAllKeyboardKeys();
10870
            }
10871
        }
10872
    }
10873
10874
#if 0
10875
    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
10876
    {
10877
        // Enlarge tab display when hovering
10878
        bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
10879
        display_draw_list = GetForegroundDrawList(window);
10880
        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
10881
    }
10882
#endif
10883
10884
    // Render tab shape
10885
    const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible);
10886
    if (is_visible)
10887
    {
10888
        ImDrawList* display_draw_list = window->DrawList;
10889
        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));
10890
        TabItemBackground(display_draw_list, bb, flags, tab_col);
10891
        if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)
10892
        {
10893
            // Might be moved to TabItemBackground() ?
10894
            ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale);
10895
            ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale);
10896
            ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline);
10897
            if (style.TabRounding > 0.0f)
10898
            {
10899
                float rounding = style.TabRounding;
10900
                display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9);
10901
                display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11);
10902
                display_draw_list->PathStroke(overline_col, style.TabBarOverlineSize);
10903
            }
10904
            else
10905
            {
10906
                display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize);
10907
            }
10908
        }
10909
        RenderNavCursor(bb, id);
10910
10911
        // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
10912
        const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
10913
        if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
10914
            TabBarQueueFocus(tab_bar, tab);
10915
10916
        if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
10917
            flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
10918
10919
        // Render tab label, process close button
10920
        const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
10921
        bool just_closed;
10922
        bool text_clipped;
10923
        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);
10924
        if (just_closed && p_open != NULL)
10925
        {
10926
            *p_open = false;
10927
            TabBarCloseTab(tab_bar, tab);
10928
        }
10929
10930
        // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
10931
        // That state is copied to window->DockTabItemStatusFlags by our caller.
10932
        if (docked_window && (hovered || g.HoveredId == close_button_id))
10933
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
10934
10935
        // Tooltip
10936
        // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
10937
        // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
10938
        // FIXME: This is a mess.
10939
        // FIXME: We may want disabled tab to still display the tooltip?
10940
        if (text_clipped && g.HoveredId == id && !held)
10941
            if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
10942
                SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
10943
    }
10944
10945
    // Restore main window position so user can draw there
10946
    if (want_clip_rect)
10947
        PopClipRect();
10948
    window->DC.CursorPos = backup_main_cursor_pos;
10949
10950
    IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
10951
    if (is_tab_button)
10952
        return pressed;
10953
    return tab_contents_visible;
10954
}
10955
10956
// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
10957
// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
10958
// Tabs closed by the close button will automatically be flagged to avoid this issue.
10959
void    ImGui::SetTabItemClosed(const char* label)
10960
{
10961
    ImGuiContext& g = *GImGui;
10962
    bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
10963
    if (is_within_manual_tab_bar)
10964
    {
10965
        ImGuiTabBar* tab_bar = g.CurrentTabBar;
10966
        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
10967
        if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
10968
            tab->WantClose = true; // Will be processed by next call to TabBarLayout()
10969
    }
10970
    else if (ImGuiWindow* window = FindWindowByName(label))
10971
    {
10972
        if (window->DockIsActive)
10973
            if (ImGuiDockNode* node = window->DockNode)
10974
            {
10975
                ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
10976
                TabBarRemoveTab(node->TabBar, tab_id);
10977
                window->DockTabWantClose = true;
10978
            }
10979
    }
10980
}
10981
10982
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
10983
{
10984
    ImGuiContext& g = *GImGui;
10985
    ImVec2 label_size = CalcTextSize(label, NULL, true);
10986
    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
10987
    if (has_close_button_or_unsaved_marker)
10988
        size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
10989
    else
10990
        size.x += g.Style.FramePadding.x + 1.0f;
10991
    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
10992
}
10993
10994
ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
10995
{
10996
    return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
10997
}
10998
10999
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
11000
{
11001
    // 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.
11002
    ImGuiContext& g = *GImGui;
11003
    const float width = bb.GetWidth();
11004
    IM_UNUSED(flags);
11005
    IM_ASSERT(width > 0.0f);
11006
    const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
11007
    const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars.
11008
    const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
11009
    draw_list->AddRectFilled(bb.Min, ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop);
11010
    if (g.Style.TabBorderSize > 0.0f)
11011
    {
11012
        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
11013
        draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
11014
        draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
11015
        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
11016
        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize);
11017
    }
11018
}
11019
11020
// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
11021
// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
11022
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)
11023
{
11024
    ImGuiContext& g = *GImGui;
11025
    const char* label_end = FindRenderedTextEnd(label);
11026
    ImVec2 label_size = CalcTextSize(label, label_end, false);
11027
11028
    if (out_just_closed)
11029
        *out_just_closed = false;
11030
    if (out_text_clipped)
11031
        *out_text_clipped = false;
11032
11033
    if (bb.GetWidth() <= 1.0f)
11034
        return;
11035
11036
    // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
11037
    // But right now if you want to alter text color of tabs this is what you need to do.
11038
#if 0
11039
    const float backup_alpha = g.Style.Alpha;
11040
    if (!is_contents_visible)
11041
        g.Style.Alpha *= 0.7f;
11042
#endif
11043
11044
    // Render text label (with clipping + alpha gradient) + unsaved marker
11045
    ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
11046
11047
    // Return clipped state ignoring the close button
11048
    if (out_text_clipped)
11049
    {
11050
        *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_ellipsis_clip_bb.Max.x;
11051
        //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));
11052
    }
11053
11054
    const float button_sz = g.FontSize;
11055
    const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
11056
11057
    // Close Button & Unsaved Marker
11058
    // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
11059
    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
11060
    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
11061
    //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
11062
    bool close_button_pressed = false;
11063
    bool close_button_visible = false;
11064
    bool is_hovered = g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == tab_id || g.ActiveId == close_button_id; // Any interaction account for this too.
11065
11066
    if (close_button_id != 0)
11067
    {
11068
        if (is_contents_visible)
11069
            close_button_visible = (g.Style.TabCloseButtonMinWidthSelected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthSelected));
11070
        else
11071
            close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected));
11072
    }
11073
11074
    // When tabs/document is unsaved, the unsaved marker takes priority over the close button.
11075
    const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered);
11076
    if (unsaved_marker_visible)
11077
    {
11078
        ImVec2 bullet_pos = button_pos + ImVec2(button_sz, button_sz) * 0.5f;
11079
        RenderBullet(draw_list, bullet_pos, GetColorU32(ImGuiCol_UnsavedMarker));
11080
    }
11081
    else if (close_button_visible)
11082
    {
11083
        ImGuiLastItemData last_item_backup = g.LastItemData;
11084
        if (CloseButton(close_button_id, button_pos))
11085
            close_button_pressed = true;
11086
        g.LastItemData = last_item_backup;
11087
11088
        // Close with middle mouse button
11089
        if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
11090
            close_button_pressed = true;
11091
    }
11092
11093
    // This is all rather complicated
11094
    // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
11095
    // 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..
11096
    float ellipsis_max_x = text_ellipsis_clip_bb.Max.x;
11097
    if (close_button_visible || unsaved_marker_visible)
11098
    {
11099
        const bool visible_without_hover = unsaved_marker_visible || (is_contents_visible ? g.Style.TabCloseButtonMinWidthSelected : g.Style.TabCloseButtonMinWidthUnselected) < 0.0f;
11100
        if (visible_without_hover)
11101
        {
11102
            text_ellipsis_clip_bb.Max.x -= button_sz * 0.90f;
11103
            ellipsis_max_x -= button_sz * 0.90f;
11104
        }
11105
        else
11106
        {
11107
            text_ellipsis_clip_bb.Max.x -= button_sz * 1.00f;
11108
        }
11109
    }
11110
    LogSetNextTextDecoration("/", "\\");
11111
    RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, label_end, &label_size);
11112
11113
#if 0
11114
    if (!is_contents_visible)
11115
        g.Style.Alpha = backup_alpha;
11116
#endif
11117
11118
    if (out_just_closed)
11119
        *out_just_closed = close_button_pressed;
11120
}
11121
11122
11123
#endif // #ifndef IMGUI_DISABLE