git repos / blockattack-game

blame: source/code/Libs/imgui/imgui_widgets.cpp

normal view · raw

4e9e2555 Poul Sander 2025-10-05 10:15 1
// dear imgui, v1.92.3
1193b4be Poul Sander 2025-02-03 21:28 2
// (widgets code)
1193b4be Poul Sander 2025-02-03 21:28 3
1193b4be Poul Sander 2025-02-03 21:28 4
/*
1193b4be Poul Sander 2025-02-03 21:28 5
1193b4be Poul Sander 2025-02-03 21:28 6
Index of this file:
1193b4be Poul Sander 2025-02-03 21:28 7
1193b4be Poul Sander 2025-02-03 21:28 8
// [SECTION] Forward Declarations
1193b4be Poul Sander 2025-02-03 21:28 9
// [SECTION] Widgets: Text, etc.
1193b4be Poul Sander 2025-02-03 21:28 10
// [SECTION] Widgets: Main (Button, Image, Checkbox, RadioButton, ProgressBar, Bullet, etc.)
1193b4be Poul Sander 2025-02-03 21:28 11
// [SECTION] Widgets: Low-level Layout helpers (Spacing, Dummy, NewLine, Separator, etc.)
1193b4be Poul Sander 2025-02-03 21:28 12
// [SECTION] Widgets: ComboBox
1193b4be Poul Sander 2025-02-03 21:28 13
// [SECTION] Data Type and Data Formatting Helpers
1193b4be Poul Sander 2025-02-03 21:28 14
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 15
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 16
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 17
// [SECTION] Widgets: InputText, InputTextMultiline
1193b4be Poul Sander 2025-02-03 21:28 18
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
1193b4be Poul Sander 2025-02-03 21:28 19
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
1193b4be Poul Sander 2025-02-03 21:28 20
// [SECTION] Widgets: Selectable
1193b4be Poul Sander 2025-02-03 21:28 21
// [SECTION] Widgets: Typing-Select support
1193b4be Poul Sander 2025-02-03 21:28 22
// [SECTION] Widgets: Box-Select support
1193b4be Poul Sander 2025-02-03 21:28 23
// [SECTION] Widgets: Multi-Select support
1193b4be Poul Sander 2025-02-03 21:28 24
// [SECTION] Widgets: Multi-Select helpers
1193b4be Poul Sander 2025-02-03 21:28 25
// [SECTION] Widgets: ListBox
1193b4be Poul Sander 2025-02-03 21:28 26
// [SECTION] Widgets: PlotLines, PlotHistogram
1193b4be Poul Sander 2025-02-03 21:28 27
// [SECTION] Widgets: Value helpers
1193b4be Poul Sander 2025-02-03 21:28 28
// [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc.
1193b4be Poul Sander 2025-02-03 21:28 29
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
1193b4be Poul Sander 2025-02-03 21:28 30
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
1193b4be Poul Sander 2025-02-03 21:28 31
// [SECTION] Widgets: Columns, BeginColumns, EndColumns, etc.
1193b4be Poul Sander 2025-02-03 21:28 32
1193b4be Poul Sander 2025-02-03 21:28 33
*/
1193b4be Poul Sander 2025-02-03 21:28 34
1193b4be Poul Sander 2025-02-03 21:28 35
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
1193b4be Poul Sander 2025-02-03 21:28 36
#define _CRT_SECURE_NO_WARNINGS
1193b4be Poul Sander 2025-02-03 21:28 37
#endif
1193b4be Poul Sander 2025-02-03 21:28 38
1193b4be Poul Sander 2025-02-03 21:28 39
#ifndef IMGUI_DEFINE_MATH_OPERATORS
1193b4be Poul Sander 2025-02-03 21:28 40
#define IMGUI_DEFINE_MATH_OPERATORS
1193b4be Poul Sander 2025-02-03 21:28 41
#endif
1193b4be Poul Sander 2025-02-03 21:28 42
1193b4be Poul Sander 2025-02-03 21:28 43
#include "imgui.h"
1193b4be Poul Sander 2025-02-03 21:28 44
#ifndef IMGUI_DISABLE
1193b4be Poul Sander 2025-02-03 21:28 45
#include "imgui_internal.h"
1193b4be Poul Sander 2025-02-03 21:28 46
1193b4be Poul Sander 2025-02-03 21:28 47
// System includes
1193b4be Poul Sander 2025-02-03 21:28 48
#include <stdint.h>     // intptr_t
1193b4be Poul Sander 2025-02-03 21:28 49
1193b4be Poul Sander 2025-02-03 21:28 50
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 51
// Warnings
1193b4be Poul Sander 2025-02-03 21:28 52
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 53
1193b4be Poul Sander 2025-02-03 21:28 54
// Visual Studio warnings
1193b4be Poul Sander 2025-02-03 21:28 55
#ifdef _MSC_VER
1193b4be Poul Sander 2025-02-03 21:28 56
#pragma warning (disable: 4127)     // condition expression is constant
1193b4be Poul Sander 2025-02-03 21:28 57
#pragma warning (disable: 4996)     // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
1193b4be Poul Sander 2025-02-03 21:28 58
#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
1193b4be Poul Sander 2025-02-03 21:28 59
#pragma warning (disable: 5054)     // operator '|': deprecated between enumerations of different types
1193b4be Poul Sander 2025-02-03 21:28 60
#endif
1193b4be Poul Sander 2025-02-03 21:28 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).
1193b4be Poul Sander 2025-02-03 21:28 62
#pragma warning (disable: 26812)    // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
1193b4be Poul Sander 2025-02-03 21:28 63
#endif
1193b4be Poul Sander 2025-02-03 21:28 64
1193b4be Poul Sander 2025-02-03 21:28 65
// Clang/GCC warnings with -Weverything
1193b4be Poul Sander 2025-02-03 21:28 66
#if defined(__clang__)
1193b4be Poul Sander 2025-02-03 21:28 67
#if __has_warning("-Wunknown-warning-option")
1193b4be Poul Sander 2025-02-03 21:28 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!
1193b4be Poul Sander 2025-02-03 21:28 69
#endif
1193b4be Poul Sander 2025-02-03 21:28 70
#pragma clang diagnostic ignored "-Wunknown-pragmas"                // warning: unknown warning group 'xxx'
1193b4be Poul Sander 2025-02-03 21:28 71
#pragma clang diagnostic ignored "-Wold-style-cast"                 // warning: use of old-style cast                            // yes, they are more terse.
1193b4be Poul Sander 2025-02-03 21:28 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.
4e9e2555 Poul Sander 2025-10-05 10:15 73
#pragma clang diagnostic ignored "-Wformat"                         // warning: format specifies type 'int' but the argument has type 'unsigned int'
1193b4be Poul Sander 2025-02-03 21:28 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.
1193b4be Poul Sander 2025-02-03 21:28 75
#pragma clang diagnostic ignored "-Wsign-conversion"                // warning: implicit conversion changes signedness
1193b4be Poul Sander 2025-02-03 21:28 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.
1193b4be Poul Sander 2025-02-03 21:28 77
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"  // warning: zero as null pointer constant                    // some standard header variations use #define NULL 0
1193b4be Poul Sander 2025-02-03 21:28 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.
1193b4be Poul Sander 2025-02-03 21:28 79
#pragma clang diagnostic ignored "-Wenum-enum-conversion"           // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_')
1193b4be Poul Sander 2025-02-03 21:28 80
#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
1193b4be Poul Sander 2025-02-03 21:28 81
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion"  // warning: implicit conversion from 'xxx' to 'float' may lose precision
1193b4be Poul Sander 2025-02-03 21:28 82
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"            // warning: 'xxx' is an unsafe pointer used for buffer access
4e9e2555 Poul Sander 2025-10-05 10:15 83
#pragma clang diagnostic ignored "-Wnontrivial-memaccess"           // warning: first argument in call to 'memset' is a pointer to non-trivially copyable type
4e9e2555 Poul Sander 2025-10-05 10:15 84
#pragma clang diagnostic ignored "-Wswitch-default"                 // warning: 'switch' missing 'default' label
1193b4be Poul Sander 2025-02-03 21:28 85
#elif defined(__GNUC__)
1193b4be Poul Sander 2025-02-03 21:28 86
#pragma GCC diagnostic ignored "-Wpragmas"                          // warning: unknown option after '#pragma GCC diagnostic' kind
4e9e2555 Poul Sander 2025-10-05 10:15 87
#pragma GCC diagnostic ignored "-Wfloat-equal"                      // warning: comparing floating-point with '==' or '!=' is unsafe
4e9e2555 Poul Sander 2025-10-05 10:15 88
#pragma GCC diagnostic ignored "-Wformat"                           // warning: format '%p' expects argument of type 'int'/'void*', but argument X has type 'unsigned int'/'ImGuiWindow*'
1193b4be Poul Sander 2025-02-03 21:28 89
#pragma GCC diagnostic ignored "-Wformat-nonliteral"                // warning: format not a string literal, format string not checked
1193b4be Poul Sander 2025-02-03 21:28 90
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"  // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated
4e9e2555 Poul Sander 2025-10-05 10:15 91
#pragma GCC diagnostic ignored "-Wdouble-promotion"                 // warning: implicit conversion from 'float' to 'double' when passing argument to function
4e9e2555 Poul Sander 2025-10-05 10:15 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
4e9e2555 Poul Sander 2025-10-05 10:15 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
4e9e2555 Poul Sander 2025-10-05 10:15 94
#pragma GCC diagnostic ignored "-Wcast-qual"                        // warning: cast from type 'const xxxx *' to type 'xxxx *' casts away qualifiers
1193b4be Poul Sander 2025-02-03 21:28 95
#endif
1193b4be Poul Sander 2025-02-03 21:28 96
1193b4be Poul Sander 2025-02-03 21:28 97
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 98
// Data
1193b4be Poul Sander 2025-02-03 21:28 99
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 100
1193b4be Poul Sander 2025-02-03 21:28 101
// Widgets
1193b4be Poul Sander 2025-02-03 21:28 102
static const float          DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;    // Time for drag-hold to activate items accepting the ImGuiButtonFlags_PressedOnDragDropHold button behavior.
1193b4be Poul Sander 2025-02-03 21:28 103
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.
1193b4be Poul Sander 2025-02-03 21:28 104
1193b4be Poul Sander 2025-02-03 21:28 105
// Those MIN/MAX values are not define because we need to point to them
1193b4be Poul Sander 2025-02-03 21:28 106
static const signed char    IM_S8_MIN  = -128;
1193b4be Poul Sander 2025-02-03 21:28 107
static const signed char    IM_S8_MAX  = 127;
1193b4be Poul Sander 2025-02-03 21:28 108
static const unsigned char  IM_U8_MIN  = 0;
1193b4be Poul Sander 2025-02-03 21:28 109
static const unsigned char  IM_U8_MAX  = 0xFF;
1193b4be Poul Sander 2025-02-03 21:28 110
static const signed short   IM_S16_MIN = -32768;
1193b4be Poul Sander 2025-02-03 21:28 111
static const signed short   IM_S16_MAX = 32767;
1193b4be Poul Sander 2025-02-03 21:28 112
static const unsigned short IM_U16_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 113
static const unsigned short IM_U16_MAX = 0xFFFF;
1193b4be Poul Sander 2025-02-03 21:28 114
static const ImS32          IM_S32_MIN = INT_MIN;    // (-2147483647 - 1), (0x80000000);
1193b4be Poul Sander 2025-02-03 21:28 115
static const ImS32          IM_S32_MAX = INT_MAX;    // (2147483647), (0x7FFFFFFF)
1193b4be Poul Sander 2025-02-03 21:28 116
static const ImU32          IM_U32_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 117
static const ImU32          IM_U32_MAX = UINT_MAX;   // (0xFFFFFFFF)
1193b4be Poul Sander 2025-02-03 21:28 118
#ifdef LLONG_MIN
1193b4be Poul Sander 2025-02-03 21:28 119
static const ImS64          IM_S64_MIN = LLONG_MIN;  // (-9223372036854775807ll - 1ll);
1193b4be Poul Sander 2025-02-03 21:28 120
static const ImS64          IM_S64_MAX = LLONG_MAX;  // (9223372036854775807ll);
1193b4be Poul Sander 2025-02-03 21:28 121
#else
1193b4be Poul Sander 2025-02-03 21:28 122
static const ImS64          IM_S64_MIN = -9223372036854775807LL - 1;
1193b4be Poul Sander 2025-02-03 21:28 123
static const ImS64          IM_S64_MAX = 9223372036854775807LL;
1193b4be Poul Sander 2025-02-03 21:28 124
#endif
1193b4be Poul Sander 2025-02-03 21:28 125
static const ImU64          IM_U64_MIN = 0;
1193b4be Poul Sander 2025-02-03 21:28 126
#ifdef ULLONG_MAX
1193b4be Poul Sander 2025-02-03 21:28 127
static const ImU64          IM_U64_MAX = ULLONG_MAX; // (0xFFFFFFFFFFFFFFFFull);
1193b4be Poul Sander 2025-02-03 21:28 128
#else
1193b4be Poul Sander 2025-02-03 21:28 129
static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
1193b4be Poul Sander 2025-02-03 21:28 130
#endif
1193b4be Poul Sander 2025-02-03 21:28 131
1193b4be Poul Sander 2025-02-03 21:28 132
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 133
// [SECTION] Forward Declarations
1193b4be Poul Sander 2025-02-03 21:28 134
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 135
1193b4be Poul Sander 2025-02-03 21:28 136
// For InputTextEx()
1193b4be Poul Sander 2025-02-03 21:28 137
static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
4e9e2555 Poul Sander 2025-10-05 10:15 138
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);
1193b4be Poul Sander 2025-02-03 21:28 139
1193b4be Poul Sander 2025-02-03 21:28 140
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 141
// [SECTION] Widgets: Text, etc.
1193b4be Poul Sander 2025-02-03 21:28 142
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 143
// - TextEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 144
// - TextUnformatted()
1193b4be Poul Sander 2025-02-03 21:28 145
// - Text()
1193b4be Poul Sander 2025-02-03 21:28 146
// - TextV()
1193b4be Poul Sander 2025-02-03 21:28 147
// - TextColored()
1193b4be Poul Sander 2025-02-03 21:28 148
// - TextColoredV()
1193b4be Poul Sander 2025-02-03 21:28 149
// - TextDisabled()
1193b4be Poul Sander 2025-02-03 21:28 150
// - TextDisabledV()
1193b4be Poul Sander 2025-02-03 21:28 151
// - TextWrapped()
1193b4be Poul Sander 2025-02-03 21:28 152
// - TextWrappedV()
1193b4be Poul Sander 2025-02-03 21:28 153
// - LabelText()
1193b4be Poul Sander 2025-02-03 21:28 154
// - LabelTextV()
1193b4be Poul Sander 2025-02-03 21:28 155
// - BulletText()
1193b4be Poul Sander 2025-02-03 21:28 156
// - BulletTextV()
1193b4be Poul Sander 2025-02-03 21:28 157
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 158
1193b4be Poul Sander 2025-02-03 21:28 159
void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 160
{
1193b4be Poul Sander 2025-02-03 21:28 161
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 162
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 163
        return;
1193b4be Poul Sander 2025-02-03 21:28 164
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 165
1193b4be Poul Sander 2025-02-03 21:28 166
    // Accept null ranges
1193b4be Poul Sander 2025-02-03 21:28 167
    if (text == text_end)
1193b4be Poul Sander 2025-02-03 21:28 168
        text = text_end = "";
1193b4be Poul Sander 2025-02-03 21:28 169
1193b4be Poul Sander 2025-02-03 21:28 170
    // Calculate length
1193b4be Poul Sander 2025-02-03 21:28 171
    const char* text_begin = text;
1193b4be Poul Sander 2025-02-03 21:28 172
    if (text_end == NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 173
        text_end = text + ImStrlen(text); // FIXME-OPT
1193b4be Poul Sander 2025-02-03 21:28 174
1193b4be Poul Sander 2025-02-03 21:28 175
    const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 176
    const float wrap_pos_x = window->DC.TextWrapPos;
1193b4be Poul Sander 2025-02-03 21:28 177
    const bool wrap_enabled = (wrap_pos_x >= 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 178
    if (text_end - text <= 2000 || wrap_enabled)
1193b4be Poul Sander 2025-02-03 21:28 179
    {
1193b4be Poul Sander 2025-02-03 21:28 180
        // Common case
1193b4be Poul Sander 2025-02-03 21:28 181
        const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 182
        const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
1193b4be Poul Sander 2025-02-03 21:28 183
1193b4be Poul Sander 2025-02-03 21:28 184
        ImRect bb(text_pos, text_pos + text_size);
1193b4be Poul Sander 2025-02-03 21:28 185
        ItemSize(text_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 186
        if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 187
            return;
1193b4be Poul Sander 2025-02-03 21:28 188
1193b4be Poul Sander 2025-02-03 21:28 189
        // Render (we don't hide text after ## in this end-user function)
1193b4be Poul Sander 2025-02-03 21:28 190
        RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
1193b4be Poul Sander 2025-02-03 21:28 191
    }
1193b4be Poul Sander 2025-02-03 21:28 192
    else
1193b4be Poul Sander 2025-02-03 21:28 193
    {
1193b4be Poul Sander 2025-02-03 21:28 194
        // Long text!
1193b4be Poul Sander 2025-02-03 21:28 195
        // Perform manual coarse clipping to optimize for long multi-line text
1193b4be Poul Sander 2025-02-03 21:28 196
        // - From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
1193b4be Poul Sander 2025-02-03 21:28 197
        // - 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.
1193b4be Poul Sander 2025-02-03 21:28 198
        // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster than a casually written loop.
1193b4be Poul Sander 2025-02-03 21:28 199
        const char* line = text;
1193b4be Poul Sander 2025-02-03 21:28 200
        const float line_height = GetTextLineHeight();
1193b4be Poul Sander 2025-02-03 21:28 201
        ImVec2 text_size(0, 0);
1193b4be Poul Sander 2025-02-03 21:28 202
1193b4be Poul Sander 2025-02-03 21:28 203
        // Lines to skip (can't skip when logging text)
1193b4be Poul Sander 2025-02-03 21:28 204
        ImVec2 pos = text_pos;
1193b4be Poul Sander 2025-02-03 21:28 205
        if (!g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 206
        {
1193b4be Poul Sander 2025-02-03 21:28 207
            int lines_skippable = (int)((window->ClipRect.Min.y - text_pos.y) / line_height);
1193b4be Poul Sander 2025-02-03 21:28 208
            if (lines_skippable > 0)
1193b4be Poul Sander 2025-02-03 21:28 209
            {
1193b4be Poul Sander 2025-02-03 21:28 210
                int lines_skipped = 0;
1193b4be Poul Sander 2025-02-03 21:28 211
                while (line < text_end && lines_skipped < lines_skippable)
1193b4be Poul Sander 2025-02-03 21:28 212
                {
4e9e2555 Poul Sander 2025-10-05 10:15 213
                    const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 214
                    if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 215
                        line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 216
                    if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
1193b4be Poul Sander 2025-02-03 21:28 217
                        text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 218
                    line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 219
                    lines_skipped++;
1193b4be Poul Sander 2025-02-03 21:28 220
                }
1193b4be Poul Sander 2025-02-03 21:28 221
                pos.y += lines_skipped * line_height;
1193b4be Poul Sander 2025-02-03 21:28 222
            }
1193b4be Poul Sander 2025-02-03 21:28 223
        }
1193b4be Poul Sander 2025-02-03 21:28 224
1193b4be Poul Sander 2025-02-03 21:28 225
        // Lines to render
1193b4be Poul Sander 2025-02-03 21:28 226
        if (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 227
        {
1193b4be Poul Sander 2025-02-03 21:28 228
            ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
1193b4be Poul Sander 2025-02-03 21:28 229
            while (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 230
            {
1193b4be Poul Sander 2025-02-03 21:28 231
                if (IsClippedEx(line_rect, 0))
1193b4be Poul Sander 2025-02-03 21:28 232
                    break;
1193b4be Poul Sander 2025-02-03 21:28 233
4e9e2555 Poul Sander 2025-10-05 10:15 234
                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 235
                if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 236
                    line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 237
                text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 238
                RenderText(pos, line, line_end, false);
1193b4be Poul Sander 2025-02-03 21:28 239
                line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 240
                line_rect.Min.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 241
                line_rect.Max.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 242
                pos.y += line_height;
1193b4be Poul Sander 2025-02-03 21:28 243
            }
1193b4be Poul Sander 2025-02-03 21:28 244
1193b4be Poul Sander 2025-02-03 21:28 245
            // Count remaining lines
1193b4be Poul Sander 2025-02-03 21:28 246
            int lines_skipped = 0;
1193b4be Poul Sander 2025-02-03 21:28 247
            while (line < text_end)
1193b4be Poul Sander 2025-02-03 21:28 248
            {
4e9e2555 Poul Sander 2025-10-05 10:15 249
                const char* line_end = (const char*)ImMemchr(line, '\n', text_end - line);
1193b4be Poul Sander 2025-02-03 21:28 250
                if (!line_end)
1193b4be Poul Sander 2025-02-03 21:28 251
                    line_end = text_end;
1193b4be Poul Sander 2025-02-03 21:28 252
                if ((flags & ImGuiTextFlags_NoWidthForLargeClippedText) == 0)
1193b4be Poul Sander 2025-02-03 21:28 253
                    text_size.x = ImMax(text_size.x, CalcTextSize(line, line_end).x);
1193b4be Poul Sander 2025-02-03 21:28 254
                line = line_end + 1;
1193b4be Poul Sander 2025-02-03 21:28 255
                lines_skipped++;
1193b4be Poul Sander 2025-02-03 21:28 256
            }
1193b4be Poul Sander 2025-02-03 21:28 257
            pos.y += lines_skipped * line_height;
1193b4be Poul Sander 2025-02-03 21:28 258
        }
1193b4be Poul Sander 2025-02-03 21:28 259
        text_size.y = (pos - text_pos).y;
1193b4be Poul Sander 2025-02-03 21:28 260
1193b4be Poul Sander 2025-02-03 21:28 261
        ImRect bb(text_pos, text_pos + text_size);
1193b4be Poul Sander 2025-02-03 21:28 262
        ItemSize(text_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 263
        ItemAdd(bb, 0);
1193b4be Poul Sander 2025-02-03 21:28 264
    }
1193b4be Poul Sander 2025-02-03 21:28 265
}
1193b4be Poul Sander 2025-02-03 21:28 266
1193b4be Poul Sander 2025-02-03 21:28 267
void ImGui::TextUnformatted(const char* text, const char* text_end)
1193b4be Poul Sander 2025-02-03 21:28 268
{
1193b4be Poul Sander 2025-02-03 21:28 269
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
1193b4be Poul Sander 2025-02-03 21:28 270
}
1193b4be Poul Sander 2025-02-03 21:28 271
1193b4be Poul Sander 2025-02-03 21:28 272
void ImGui::Text(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 273
{
1193b4be Poul Sander 2025-02-03 21:28 274
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 275
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 276
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 277
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 278
}
1193b4be Poul Sander 2025-02-03 21:28 279
1193b4be Poul Sander 2025-02-03 21:28 280
void ImGui::TextV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 281
{
1193b4be Poul Sander 2025-02-03 21:28 282
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 283
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 284
        return;
1193b4be Poul Sander 2025-02-03 21:28 285
1193b4be Poul Sander 2025-02-03 21:28 286
    const char* text, *text_end;
1193b4be Poul Sander 2025-02-03 21:28 287
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 288
    TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText);
1193b4be Poul Sander 2025-02-03 21:28 289
}
1193b4be Poul Sander 2025-02-03 21:28 290
1193b4be Poul Sander 2025-02-03 21:28 291
void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 292
{
1193b4be Poul Sander 2025-02-03 21:28 293
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 294
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 295
    TextColoredV(col, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 296
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 297
}
1193b4be Poul Sander 2025-02-03 21:28 298
1193b4be Poul Sander 2025-02-03 21:28 299
void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 300
{
1193b4be Poul Sander 2025-02-03 21:28 301
    PushStyleColor(ImGuiCol_Text, col);
1193b4be Poul Sander 2025-02-03 21:28 302
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 303
    PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 304
}
1193b4be Poul Sander 2025-02-03 21:28 305
1193b4be Poul Sander 2025-02-03 21:28 306
void ImGui::TextDisabled(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 307
{
1193b4be Poul Sander 2025-02-03 21:28 308
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 309
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 310
    TextDisabledV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 311
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 312
}
1193b4be Poul Sander 2025-02-03 21:28 313
1193b4be Poul Sander 2025-02-03 21:28 314
void ImGui::TextDisabledV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 315
{
1193b4be Poul Sander 2025-02-03 21:28 316
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 317
    PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
1193b4be Poul Sander 2025-02-03 21:28 318
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 319
    PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 320
}
1193b4be Poul Sander 2025-02-03 21:28 321
1193b4be Poul Sander 2025-02-03 21:28 322
void ImGui::TextWrapped(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 323
{
1193b4be Poul Sander 2025-02-03 21:28 324
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 325
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 326
    TextWrappedV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 327
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 328
}
1193b4be Poul Sander 2025-02-03 21:28 329
1193b4be Poul Sander 2025-02-03 21:28 330
void ImGui::TextWrappedV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 331
{
1193b4be Poul Sander 2025-02-03 21:28 332
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 333
    const bool need_backup = (g.CurrentWindow->DC.TextWrapPos < 0.0f);  // Keep existing wrap position if one is already set
1193b4be Poul Sander 2025-02-03 21:28 334
    if (need_backup)
1193b4be Poul Sander 2025-02-03 21:28 335
        PushTextWrapPos(0.0f);
1193b4be Poul Sander 2025-02-03 21:28 336
    TextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 337
    if (need_backup)
1193b4be Poul Sander 2025-02-03 21:28 338
        PopTextWrapPos();
1193b4be Poul Sander 2025-02-03 21:28 339
}
1193b4be Poul Sander 2025-02-03 21:28 340
4e9e2555 Poul Sander 2025-10-05 10:15 341
void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...)
4e9e2555 Poul Sander 2025-10-05 10:15 342
{
4e9e2555 Poul Sander 2025-10-05 10:15 343
    va_list args;
4e9e2555 Poul Sander 2025-10-05 10:15 344
    va_start(args, fmt);
4e9e2555 Poul Sander 2025-10-05 10:15 345
    TextAlignedV(align_x, size_x, fmt, args);
4e9e2555 Poul Sander 2025-10-05 10:15 346
    va_end(args);
4e9e2555 Poul Sander 2025-10-05 10:15 347
}
4e9e2555 Poul Sander 2025-10-05 10:15 348
4e9e2555 Poul Sander 2025-10-05 10:15 349
// align_x: 0.0f = left, 0.5f = center, 1.0f = right.
4e9e2555 Poul Sander 2025-10-05 10:15 350
// size_x : 0.0f = shortcut for GetContentRegionAvail().x
4e9e2555 Poul Sander 2025-10-05 10:15 351
// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024)
4e9e2555 Poul Sander 2025-10-05 10:15 352
void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args)
4e9e2555 Poul Sander 2025-10-05 10:15 353
{
4e9e2555 Poul Sander 2025-10-05 10:15 354
    ImGuiWindow* window = GetCurrentWindow();
4e9e2555 Poul Sander 2025-10-05 10:15 355
    if (window->SkipItems)
4e9e2555 Poul Sander 2025-10-05 10:15 356
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 357
4e9e2555 Poul Sander 2025-10-05 10:15 358
    const char* text, *text_end;
4e9e2555 Poul Sander 2025-10-05 10:15 359
    ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
4e9e2555 Poul Sander 2025-10-05 10:15 360
    const ImVec2 text_size = CalcTextSize(text, text_end);
4e9e2555 Poul Sander 2025-10-05 10:15 361
    size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x;
4e9e2555 Poul Sander 2025-10-05 10:15 362
4e9e2555 Poul Sander 2025-10-05 10:15 363
    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
4e9e2555 Poul Sander 2025-10-05 10:15 364
    ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y);
4e9e2555 Poul Sander 2025-10-05 10:15 365
    ImVec2 size(ImMin(size_x, text_size.x), text_size.y);
4e9e2555 Poul Sander 2025-10-05 10:15 366
    window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x);
4e9e2555 Poul Sander 2025-10-05 10:15 367
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x);
4e9e2555 Poul Sander 2025-10-05 10:15 368
    if (align_x > 0.0f && text_size.x < size_x)
4e9e2555 Poul Sander 2025-10-05 10:15 369
        pos.x += ImTrunc((size_x - text_size.x) * align_x);
4e9e2555 Poul Sander 2025-10-05 10:15 370
    RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size);
4e9e2555 Poul Sander 2025-10-05 10:15 371
4e9e2555 Poul Sander 2025-10-05 10:15 372
    const ImVec2 backup_max_pos = window->DC.CursorMaxPos;
4e9e2555 Poul Sander 2025-10-05 10:15 373
    ItemSize(size);
4e9e2555 Poul Sander 2025-10-05 10:15 374
    ItemAdd(ImRect(pos, pos + size), 0);
4e9e2555 Poul Sander 2025-10-05 10:15 375
    window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up.
4e9e2555 Poul Sander 2025-10-05 10:15 376
4e9e2555 Poul Sander 2025-10-05 10:15 377
    if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip))
4e9e2555 Poul Sander 2025-10-05 10:15 378
        SetTooltip("%.*s", (int)(text_end - text), text);
4e9e2555 Poul Sander 2025-10-05 10:15 379
}
4e9e2555 Poul Sander 2025-10-05 10:15 380
1193b4be Poul Sander 2025-02-03 21:28 381
void ImGui::LabelText(const char* label, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 382
{
1193b4be Poul Sander 2025-02-03 21:28 383
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 384
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 385
    LabelTextV(label, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 386
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 387
}
1193b4be Poul Sander 2025-02-03 21:28 388
1193b4be Poul Sander 2025-02-03 21:28 389
// Add a label+text combo aligned to other label+value widgets
1193b4be Poul Sander 2025-02-03 21:28 390
void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 391
{
1193b4be Poul Sander 2025-02-03 21:28 392
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 393
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 394
        return;
1193b4be Poul Sander 2025-02-03 21:28 395
1193b4be Poul Sander 2025-02-03 21:28 396
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 397
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 398
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 399
1193b4be Poul Sander 2025-02-03 21:28 400
    const char* value_text_begin, *value_text_end;
1193b4be Poul Sander 2025-02-03 21:28 401
    ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 402
    const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false);
1193b4be Poul Sander 2025-02-03 21:28 403
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 404
1193b4be Poul Sander 2025-02-03 21:28 405
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 406
    const ImRect value_bb(pos, pos + ImVec2(w, value_size.y + style.FramePadding.y * 2));
1193b4be Poul Sander 2025-02-03 21:28 407
    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));
1193b4be Poul Sander 2025-02-03 21:28 408
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 409
    if (!ItemAdd(total_bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 410
        return;
1193b4be Poul Sander 2025-02-03 21:28 411
1193b4be Poul Sander 2025-02-03 21:28 412
    // Render
1193b4be Poul Sander 2025-02-03 21:28 413
    RenderTextClipped(value_bb.Min + style.FramePadding, value_bb.Max, value_text_begin, value_text_end, &value_size, ImVec2(0.0f, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 414
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 415
        RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 416
}
1193b4be Poul Sander 2025-02-03 21:28 417
1193b4be Poul Sander 2025-02-03 21:28 418
void ImGui::BulletText(const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 419
{
1193b4be Poul Sander 2025-02-03 21:28 420
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 421
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 422
    BulletTextV(fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 423
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 424
}
1193b4be Poul Sander 2025-02-03 21:28 425
1193b4be Poul Sander 2025-02-03 21:28 426
// Text with a little bullet aligned to the typical tree node.
1193b4be Poul Sander 2025-02-03 21:28 427
void ImGui::BulletTextV(const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 428
{
1193b4be Poul Sander 2025-02-03 21:28 429
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 430
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 431
        return;
1193b4be Poul Sander 2025-02-03 21:28 432
1193b4be Poul Sander 2025-02-03 21:28 433
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 434
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 435
1193b4be Poul Sander 2025-02-03 21:28 436
    const char* text_begin, *text_end;
1193b4be Poul Sander 2025-02-03 21:28 437
    ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 438
    const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
1193b4be Poul Sander 2025-02-03 21:28 439
    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
1193b4be Poul Sander 2025-02-03 21:28 440
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 441
    pos.y += window->DC.CurrLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 442
    ItemSize(total_size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 443
    const ImRect bb(pos, pos + total_size);
1193b4be Poul Sander 2025-02-03 21:28 444
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 445
        return;
1193b4be Poul Sander 2025-02-03 21:28 446
1193b4be Poul Sander 2025-02-03 21:28 447
    // Render
1193b4be Poul Sander 2025-02-03 21:28 448
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 449
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, g.FontSize * 0.5f), text_col);
1193b4be Poul Sander 2025-02-03 21:28 450
    RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, 0.0f), text_begin, text_end, false);
1193b4be Poul Sander 2025-02-03 21:28 451
}
1193b4be Poul Sander 2025-02-03 21:28 452
1193b4be Poul Sander 2025-02-03 21:28 453
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 454
// [SECTION] Widgets: Main
1193b4be Poul Sander 2025-02-03 21:28 455
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 456
// - ButtonBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 457
// - Button()
1193b4be Poul Sander 2025-02-03 21:28 458
// - SmallButton()
1193b4be Poul Sander 2025-02-03 21:28 459
// - InvisibleButton()
1193b4be Poul Sander 2025-02-03 21:28 460
// - ArrowButton()
1193b4be Poul Sander 2025-02-03 21:28 461
// - CloseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 462
// - CollapseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 463
// - GetWindowScrollbarID() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 464
// - GetWindowScrollbarRect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 465
// - Scrollbar() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 466
// - ScrollbarEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 467
// - Image()
1193b4be Poul Sander 2025-02-03 21:28 468
// - ImageButton()
1193b4be Poul Sander 2025-02-03 21:28 469
// - Checkbox()
1193b4be Poul Sander 2025-02-03 21:28 470
// - CheckboxFlagsT() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 471
// - CheckboxFlags()
1193b4be Poul Sander 2025-02-03 21:28 472
// - RadioButton()
1193b4be Poul Sander 2025-02-03 21:28 473
// - ProgressBar()
1193b4be Poul Sander 2025-02-03 21:28 474
// - Bullet()
1193b4be Poul Sander 2025-02-03 21:28 475
// - Hyperlink()
1193b4be Poul Sander 2025-02-03 21:28 476
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 477
1193b4be Poul Sander 2025-02-03 21:28 478
// The ButtonBehavior() function is key to many interactions and used by many/most widgets.
1193b4be Poul Sander 2025-02-03 21:28 479
// Because we handle so many cases (keyboard/gamepad navigation, drag and drop) and many specific behavior (via ImGuiButtonFlags_),
1193b4be Poul Sander 2025-02-03 21:28 480
// this code is a little complex.
1193b4be Poul Sander 2025-02-03 21:28 481
// By far the most common path is interacting with the Mouse using the default ImGuiButtonFlags_PressedOnClickRelease button behavior.
1193b4be Poul Sander 2025-02-03 21:28 482
// See the series of events below and the corresponding state reported by dear imgui:
1193b4be Poul Sander 2025-02-03 21:28 483
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 484
// with PressedOnClickRelease:             return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 485
//   Frame N+0 (mouse is outside bb)        -             -                -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 486
//   Frame N+1 (mouse moves inside bb)      -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 487
//   Frame N+2 (mouse button is down)       -             true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 488
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 489
//   Frame N+4 (mouse moves outside bb)     -             -                true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 490
//   Frame N+5 (mouse moves inside bb)      -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 491
//   Frame N+6 (mouse button is released)   true          true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 492
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 493
//   Frame N+8 (mouse moves outside bb)     -             -                -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 494
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 495
// with PressedOnClick:                    return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 496
//   Frame N+2 (mouse button is down)       true          true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 497
//   Frame N+3 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 498
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 499
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 500
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 501
// with PressedOnRelease:                  return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 502
//   Frame N+2 (mouse button is down)       -             true             -               -                  -                    true
1193b4be Poul Sander 2025-02-03 21:28 503
//   Frame N+3 (mouse button is down)       -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 504
//   Frame N+6 (mouse button is released)   true          true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 505
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 506
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 507
// with PressedOnDoubleClick:              return-value  IsItemHovered()  IsItemActive()  IsItemActivated()  IsItemDeactivated()  IsItemClicked()
1193b4be Poul Sander 2025-02-03 21:28 508
//   Frame N+0 (mouse button is down)       -             true             -               -                  -                    true
1193b4be Poul Sander 2025-02-03 21:28 509
//   Frame N+1 (mouse button is down)       -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 510
//   Frame N+2 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 511
//   Frame N+3 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 512
//   Frame N+4 (mouse button is down)       true          true             true            true               -                    true
1193b4be Poul Sander 2025-02-03 21:28 513
//   Frame N+5 (mouse button is down)       -             true             true            -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 514
//   Frame N+6 (mouse button is released)   -             true             -               -                  true                 -
1193b4be Poul Sander 2025-02-03 21:28 515
//   Frame N+7 (mouse button is released)   -             true             -               -                  -                    -
1193b4be Poul Sander 2025-02-03 21:28 516
//------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 517
// Note that some combinations are supported,
1193b4be Poul Sander 2025-02-03 21:28 518
// - PressedOnDragDropHold can generally be associated with any flag.
1193b4be Poul Sander 2025-02-03 21:28 519
// - PressedOnDoubleClick can be associated by PressedOnClickRelease/PressedOnRelease, in which case the second release event won't be reported.
1193b4be Poul Sander 2025-02-03 21:28 520
//------------------------------------------------------------------------------------------------------------------------------------------------
4e9e2555 Poul Sander 2025-10-05 10:15 521
// The behavior of the return-value changes when ImGuiItemFlags_ButtonRepeat is set:
1193b4be Poul Sander 2025-02-03 21:28 522
//                                         Repeat+                  Repeat+           Repeat+             Repeat+
1193b4be Poul Sander 2025-02-03 21:28 523
//                                         PressedOnClickRelease    PressedOnClick    PressedOnRelease    PressedOnDoubleClick
1193b4be Poul Sander 2025-02-03 21:28 524
//-------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 525
//   Frame N+0 (mouse button is down)       -                        true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 526
//   ...                                    -                        -                 -                   -
1193b4be Poul Sander 2025-02-03 21:28 527
//   Frame N + RepeatDelay                  true                     true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 528
//   ...                                    -                        -                 -                   -
1193b4be Poul Sander 2025-02-03 21:28 529
//   Frame N + RepeatDelay + RepeatRate*N   true                     true              -                   true
1193b4be Poul Sander 2025-02-03 21:28 530
//-------------------------------------------------------------------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 531
1193b4be Poul Sander 2025-02-03 21:28 532
// - FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc.
1193b4be Poul Sander 2025-02-03 21:28 533
//   And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
1193b4be Poul Sander 2025-02-03 21:28 534
//   For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
1193b4be Poul Sander 2025-02-03 21:28 535
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
4e9e2555 Poul Sander 2025-10-05 10:15 536
//   One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior()
1193b4be Poul Sander 2025-02-03 21:28 537
//   with same ID and different MouseButton (see #8030). You can fix it by:
1193b4be Poul Sander 2025-02-03 21:28 538
//       (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
1193b4be Poul Sander 2025-02-03 21:28 539
//    or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
1193b4be Poul Sander 2025-02-03 21:28 540
bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 541
{
1193b4be Poul Sander 2025-02-03 21:28 542
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 543
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 544
4e9e2555 Poul Sander 2025-10-05 10:15 545
    // Default behavior inherited from item flags
4e9e2555 Poul Sander 2025-10-05 10:15 546
    // Note that _both_ ButtonFlags and ItemFlags are valid sources, so copy one into the item_flags and only check that.
4e9e2555 Poul Sander 2025-10-05 10:15 547
    ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
4e9e2555 Poul Sander 2025-10-05 10:15 548
    if (flags & ImGuiButtonFlags_AllowOverlap)
4e9e2555 Poul Sander 2025-10-05 10:15 549
        item_flags |= ImGuiItemFlags_AllowOverlap;
4e9e2555 Poul Sander 2025-10-05 10:15 550
    if (item_flags & ImGuiItemFlags_NoFocus)
4e9e2555 Poul Sander 2025-10-05 10:15 551
        flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus;
4e9e2555 Poul Sander 2025-10-05 10:15 552
1193b4be Poul Sander 2025-02-03 21:28 553
    // Default only reacts to left mouse button
1193b4be Poul Sander 2025-02-03 21:28 554
    if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 555
        flags |= ImGuiButtonFlags_MouseButtonLeft;
1193b4be Poul Sander 2025-02-03 21:28 556
1193b4be Poul Sander 2025-02-03 21:28 557
    // Default behavior requires click + release inside bounding box
1193b4be Poul Sander 2025-02-03 21:28 558
    if ((flags & ImGuiButtonFlags_PressedOnMask_) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 559
        flags |= (item_flags & ImGuiItemFlags_ButtonRepeat) ? ImGuiButtonFlags_PressedOnClick : ImGuiButtonFlags_PressedOnDefault_;
1193b4be Poul Sander 2025-02-03 21:28 560
1193b4be Poul Sander 2025-02-03 21:28 561
    ImGuiWindow* backup_hovered_window = g.HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 562
    const bool flatten_hovered_children = (flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredWindow && g.HoveredWindow->RootWindowDockTree == window->RootWindowDockTree;
1193b4be Poul Sander 2025-02-03 21:28 563
    if (flatten_hovered_children)
1193b4be Poul Sander 2025-02-03 21:28 564
        g.HoveredWindow = window;
1193b4be Poul Sander 2025-02-03 21:28 565
1193b4be Poul Sander 2025-02-03 21:28 566
#ifdef IMGUI_ENABLE_TEST_ENGINE
1193b4be Poul Sander 2025-02-03 21:28 567
    // Alternate registration spot, for when caller didn't use ItemAdd()
1193b4be Poul Sander 2025-02-03 21:28 568
    if (g.LastItemData.ID != id)
1193b4be Poul Sander 2025-02-03 21:28 569
        IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL);
1193b4be Poul Sander 2025-02-03 21:28 570
#endif
1193b4be Poul Sander 2025-02-03 21:28 571
1193b4be Poul Sander 2025-02-03 21:28 572
    bool pressed = false;
1193b4be Poul Sander 2025-02-03 21:28 573
    bool hovered = ItemHoverable(bb, id, item_flags);
1193b4be Poul Sander 2025-02-03 21:28 574
4e9e2555 Poul Sander 2025-10-05 10:15 575
    // Special mode for Drag and Drop used by openables (tree nodes, tabs etc.)
4e9e2555 Poul Sander 2025-10-05 10:15 576
    // where holding the button pressed for a long time while drag a payload item triggers the button.
1193b4be Poul Sander 2025-02-03 21:28 577
    if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
1193b4be Poul Sander 2025-02-03 21:28 578
        if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
1193b4be Poul Sander 2025-02-03 21:28 579
        {
1193b4be Poul Sander 2025-02-03 21:28 580
            hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 581
            SetHoveredID(id);
1193b4be Poul Sander 2025-02-03 21:28 582
            if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
1193b4be Poul Sander 2025-02-03 21:28 583
            {
1193b4be Poul Sander 2025-02-03 21:28 584
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 585
                g.DragDropHoldJustPressedId = id;
1193b4be Poul Sander 2025-02-03 21:28 586
                FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 587
            }
1193b4be Poul Sander 2025-02-03 21:28 588
        }
1193b4be Poul Sander 2025-02-03 21:28 589
1193b4be Poul Sander 2025-02-03 21:28 590
    if (flatten_hovered_children)
1193b4be Poul Sander 2025-02-03 21:28 591
        g.HoveredWindow = backup_hovered_window;
1193b4be Poul Sander 2025-02-03 21:28 592
1193b4be Poul Sander 2025-02-03 21:28 593
    // Mouse handling
1193b4be Poul Sander 2025-02-03 21:28 594
    const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id;
1193b4be Poul Sander 2025-02-03 21:28 595
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 596
    {
1193b4be Poul Sander 2025-02-03 21:28 597
        IM_ASSERT(id != 0); // Lazily check inside rare path.
1193b4be Poul Sander 2025-02-03 21:28 598
1193b4be Poul Sander 2025-02-03 21:28 599
        // Poll mouse buttons
1193b4be Poul Sander 2025-02-03 21:28 600
        // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId.
1193b4be Poul Sander 2025-02-03 21:28 601
        // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine.
1193b4be Poul Sander 2025-02-03 21:28 602
        int mouse_button_clicked = -1;
1193b4be Poul Sander 2025-02-03 21:28 603
        int mouse_button_released = -1;
1193b4be Poul Sander 2025-02-03 21:28 604
        for (int button = 0; button < 3; button++)
1193b4be Poul Sander 2025-02-03 21:28 605
            if (flags & (ImGuiButtonFlags_MouseButtonLeft << button)) // Handle ImGuiButtonFlags_MouseButtonRight and ImGuiButtonFlags_MouseButtonMiddle here.
1193b4be Poul Sander 2025-02-03 21:28 606
            {
1193b4be Poul Sander 2025-02-03 21:28 607
                if (IsMouseClicked(button, ImGuiInputFlags_None, test_owner_id) && mouse_button_clicked == -1) { mouse_button_clicked = button; }
1193b4be Poul Sander 2025-02-03 21:28 608
                if (IsMouseReleased(button, test_owner_id) && mouse_button_released == -1) { mouse_button_released = button; }
1193b4be Poul Sander 2025-02-03 21:28 609
            }
1193b4be Poul Sander 2025-02-03 21:28 610
1193b4be Poul Sander 2025-02-03 21:28 611
        // Process initial action
1193b4be Poul Sander 2025-02-03 21:28 612
        const bool mods_ok = !(flags & ImGuiButtonFlags_NoKeyModsAllowed) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt);
1193b4be Poul Sander 2025-02-03 21:28 613
        if (mods_ok)
1193b4be Poul Sander 2025-02-03 21:28 614
        {
1193b4be Poul Sander 2025-02-03 21:28 615
            if (mouse_button_clicked != -1 && g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 616
            {
1193b4be Poul Sander 2025-02-03 21:28 617
                if (!(flags & ImGuiButtonFlags_NoSetKeyOwner))
1193b4be Poul Sander 2025-02-03 21:28 618
                    SetKeyOwner(MouseButtonToKey(mouse_button_clicked), id);
1193b4be Poul Sander 2025-02-03 21:28 619
                if (flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere))
1193b4be Poul Sander 2025-02-03 21:28 620
                {
1193b4be Poul Sander 2025-02-03 21:28 621
                    SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 622
                    g.ActiveIdMouseButton = mouse_button_clicked;
1193b4be Poul Sander 2025-02-03 21:28 623
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 624
                    {
1193b4be Poul Sander 2025-02-03 21:28 625
                        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 626
                        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 627
                    }
4e9e2555 Poul Sander 2025-10-05 10:15 628
                    else if (!(flags & ImGuiButtonFlags_NoFocus))
1193b4be Poul Sander 2025-02-03 21:28 629
                    {
1193b4be Poul Sander 2025-02-03 21:28 630
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
1193b4be Poul Sander 2025-02-03 21:28 631
                    }
1193b4be Poul Sander 2025-02-03 21:28 632
                }
1193b4be Poul Sander 2025-02-03 21:28 633
                if ((flags & ImGuiButtonFlags_PressedOnClick) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseClickedCount[mouse_button_clicked] == 2))
1193b4be Poul Sander 2025-02-03 21:28 634
                {
1193b4be Poul Sander 2025-02-03 21:28 635
                    pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 636
                    if (flags & ImGuiButtonFlags_NoHoldingActiveId)
1193b4be Poul Sander 2025-02-03 21:28 637
                        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 638
                    else
1193b4be Poul Sander 2025-02-03 21:28 639
                        SetActiveID(id, window); // Hold on ID
1193b4be Poul Sander 2025-02-03 21:28 640
                    g.ActiveIdMouseButton = mouse_button_clicked;
1193b4be Poul Sander 2025-02-03 21:28 641
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 642
                    {
1193b4be Poul Sander 2025-02-03 21:28 643
                        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 644
                        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 645
                    }
4e9e2555 Poul Sander 2025-10-05 10:15 646
                    else if (!(flags & ImGuiButtonFlags_NoFocus))
1193b4be Poul Sander 2025-02-03 21:28 647
                    {
1193b4be Poul Sander 2025-02-03 21:28 648
                        FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
1193b4be Poul Sander 2025-02-03 21:28 649
                    }
1193b4be Poul Sander 2025-02-03 21:28 650
                }
1193b4be Poul Sander 2025-02-03 21:28 651
            }
1193b4be Poul Sander 2025-02-03 21:28 652
            if (flags & ImGuiButtonFlags_PressedOnRelease)
1193b4be Poul Sander 2025-02-03 21:28 653
            {
1193b4be Poul Sander 2025-02-03 21:28 654
                if (mouse_button_released != -1)
1193b4be Poul Sander 2025-02-03 21:28 655
                {
1193b4be Poul Sander 2025-02-03 21:28 656
                    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
1193b4be Poul Sander 2025-02-03 21:28 657
                    if (!has_repeated_at_least_once)
1193b4be Poul Sander 2025-02-03 21:28 658
                        pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 659
                    if (!(flags & ImGuiButtonFlags_NoNavFocus))
1193b4be Poul Sander 2025-02-03 21:28 660
                        SetFocusID(id, window); // FIXME: Lack of FocusWindow() call here is inconsistent with other paths. Research why.
1193b4be Poul Sander 2025-02-03 21:28 661
                    ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 662
                }
1193b4be Poul Sander 2025-02-03 21:28 663
            }
1193b4be Poul Sander 2025-02-03 21:28 664
1193b4be Poul Sander 2025-02-03 21:28 665
            // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
1193b4be Poul Sander 2025-02-03 21:28 666
            // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
1193b4be Poul Sander 2025-02-03 21:28 667
            if (g.ActiveId == id && (item_flags & ImGuiItemFlags_ButtonRepeat))
1193b4be Poul Sander 2025-02-03 21:28 668
                if (g.IO.MouseDownDuration[g.ActiveIdMouseButton] > 0.0f && IsMouseClicked(g.ActiveIdMouseButton, ImGuiInputFlags_Repeat, test_owner_id))
1193b4be Poul Sander 2025-02-03 21:28 669
                    pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 670
        }
1193b4be Poul Sander 2025-02-03 21:28 671
1193b4be Poul Sander 2025-02-03 21:28 672
        if (pressed && g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 673
            g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 674
    }
1193b4be Poul Sander 2025-02-03 21:28 675
1193b4be Poul Sander 2025-02-03 21:28 676
    // Keyboard/Gamepad navigation handling
1193b4be Poul Sander 2025-02-03 21:28 677
    // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse.
1193b4be Poul Sander 2025-02-03 21:28 678
    if (g.NavId == id && g.NavCursorVisible && g.NavHighlightItemUnderNav)
1193b4be Poul Sander 2025-02-03 21:28 679
        if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus))
1193b4be Poul Sander 2025-02-03 21:28 680
            hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 681
    if (g.NavActivateDownId == id)
1193b4be Poul Sander 2025-02-03 21:28 682
    {
1193b4be Poul Sander 2025-02-03 21:28 683
        bool nav_activated_by_code = (g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 684
        bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
1193b4be Poul Sander 2025-02-03 21:28 685
        if (!nav_activated_by_inputs && (item_flags & ImGuiItemFlags_ButtonRepeat))
1193b4be Poul Sander 2025-02-03 21:28 686
        {
1193b4be Poul Sander 2025-02-03 21:28 687
            // Avoid pressing multiple keys from triggering excessive amount of repeat events
1193b4be Poul Sander 2025-02-03 21:28 688
            const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
1193b4be Poul Sander 2025-02-03 21:28 689
            const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_Enter);
1193b4be Poul Sander 2025-02-03 21:28 690
            const ImGuiKeyData* key3 = GetKeyData(ImGuiKey_NavGamepadActivate);
1193b4be Poul Sander 2025-02-03 21:28 691
            const float t1 = ImMax(ImMax(key1->DownDuration, key2->DownDuration), key3->DownDuration);
1193b4be Poul Sander 2025-02-03 21:28 692
            nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
1193b4be Poul Sander 2025-02-03 21:28 693
        }
1193b4be Poul Sander 2025-02-03 21:28 694
        if (nav_activated_by_code || nav_activated_by_inputs)
1193b4be Poul Sander 2025-02-03 21:28 695
        {
1193b4be Poul Sander 2025-02-03 21:28 696
            // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
1193b4be Poul Sander 2025-02-03 21:28 697
            pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 698
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 699
            g.ActiveIdSource = g.NavInputSource;
1193b4be Poul Sander 2025-02-03 21:28 700
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut))
1193b4be Poul Sander 2025-02-03 21:28 701
                SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 702
            if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)
1193b4be Poul Sander 2025-02-03 21:28 703
                g.ActiveIdFromShortcut = true;
1193b4be Poul Sander 2025-02-03 21:28 704
        }
1193b4be Poul Sander 2025-02-03 21:28 705
    }
1193b4be Poul Sander 2025-02-03 21:28 706
1193b4be Poul Sander 2025-02-03 21:28 707
    // Process while held
1193b4be Poul Sander 2025-02-03 21:28 708
    bool held = false;
1193b4be Poul Sander 2025-02-03 21:28 709
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 710
    {
1193b4be Poul Sander 2025-02-03 21:28 711
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
1193b4be Poul Sander 2025-02-03 21:28 712
        {
1193b4be Poul Sander 2025-02-03 21:28 713
            if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 714
                g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 715
1193b4be Poul Sander 2025-02-03 21:28 716
            const int mouse_button = g.ActiveIdMouseButton;
1193b4be Poul Sander 2025-02-03 21:28 717
            if (mouse_button == -1)
1193b4be Poul Sander 2025-02-03 21:28 718
            {
1193b4be Poul Sander 2025-02-03 21:28 719
                // Fallback for the rare situation were g.ActiveId was set programmatically or from another widget (e.g. #6304).
1193b4be Poul Sander 2025-02-03 21:28 720
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 721
            }
1193b4be Poul Sander 2025-02-03 21:28 722
            else if (IsMouseDown(mouse_button, test_owner_id))
1193b4be Poul Sander 2025-02-03 21:28 723
            {
1193b4be Poul Sander 2025-02-03 21:28 724
                held = true;
1193b4be Poul Sander 2025-02-03 21:28 725
            }
1193b4be Poul Sander 2025-02-03 21:28 726
            else
1193b4be Poul Sander 2025-02-03 21:28 727
            {
1193b4be Poul Sander 2025-02-03 21:28 728
                bool release_in = hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease) != 0;
1193b4be Poul Sander 2025-02-03 21:28 729
                bool release_anywhere = (flags & ImGuiButtonFlags_PressedOnClickReleaseAnywhere) != 0;
1193b4be Poul Sander 2025-02-03 21:28 730
                if ((release_in || release_anywhere) && !g.DragDropActive)
1193b4be Poul Sander 2025-02-03 21:28 731
                {
1193b4be Poul Sander 2025-02-03 21:28 732
                    // Report as pressed when releasing the mouse (this is the most common path)
1193b4be Poul Sander 2025-02-03 21:28 733
                    bool is_double_click_release = (flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseReleased[mouse_button] && g.IO.MouseClickedLastCount[mouse_button] == 2;
1193b4be Poul Sander 2025-02-03 21:28 734
                    bool is_repeating_already = (item_flags & ImGuiItemFlags_ButtonRepeat) && g.IO.MouseDownDurationPrev[mouse_button] >= g.IO.KeyRepeatDelay; // Repeat mode trumps <on release>
1193b4be Poul Sander 2025-02-03 21:28 735
                    bool is_button_avail_or_owned = TestKeyOwner(MouseButtonToKey(mouse_button), test_owner_id);
1193b4be Poul Sander 2025-02-03 21:28 736
                    if (!is_double_click_release && !is_repeating_already && is_button_avail_or_owned)
1193b4be Poul Sander 2025-02-03 21:28 737
                        pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 738
                }
1193b4be Poul Sander 2025-02-03 21:28 739
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 740
            }
1193b4be Poul Sander 2025-02-03 21:28 741
            if (!(flags & ImGuiButtonFlags_NoNavFocus) && g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 742
                g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 743
        }
1193b4be Poul Sander 2025-02-03 21:28 744
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 745
        {
1193b4be Poul Sander 2025-02-03 21:28 746
            // When activated using Nav, we hold on the ActiveID until activation button is released
1193b4be Poul Sander 2025-02-03 21:28 747
            if (g.NavActivateDownId == id)
1193b4be Poul Sander 2025-02-03 21:28 748
                held = true; // hovered == true not true as we are already likely hovered on direct activation.
1193b4be Poul Sander 2025-02-03 21:28 749
            else
1193b4be Poul Sander 2025-02-03 21:28 750
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 751
        }
1193b4be Poul Sander 2025-02-03 21:28 752
        if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 753
            g.ActiveIdHasBeenPressedBefore = true;
1193b4be Poul Sander 2025-02-03 21:28 754
    }
1193b4be Poul Sander 2025-02-03 21:28 755
1193b4be Poul Sander 2025-02-03 21:28 756
    // Activation highlight (this may be a remote activation)
1193b4be Poul Sander 2025-02-03 21:28 757
    if (g.NavHighlightActivatedId == id)
1193b4be Poul Sander 2025-02-03 21:28 758
        hovered = true;
1193b4be Poul Sander 2025-02-03 21:28 759
1193b4be Poul Sander 2025-02-03 21:28 760
    if (out_hovered) *out_hovered = hovered;
1193b4be Poul Sander 2025-02-03 21:28 761
    if (out_held) *out_held = held;
1193b4be Poul Sander 2025-02-03 21:28 762
1193b4be Poul Sander 2025-02-03 21:28 763
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 764
}
1193b4be Poul Sander 2025-02-03 21:28 765
1193b4be Poul Sander 2025-02-03 21:28 766
bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 767
{
1193b4be Poul Sander 2025-02-03 21:28 768
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 769
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 770
        return false;
1193b4be Poul Sander 2025-02-03 21:28 771
1193b4be Poul Sander 2025-02-03 21:28 772
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 773
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 774
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 775
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 776
1193b4be Poul Sander 2025-02-03 21:28 777
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 778
    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)
1193b4be Poul Sander 2025-02-03 21:28 779
        pos.y += window->DC.CurrLineTextBaseOffset - style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 780
    ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 781
1193b4be Poul Sander 2025-02-03 21:28 782
    const ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 783
    ItemSize(size, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 784
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 785
        return false;
1193b4be Poul Sander 2025-02-03 21:28 786
1193b4be Poul Sander 2025-02-03 21:28 787
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 788
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 789
1193b4be Poul Sander 2025-02-03 21:28 790
    // Render
1193b4be Poul Sander 2025-02-03 21:28 791
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 792
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 793
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 794
1193b4be Poul Sander 2025-02-03 21:28 795
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 796
        LogSetNextTextDecoration("[", "]");
1193b4be Poul Sander 2025-02-03 21:28 797
    RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
1193b4be Poul Sander 2025-02-03 21:28 798
1193b4be Poul Sander 2025-02-03 21:28 799
    // Automatically close popups
1193b4be Poul Sander 2025-02-03 21:28 800
    //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
1193b4be Poul Sander 2025-02-03 21:28 801
    //    CloseCurrentPopup();
1193b4be Poul Sander 2025-02-03 21:28 802
1193b4be Poul Sander 2025-02-03 21:28 803
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 804
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 805
}
1193b4be Poul Sander 2025-02-03 21:28 806
1193b4be Poul Sander 2025-02-03 21:28 807
bool ImGui::Button(const char* label, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 808
{
1193b4be Poul Sander 2025-02-03 21:28 809
    return ButtonEx(label, size_arg, ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 810
}
1193b4be Poul Sander 2025-02-03 21:28 811
1193b4be Poul Sander 2025-02-03 21:28 812
// Small buttons fits within text without additional vertical spacing.
1193b4be Poul Sander 2025-02-03 21:28 813
bool ImGui::SmallButton(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 814
{
1193b4be Poul Sander 2025-02-03 21:28 815
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 816
    float backup_padding_y = g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 817
    g.Style.FramePadding.y = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 818
    bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
1193b4be Poul Sander 2025-02-03 21:28 819
    g.Style.FramePadding.y = backup_padding_y;
1193b4be Poul Sander 2025-02-03 21:28 820
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 821
}
1193b4be Poul Sander 2025-02-03 21:28 822
1193b4be Poul Sander 2025-02-03 21:28 823
// Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
1193b4be Poul Sander 2025-02-03 21:28 824
// 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)
1193b4be Poul Sander 2025-02-03 21:28 825
bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 826
{
1193b4be Poul Sander 2025-02-03 21:28 827
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 828
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 829
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 830
        return false;
1193b4be Poul Sander 2025-02-03 21:28 831
1193b4be Poul Sander 2025-02-03 21:28 832
    // Cannot use zero-size for InvisibleButton(). Unlike Button() there is not way to fallback using the label size.
1193b4be Poul Sander 2025-02-03 21:28 833
    IM_ASSERT(size_arg.x != 0.0f && size_arg.y != 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 834
1193b4be Poul Sander 2025-02-03 21:28 835
    const ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 836
    ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 837
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 838
    ItemSize(size);
1193b4be Poul Sander 2025-02-03 21:28 839
    if (!ItemAdd(bb, id, NULL, (flags & ImGuiButtonFlags_EnableNav) ? ImGuiItemFlags_None : ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 840
        return false;
1193b4be Poul Sander 2025-02-03 21:28 841
1193b4be Poul Sander 2025-02-03 21:28 842
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 843
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 844
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 845
1193b4be Poul Sander 2025-02-03 21:28 846
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 847
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 848
}
1193b4be Poul Sander 2025-02-03 21:28 849
1193b4be Poul Sander 2025-02-03 21:28 850
bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 851
{
1193b4be Poul Sander 2025-02-03 21:28 852
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 853
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 854
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 855
        return false;
1193b4be Poul Sander 2025-02-03 21:28 856
1193b4be Poul Sander 2025-02-03 21:28 857
    const ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 858
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 859
    const float default_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 860
    ItemSize(size, (size.y >= default_size) ? g.Style.FramePadding.y : -1.0f);
1193b4be Poul Sander 2025-02-03 21:28 861
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 862
        return false;
1193b4be Poul Sander 2025-02-03 21:28 863
1193b4be Poul Sander 2025-02-03 21:28 864
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 865
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 866
1193b4be Poul Sander 2025-02-03 21:28 867
    // Render
1193b4be Poul Sander 2025-02-03 21:28 868
    const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 869
    const ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 870
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 871
    RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 872
    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);
1193b4be Poul Sander 2025-02-03 21:28 873
1193b4be Poul Sander 2025-02-03 21:28 874
    IMGUI_TEST_ENGINE_ITEM_INFO(id, str_id, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 875
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 876
}
1193b4be Poul Sander 2025-02-03 21:28 877
1193b4be Poul Sander 2025-02-03 21:28 878
bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
1193b4be Poul Sander 2025-02-03 21:28 879
{
1193b4be Poul Sander 2025-02-03 21:28 880
    float sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 881
    return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 882
}
1193b4be Poul Sander 2025-02-03 21:28 883
1193b4be Poul Sander 2025-02-03 21:28 884
// Button to close a window
1193b4be Poul Sander 2025-02-03 21:28 885
bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
1193b4be Poul Sander 2025-02-03 21:28 886
{
1193b4be Poul Sander 2025-02-03 21:28 887
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 888
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 889
1193b4be Poul Sander 2025-02-03 21:28 890
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 891
    // 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?
1193b4be Poul Sander 2025-02-03 21:28 892
    const ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 893
    ImRect bb_interact = bb;
1193b4be Poul Sander 2025-02-03 21:28 894
    const float area_to_visible_ratio = window->OuterRectClipped.GetArea() / bb.GetArea();
1193b4be Poul Sander 2025-02-03 21:28 895
    if (area_to_visible_ratio < 1.5f)
1193b4be Poul Sander 2025-02-03 21:28 896
        bb_interact.Expand(ImTrunc(bb_interact.GetSize() * -0.25f));
1193b4be Poul Sander 2025-02-03 21:28 897
1193b4be Poul Sander 2025-02-03 21:28 898
    // Tweak 2: We intentionally allow interaction when clipped so that a mechanical Alt,Right,Activate sequence can always close a window.
1193b4be Poul Sander 2025-02-03 21:28 899
    // (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).
1193b4be Poul Sander 2025-02-03 21:28 900
    bool is_clipped = !ItemAdd(bb_interact, id);
1193b4be Poul Sander 2025-02-03 21:28 901
1193b4be Poul Sander 2025-02-03 21:28 902
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 903
    bool pressed = ButtonBehavior(bb_interact, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 904
    if (is_clipped)
1193b4be Poul Sander 2025-02-03 21:28 905
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 906
1193b4be Poul Sander 2025-02-03 21:28 907
    // Render
1193b4be Poul Sander 2025-02-03 21:28 908
    ImU32 bg_col = GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
1193b4be Poul Sander 2025-02-03 21:28 909
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 910
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
1193b4be Poul Sander 2025-02-03 21:28 911
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
4e9e2555 Poul Sander 2025-10-05 10:15 912
    const ImU32 cross_col = GetColorU32(ImGuiCol_Text);
4e9e2555 Poul Sander 2025-10-05 10:15 913
    const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
4e9e2555 Poul Sander 2025-10-05 10:15 914
    const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 915
    const float cross_thickness = 1.0f; // FIXME-DPI
4e9e2555 Poul Sander 2025-10-05 10:15 916
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness);
4e9e2555 Poul Sander 2025-10-05 10:15 917
    window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness);
1193b4be Poul Sander 2025-02-03 21:28 918
1193b4be Poul Sander 2025-02-03 21:28 919
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 920
}
1193b4be Poul Sander 2025-02-03 21:28 921
1193b4be Poul Sander 2025-02-03 21:28 922
// The Collapse button also functions as a Dock Menu button.
1193b4be Poul Sander 2025-02-03 21:28 923
bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node)
1193b4be Poul Sander 2025-02-03 21:28 924
{
1193b4be Poul Sander 2025-02-03 21:28 925
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 926
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 927
1193b4be Poul Sander 2025-02-03 21:28 928
    ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 929
    bool is_clipped = !ItemAdd(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 930
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 931
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 932
    if (is_clipped)
1193b4be Poul Sander 2025-02-03 21:28 933
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 934
1193b4be Poul Sander 2025-02-03 21:28 935
    // Render
1193b4be Poul Sander 2025-02-03 21:28 936
    //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed);
1193b4be Poul Sander 2025-02-03 21:28 937
    ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 938
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 939
    if (hovered || held)
1193b4be Poul Sander 2025-02-03 21:28 940
        window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
1193b4be Poul Sander 2025-02-03 21:28 941
    RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
1193b4be Poul Sander 2025-02-03 21:28 942
1193b4be Poul Sander 2025-02-03 21:28 943
    if (dock_node)
1193b4be Poul Sander 2025-02-03 21:28 944
        RenderArrowDockMenu(window->DrawList, bb.Min, g.FontSize, text_col);
1193b4be Poul Sander 2025-02-03 21:28 945
    else
1193b4be Poul Sander 2025-02-03 21:28 946
        RenderArrow(window->DrawList, bb.Min, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 947
1193b4be Poul Sander 2025-02-03 21:28 948
    // Switch to moving the window after mouse is moved beyond the initial drag threshold
1193b4be Poul Sander 2025-02-03 21:28 949
    if (IsItemActive() && IsMouseDragging(0))
1193b4be Poul Sander 2025-02-03 21:28 950
        StartMouseMovingWindowOrNode(window, dock_node, true); // Undock from window/collapse menu button
1193b4be Poul Sander 2025-02-03 21:28 951
1193b4be Poul Sander 2025-02-03 21:28 952
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 953
}
1193b4be Poul Sander 2025-02-03 21:28 954
1193b4be Poul Sander 2025-02-03 21:28 955
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
1193b4be Poul Sander 2025-02-03 21:28 956
{
1193b4be Poul Sander 2025-02-03 21:28 957
    return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
1193b4be Poul Sander 2025-02-03 21:28 958
}
1193b4be Poul Sander 2025-02-03 21:28 959
1193b4be Poul Sander 2025-02-03 21:28 960
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
1193b4be Poul Sander 2025-02-03 21:28 961
ImRect ImGui::GetWindowScrollbarRect(ImGuiWindow* window, ImGuiAxis axis)
1193b4be Poul Sander 2025-02-03 21:28 962
{
4e9e2555 Poul Sander 2025-10-05 10:15 963
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 964
    const ImRect outer_rect = window->Rect();
1193b4be Poul Sander 2025-02-03 21:28 965
    const ImRect inner_rect = window->InnerRect;
1193b4be Poul Sander 2025-02-03 21:28 966
    const float scrollbar_size = window->ScrollbarSizes[axis ^ 1]; // (ScrollbarSizes.x = width of Y scrollbar; ScrollbarSizes.y = height of X scrollbar)
4e9e2555 Poul Sander 2025-10-05 10:15 967
    IM_ASSERT(scrollbar_size >= 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 968
    const float border_size = IM_ROUND(window->WindowBorderSize * 0.5f);
4e9e2555 Poul Sander 2025-10-05 10:15 969
    const float border_top = (window->Flags & ImGuiWindowFlags_MenuBar) ? IM_ROUND(g.Style.FrameBorderSize * 0.5f) : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 970
    if (axis == ImGuiAxis_X)
4e9e2555 Poul Sander 2025-10-05 10:15 971
        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);
1193b4be Poul Sander 2025-02-03 21:28 972
    else
4e9e2555 Poul Sander 2025-10-05 10:15 973
        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);
1193b4be Poul Sander 2025-02-03 21:28 974
}
1193b4be Poul Sander 2025-02-03 21:28 975
1193b4be Poul Sander 2025-02-03 21:28 976
void ImGui::Scrollbar(ImGuiAxis axis)
1193b4be Poul Sander 2025-02-03 21:28 977
{
1193b4be Poul Sander 2025-02-03 21:28 978
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 979
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 980
    const ImGuiID id = GetWindowScrollbarID(window, axis);
1193b4be Poul Sander 2025-02-03 21:28 981
1193b4be Poul Sander 2025-02-03 21:28 982
    // Calculate scrollbar bounding box
1193b4be Poul Sander 2025-02-03 21:28 983
    ImRect bb = GetWindowScrollbarRect(window, axis);
1193b4be Poul Sander 2025-02-03 21:28 984
    ImDrawFlags rounding_corners = ImDrawFlags_RoundCornersNone;
1193b4be Poul Sander 2025-02-03 21:28 985
    if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 986
    {
1193b4be Poul Sander 2025-02-03 21:28 987
        rounding_corners |= ImDrawFlags_RoundCornersBottomLeft;
1193b4be Poul Sander 2025-02-03 21:28 988
        if (!window->ScrollbarY)
1193b4be Poul Sander 2025-02-03 21:28 989
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
1193b4be Poul Sander 2025-02-03 21:28 990
    }
1193b4be Poul Sander 2025-02-03 21:28 991
    else
1193b4be Poul Sander 2025-02-03 21:28 992
    {
1193b4be Poul Sander 2025-02-03 21:28 993
        if ((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar))
1193b4be Poul Sander 2025-02-03 21:28 994
            rounding_corners |= ImDrawFlags_RoundCornersTopRight;
1193b4be Poul Sander 2025-02-03 21:28 995
        if (!window->ScrollbarX)
1193b4be Poul Sander 2025-02-03 21:28 996
            rounding_corners |= ImDrawFlags_RoundCornersBottomRight;
1193b4be Poul Sander 2025-02-03 21:28 997
    }
1193b4be Poul Sander 2025-02-03 21:28 998
    float size_visible = window->InnerRect.Max[axis] - window->InnerRect.Min[axis];
1193b4be Poul Sander 2025-02-03 21:28 999
    float size_contents = window->ContentSize[axis] + window->WindowPadding[axis] * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 1000
    ImS64 scroll = (ImS64)window->Scroll[axis];
1193b4be Poul Sander 2025-02-03 21:28 1001
    ScrollbarEx(bb, id, axis, &scroll, (ImS64)size_visible, (ImS64)size_contents, rounding_corners);
1193b4be Poul Sander 2025-02-03 21:28 1002
    window->Scroll[axis] = (float)scroll;
1193b4be Poul Sander 2025-02-03 21:28 1003
}
1193b4be Poul Sander 2025-02-03 21:28 1004
1193b4be Poul Sander 2025-02-03 21:28 1005
// Vertical/Horizontal scrollbar
1193b4be Poul Sander 2025-02-03 21:28 1006
// The entire piece of code below is rather confusing because:
1193b4be Poul Sander 2025-02-03 21:28 1007
// - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
1193b4be Poul Sander 2025-02-03 21:28 1008
// - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
1193b4be Poul Sander 2025-02-03 21:28 1009
// - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
1193b4be Poul Sander 2025-02-03 21:28 1010
// Still, the code should probably be made simpler..
4e9e2555 Poul Sander 2025-10-05 10:15 1011
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)
1193b4be Poul Sander 2025-02-03 21:28 1012
{
1193b4be Poul Sander 2025-02-03 21:28 1013
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1014
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1015
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1016
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1017
1193b4be Poul Sander 2025-02-03 21:28 1018
    const float bb_frame_width = bb_frame.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 1019
    const float bb_frame_height = bb_frame.GetHeight();
1193b4be Poul Sander 2025-02-03 21:28 1020
    if (bb_frame_width <= 0.0f || bb_frame_height <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1021
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1022
1193b4be Poul Sander 2025-02-03 21:28 1023
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 1024
    float alpha = 1.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 1025
    if ((axis == ImGuiAxis_Y) && bb_frame_height < bb_frame_width)
4e9e2555 Poul Sander 2025-10-05 10:15 1026
        alpha = ImSaturate(bb_frame_height / ImMax(bb_frame_width * 2.0f, 1.0f));
1193b4be Poul Sander 2025-02-03 21:28 1027
    if (alpha <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1028
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1029
1193b4be Poul Sander 2025-02-03 21:28 1030
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1031
    const bool allow_interaction = (alpha >= 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1032
1193b4be Poul Sander 2025-02-03 21:28 1033
    ImRect bb = bb_frame;
4e9e2555 Poul Sander 2025-10-05 10:15 1034
    float padding = IM_TRUNC(ImMin(style.ScrollbarPadding, ImMin(bb_frame_width, bb_frame_height) * 0.5f));
4e9e2555 Poul Sander 2025-10-05 10:15 1035
    bb.Expand(-padding);
1193b4be Poul Sander 2025-02-03 21:28 1036
1193b4be Poul Sander 2025-02-03 21:28 1037
    // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 1038
    const float scrollbar_size_v = (axis == ImGuiAxis_X) ? bb.GetWidth() : bb.GetHeight();
1193b4be Poul Sander 2025-02-03 21:28 1039
1193b4be Poul Sander 2025-02-03 21:28 1040
    // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
1193b4be Poul Sander 2025-02-03 21:28 1041
    // But we maintain a minimum size in pixel to allow for the user to still aim inside.
1193b4be Poul Sander 2025-02-03 21:28 1042
    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.
1193b4be Poul Sander 2025-02-03 21:28 1043
    const ImS64 win_size_v = ImMax(ImMax(size_contents_v, size_visible_v), (ImS64)1);
4e9e2555 Poul Sander 2025-10-05 10:15 1044
    const float grab_h_minsize = ImMin(bb.GetSize()[axis], style.GrabMinSize);
4e9e2555 Poul Sander 2025-10-05 10:15 1045
    const float grab_h_pixels = ImClamp(scrollbar_size_v * ((float)size_visible_v / (float)win_size_v), grab_h_minsize, scrollbar_size_v);
1193b4be Poul Sander 2025-02-03 21:28 1046
    const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
1193b4be Poul Sander 2025-02-03 21:28 1047
1193b4be Poul Sander 2025-02-03 21:28 1048
    // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
1193b4be Poul Sander 2025-02-03 21:28 1049
    bool held = false;
1193b4be Poul Sander 2025-02-03 21:28 1050
    bool hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 1051
    ItemAdd(bb_frame, id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 1052
    ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
1193b4be Poul Sander 2025-02-03 21:28 1053
1193b4be Poul Sander 2025-02-03 21:28 1054
    const ImS64 scroll_max = ImMax((ImS64)1, size_contents_v - size_visible_v);
1193b4be Poul Sander 2025-02-03 21:28 1055
    float scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1056
    float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v; // Grab position in normalized space
1193b4be Poul Sander 2025-02-03 21:28 1057
    if (held && allow_interaction && grab_h_norm < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 1058
    {
1193b4be Poul Sander 2025-02-03 21:28 1059
        const float scrollbar_pos_v = bb.Min[axis];
1193b4be Poul Sander 2025-02-03 21:28 1060
        const float mouse_pos_v = g.IO.MousePos[axis];
1193b4be Poul Sander 2025-02-03 21:28 1061
1193b4be Poul Sander 2025-02-03 21:28 1062
        // Click position in scrollbar normalized space (0.0f->1.0f)
1193b4be Poul Sander 2025-02-03 21:28 1063
        const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
1193b4be Poul Sander 2025-02-03 21:28 1064
1193b4be Poul Sander 2025-02-03 21:28 1065
        const int held_dir = (clicked_v_norm < grab_v_norm) ? -1 : (clicked_v_norm > grab_v_norm + grab_h_norm) ? +1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 1066
        if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 1067
        {
1193b4be Poul Sander 2025-02-03 21:28 1068
            // On initial click when held_dir == 0 (clicked over grab): calculate the distance between mouse and the center of the grab
1193b4be Poul Sander 2025-02-03 21:28 1069
            const bool scroll_to_clicked_location = (g.IO.ConfigScrollbarScrollByPage == false || g.IO.KeyShift || held_dir == 0);
1193b4be Poul Sander 2025-02-03 21:28 1070
            g.ScrollbarSeekMode = scroll_to_clicked_location ? 0 : (short)held_dir;
1193b4be Poul Sander 2025-02-03 21:28 1071
            g.ScrollbarClickDeltaToGrabCenter = (held_dir == 0 && !g.IO.KeyShift) ? clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1072
        }
1193b4be Poul Sander 2025-02-03 21:28 1073
1193b4be Poul Sander 2025-02-03 21:28 1074
        // Apply scroll (p_scroll_v will generally point on one member of window->Scroll)
1193b4be Poul Sander 2025-02-03 21:28 1075
        // 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
1193b4be Poul Sander 2025-02-03 21:28 1076
        if (g.ScrollbarSeekMode == 0)
1193b4be Poul Sander 2025-02-03 21:28 1077
        {
1193b4be Poul Sander 2025-02-03 21:28 1078
            // Absolute seeking
1193b4be Poul Sander 2025-02-03 21:28 1079
            const float scroll_v_norm = ImSaturate((clicked_v_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm * 0.5f) / (1.0f - grab_h_norm));
1193b4be Poul Sander 2025-02-03 21:28 1080
            *p_scroll_v = (ImS64)(scroll_v_norm * scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1081
        }
1193b4be Poul Sander 2025-02-03 21:28 1082
        else
1193b4be Poul Sander 2025-02-03 21:28 1083
        {
1193b4be Poul Sander 2025-02-03 21:28 1084
            // Page by page
1193b4be Poul Sander 2025-02-03 21:28 1085
            if (IsMouseClicked(ImGuiMouseButton_Left, ImGuiInputFlags_Repeat) && held_dir == g.ScrollbarSeekMode)
1193b4be Poul Sander 2025-02-03 21:28 1086
            {
1193b4be Poul Sander 2025-02-03 21:28 1087
                float page_dir = (g.ScrollbarSeekMode > 0.0f) ? +1.0f : -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 1088
                *p_scroll_v = ImClamp(*p_scroll_v + (ImS64)(page_dir * size_visible_v), (ImS64)0, scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1089
            }
1193b4be Poul Sander 2025-02-03 21:28 1090
        }
1193b4be Poul Sander 2025-02-03 21:28 1091
1193b4be Poul Sander 2025-02-03 21:28 1092
        // Update values for rendering
1193b4be Poul Sander 2025-02-03 21:28 1093
        scroll_ratio = ImSaturate((float)*p_scroll_v / (float)scroll_max);
1193b4be Poul Sander 2025-02-03 21:28 1094
        grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
1193b4be Poul Sander 2025-02-03 21:28 1095
1193b4be Poul Sander 2025-02-03 21:28 1096
        // Update distance to grab now that we have seek'ed and saturated
1193b4be Poul Sander 2025-02-03 21:28 1097
        //if (seek_absolute)
1193b4be Poul Sander 2025-02-03 21:28 1098
        //    g.ScrollbarClickDeltaToGrabCenter = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 1099
    }
1193b4be Poul Sander 2025-02-03 21:28 1100
1193b4be Poul Sander 2025-02-03 21:28 1101
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1102
    const ImU32 bg_col = GetColorU32(ImGuiCol_ScrollbarBg);
1193b4be Poul Sander 2025-02-03 21:28 1103
    const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab, alpha);
4e9e2555 Poul Sander 2025-10-05 10:15 1104
    window->DrawList->AddRectFilled(bb_frame.Min, bb_frame.Max, bg_col, window->WindowRounding, draw_rounding_flags);
1193b4be Poul Sander 2025-02-03 21:28 1105
    ImRect grab_rect;
1193b4be Poul Sander 2025-02-03 21:28 1106
    if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 1107
        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);
1193b4be Poul Sander 2025-02-03 21:28 1108
    else
1193b4be Poul Sander 2025-02-03 21:28 1109
        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);
1193b4be Poul Sander 2025-02-03 21:28 1110
    window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
1193b4be Poul Sander 2025-02-03 21:28 1111
1193b4be Poul Sander 2025-02-03 21:28 1112
    return held;
1193b4be Poul Sander 2025-02-03 21:28 1113
}
1193b4be Poul Sander 2025-02-03 21:28 1114
4e9e2555 Poul Sander 2025-10-05 10:15 1115
// - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
1193b4be Poul Sander 2025-02-03 21:28 1116
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
4e9e2555 Poul Sander 2025-10-05 10:15 1117
void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
1193b4be Poul Sander 2025-02-03 21:28 1118
{
4e9e2555 Poul Sander 2025-10-05 10:15 1119
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1120
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1121
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1122
        return;
1193b4be Poul Sander 2025-02-03 21:28 1123
4e9e2555 Poul Sander 2025-10-05 10:15 1124
    const ImVec2 padding(g.Style.ImageBorderSize, g.Style.ImageBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 1125
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1126
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1127
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1128
        return;
1193b4be Poul Sander 2025-02-03 21:28 1129
1193b4be Poul Sander 2025-02-03 21:28 1130
    // Render
4e9e2555 Poul Sander 2025-10-05 10:15 1131
    if (g.Style.ImageBorderSize > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 1132
        window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);
4e9e2555 Poul Sander 2025-10-05 10:15 1133
    if (bg_col.w > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 1134
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
4e9e2555 Poul Sander 2025-10-05 10:15 1135
    window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1193b4be Poul Sander 2025-02-03 21:28 1136
}
1193b4be Poul Sander 2025-02-03 21:28 1137
4e9e2555 Poul Sander 2025-10-05 10:15 1138
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
4e9e2555 Poul Sander 2025-10-05 10:15 1139
{
4e9e2555 Poul Sander 2025-10-05 10:15 1140
    ImageWithBg(tex_ref, image_size, uv0, uv1);
4e9e2555 Poul Sander 2025-10-05 10:15 1141
}
4e9e2555 Poul Sander 2025-10-05 10:15 1142
4e9e2555 Poul Sander 2025-10-05 10:15 1143
// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)
4e9e2555 Poul Sander 2025-10-05 10:15 1144
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
4e9e2555 Poul Sander 2025-10-05 10:15 1145
void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
4e9e2555 Poul Sander 2025-10-05 10:15 1146
{
4e9e2555 Poul Sander 2025-10-05 10:15 1147
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 1148
    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
4e9e2555 Poul Sander 2025-10-05 10:15 1149
    PushStyleColor(ImGuiCol_Border, border_col);
4e9e2555 Poul Sander 2025-10-05 10:15 1150
    ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);
4e9e2555 Poul Sander 2025-10-05 10:15 1151
    PopStyleColor();
4e9e2555 Poul Sander 2025-10-05 10:15 1152
    PopStyleVar();
4e9e2555 Poul Sander 2025-10-05 10:15 1153
}
4e9e2555 Poul Sander 2025-10-05 10:15 1154
#endif
4e9e2555 Poul Sander 2025-10-05 10:15 1155
4e9e2555 Poul Sander 2025-10-05 10:15 1156
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)
1193b4be Poul Sander 2025-02-03 21:28 1157
{
1193b4be Poul Sander 2025-02-03 21:28 1158
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1159
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1160
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1161
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1162
1193b4be Poul Sander 2025-02-03 21:28 1163
    const ImVec2 padding = g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 1164
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + image_size + padding * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1165
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1166
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1167
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1168
1193b4be Poul Sander 2025-02-03 21:28 1169
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1170
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
1193b4be Poul Sander 2025-02-03 21:28 1171
1193b4be Poul Sander 2025-02-03 21:28 1172
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1173
    const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 1174
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1175
    RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
1193b4be Poul Sander 2025-02-03 21:28 1176
    if (bg_col.w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1177
        window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
4e9e2555 Poul Sander 2025-10-05 10:15 1178
    window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
1193b4be Poul Sander 2025-02-03 21:28 1179
1193b4be Poul Sander 2025-02-03 21:28 1180
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1181
}
1193b4be Poul Sander 2025-02-03 21:28 1182
4e9e2555 Poul Sander 2025-10-05 10:15 1183
// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
4e9e2555 Poul Sander 2025-10-05 10:15 1184
// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design?
4e9e2555 Poul Sander 2025-10-05 10:15 1185
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)
1193b4be Poul Sander 2025-02-03 21:28 1186
{
1193b4be Poul Sander 2025-02-03 21:28 1187
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1188
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1189
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1190
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1191
4e9e2555 Poul Sander 2025-10-05 10:15 1192
    return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col);
1193b4be Poul Sander 2025-02-03 21:28 1193
}
1193b4be Poul Sander 2025-02-03 21:28 1194
1193b4be Poul Sander 2025-02-03 21:28 1195
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1196
// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
4e9e2555 Poul Sander 2025-10-05 10:15 1197
// - old ImageButton() used ImTextureID as item id (created issue with multiple buttons with same image, transient texture id values, opaque computation of ID)
1193b4be Poul Sander 2025-02-03 21:28 1198
// - new ImageButton() requires an explicit 'const char* str_id'
1193b4be Poul Sander 2025-02-03 21:28 1199
// - old ImageButton() had frame_padding' override argument.
1193b4be Poul Sander 2025-02-03 21:28 1200
// - new ImageButton() always use style.FramePadding.
1193b4be Poul Sander 2025-02-03 21:28 1201
/*
1193b4be Poul Sander 2025-02-03 21:28 1202
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)
1193b4be Poul Sander 2025-02-03 21:28 1203
{
1193b4be Poul Sander 2025-02-03 21:28 1204
    // Default to using texture ID as ID. User can still push string/integer prefixes.
1193b4be Poul Sander 2025-02-03 21:28 1205
    PushID((ImTextureID)(intptr_t)user_texture_id);
1193b4be Poul Sander 2025-02-03 21:28 1206
    if (frame_padding >= 0)
1193b4be Poul Sander 2025-02-03 21:28 1207
        PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
1193b4be Poul Sander 2025-02-03 21:28 1208
    bool ret = ImageButton("", user_texture_id, size, uv0, uv1, bg_col, tint_col);
1193b4be Poul Sander 2025-02-03 21:28 1209
    if (frame_padding >= 0)
1193b4be Poul Sander 2025-02-03 21:28 1210
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 1211
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 1212
    return ret;
1193b4be Poul Sander 2025-02-03 21:28 1213
}
1193b4be Poul Sander 2025-02-03 21:28 1214
*/
1193b4be Poul Sander 2025-02-03 21:28 1215
#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1216
1193b4be Poul Sander 2025-02-03 21:28 1217
bool ImGui::Checkbox(const char* label, bool* v)
1193b4be Poul Sander 2025-02-03 21:28 1218
{
1193b4be Poul Sander 2025-02-03 21:28 1219
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1220
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1221
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1222
1193b4be Poul Sander 2025-02-03 21:28 1223
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1224
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1225
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1226
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1227
1193b4be Poul Sander 2025-02-03 21:28 1228
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1229
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1230
    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));
1193b4be Poul Sander 2025-02-03 21:28 1231
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1232
    const bool is_visible = ItemAdd(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1233
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1234
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 1235
        if (!is_multi_select || !g.BoxSelectState.UnclipMode || !g.BoxSelectState.UnclipRect.Overlaps(total_bb)) // Extra layer of "no logic clip" for box-select support
1193b4be Poul Sander 2025-02-03 21:28 1236
        {
1193b4be Poul Sander 2025-02-03 21:28 1237
            IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1193b4be Poul Sander 2025-02-03 21:28 1238
            return false;
1193b4be Poul Sander 2025-02-03 21:28 1239
        }
1193b4be Poul Sander 2025-02-03 21:28 1240
1193b4be Poul Sander 2025-02-03 21:28 1241
    // Range-Selection/Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 1242
    bool checked = *v;
1193b4be Poul Sander 2025-02-03 21:28 1243
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 1244
        MultiSelectItemHeader(id, &checked, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1245
1193b4be Poul Sander 2025-02-03 21:28 1246
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1247
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1248
1193b4be Poul Sander 2025-02-03 21:28 1249
    // Range-Selection/Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 1250
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 1251
        MultiSelectItemFooter(id, &checked, &pressed);
1193b4be Poul Sander 2025-02-03 21:28 1252
    else if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1253
        checked = !checked;
1193b4be Poul Sander 2025-02-03 21:28 1254
1193b4be Poul Sander 2025-02-03 21:28 1255
    if (*v != checked)
1193b4be Poul Sander 2025-02-03 21:28 1256
    {
1193b4be Poul Sander 2025-02-03 21:28 1257
        *v = checked;
1193b4be Poul Sander 2025-02-03 21:28 1258
        pressed = true; // return value
1193b4be Poul Sander 2025-02-03 21:28 1259
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1260
    }
1193b4be Poul Sander 2025-02-03 21:28 1261
1193b4be Poul Sander 2025-02-03 21:28 1262
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1193b4be Poul Sander 2025-02-03 21:28 1263
    const bool mixed_value = (g.LastItemData.ItemFlags & ImGuiItemFlags_MixedValue) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1264
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 1265
    {
1193b4be Poul Sander 2025-02-03 21:28 1266
        RenderNavCursor(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1267
        RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1268
        ImU32 check_col = GetColorU32(ImGuiCol_CheckMark);
1193b4be Poul Sander 2025-02-03 21:28 1269
        if (mixed_value)
1193b4be Poul Sander 2025-02-03 21:28 1270
        {
1193b4be Poul Sander 2025-02-03 21:28 1271
            // Undocumented tristate/mixed/indeterminate checkbox (#2644)
1193b4be Poul Sander 2025-02-03 21:28 1272
            // This may seem awkwardly designed because the aim is to make ImGuiItemFlags_MixedValue supported by all widgets (not just checkbox)
1193b4be Poul Sander 2025-02-03 21:28 1273
            ImVec2 pad(ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)), ImMax(1.0f, IM_TRUNC(square_sz / 3.6f)));
1193b4be Poul Sander 2025-02-03 21:28 1274
            window->DrawList->AddRectFilled(check_bb.Min + pad, check_bb.Max - pad, check_col, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1275
        }
1193b4be Poul Sander 2025-02-03 21:28 1276
        else if (*v)
1193b4be Poul Sander 2025-02-03 21:28 1277
        {
1193b4be Poul Sander 2025-02-03 21:28 1278
            const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1193b4be Poul Sander 2025-02-03 21:28 1279
            RenderCheckMark(window->DrawList, check_bb.Min + ImVec2(pad, pad), check_col, square_sz - pad * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1280
        }
1193b4be Poul Sander 2025-02-03 21:28 1281
    }
1193b4be Poul Sander 2025-02-03 21:28 1282
    const ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1283
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1284
        LogRenderedText(&label_pos, mixed_value ? "[~]" : *v ? "[x]" : "[ ]");
1193b4be Poul Sander 2025-02-03 21:28 1285
    if (is_visible && label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1286
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 1287
1193b4be Poul Sander 2025-02-03 21:28 1288
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (*v ? ImGuiItemStatusFlags_Checked : 0));
1193b4be Poul Sander 2025-02-03 21:28 1289
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1290
}
1193b4be Poul Sander 2025-02-03 21:28 1291
1193b4be Poul Sander 2025-02-03 21:28 1292
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 1293
bool ImGui::CheckboxFlagsT(const char* label, T* flags, T flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1294
{
1193b4be Poul Sander 2025-02-03 21:28 1295
    bool all_on = (*flags & flags_value) == flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1296
    bool any_on = (*flags & flags_value) != 0;
1193b4be Poul Sander 2025-02-03 21:28 1297
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 1298
    if (!all_on && any_on)
1193b4be Poul Sander 2025-02-03 21:28 1299
    {
1193b4be Poul Sander 2025-02-03 21:28 1300
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1301
        g.NextItemData.ItemFlags |= ImGuiItemFlags_MixedValue;
1193b4be Poul Sander 2025-02-03 21:28 1302
        pressed = Checkbox(label, &all_on);
1193b4be Poul Sander 2025-02-03 21:28 1303
    }
1193b4be Poul Sander 2025-02-03 21:28 1304
    else
1193b4be Poul Sander 2025-02-03 21:28 1305
    {
1193b4be Poul Sander 2025-02-03 21:28 1306
        pressed = Checkbox(label, &all_on);
1193b4be Poul Sander 2025-02-03 21:28 1307
1193b4be Poul Sander 2025-02-03 21:28 1308
    }
1193b4be Poul Sander 2025-02-03 21:28 1309
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1310
    {
1193b4be Poul Sander 2025-02-03 21:28 1311
        if (all_on)
1193b4be Poul Sander 2025-02-03 21:28 1312
            *flags |= flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1313
        else
1193b4be Poul Sander 2025-02-03 21:28 1314
            *flags &= ~flags_value;
1193b4be Poul Sander 2025-02-03 21:28 1315
    }
1193b4be Poul Sander 2025-02-03 21:28 1316
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1317
}
1193b4be Poul Sander 2025-02-03 21:28 1318
1193b4be Poul Sander 2025-02-03 21:28 1319
bool ImGui::CheckboxFlags(const char* label, int* flags, int flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1320
{
1193b4be Poul Sander 2025-02-03 21:28 1321
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1322
}
1193b4be Poul Sander 2025-02-03 21:28 1323
1193b4be Poul Sander 2025-02-03 21:28 1324
bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1325
{
1193b4be Poul Sander 2025-02-03 21:28 1326
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1327
}
1193b4be Poul Sander 2025-02-03 21:28 1328
1193b4be Poul Sander 2025-02-03 21:28 1329
bool ImGui::CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1330
{
1193b4be Poul Sander 2025-02-03 21:28 1331
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1332
}
1193b4be Poul Sander 2025-02-03 21:28 1333
1193b4be Poul Sander 2025-02-03 21:28 1334
bool ImGui::CheckboxFlags(const char* label, ImU64* flags, ImU64 flags_value)
1193b4be Poul Sander 2025-02-03 21:28 1335
{
1193b4be Poul Sander 2025-02-03 21:28 1336
    return CheckboxFlagsT(label, flags, flags_value);
1193b4be Poul Sander 2025-02-03 21:28 1337
}
1193b4be Poul Sander 2025-02-03 21:28 1338
1193b4be Poul Sander 2025-02-03 21:28 1339
bool ImGui::RadioButton(const char* label, bool active)
1193b4be Poul Sander 2025-02-03 21:28 1340
{
1193b4be Poul Sander 2025-02-03 21:28 1341
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1342
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1343
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1344
1193b4be Poul Sander 2025-02-03 21:28 1345
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1346
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1347
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1348
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1349
1193b4be Poul Sander 2025-02-03 21:28 1350
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1351
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1352
    const ImRect check_bb(pos, pos + ImVec2(square_sz, square_sz));
1193b4be Poul Sander 2025-02-03 21:28 1353
    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));
1193b4be Poul Sander 2025-02-03 21:28 1354
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1355
    if (!ItemAdd(total_bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1356
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1357
1193b4be Poul Sander 2025-02-03 21:28 1358
    ImVec2 center = check_bb.GetCenter();
1193b4be Poul Sander 2025-02-03 21:28 1359
    center.x = IM_ROUND(center.x);
1193b4be Poul Sander 2025-02-03 21:28 1360
    center.y = IM_ROUND(center.y);
1193b4be Poul Sander 2025-02-03 21:28 1361
    const float radius = (square_sz - 1.0f) * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 1362
1193b4be Poul Sander 2025-02-03 21:28 1363
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1364
    bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1365
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1366
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1367
1193b4be Poul Sander 2025-02-03 21:28 1368
    RenderNavCursor(total_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1369
    const int num_segment = window->DrawList->_CalcCircleAutoSegmentCount(radius);
1193b4be Poul Sander 2025-02-03 21:28 1370
    window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), num_segment);
1193b4be Poul Sander 2025-02-03 21:28 1371
    if (active)
1193b4be Poul Sander 2025-02-03 21:28 1372
    {
1193b4be Poul Sander 2025-02-03 21:28 1373
        const float pad = ImMax(1.0f, IM_TRUNC(square_sz / 6.0f));
1193b4be Poul Sander 2025-02-03 21:28 1374
        window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark));
1193b4be Poul Sander 2025-02-03 21:28 1375
    }
1193b4be Poul Sander 2025-02-03 21:28 1376
1193b4be Poul Sander 2025-02-03 21:28 1377
    if (style.FrameBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1378
    {
1193b4be Poul Sander 2025-02-03 21:28 1379
        window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), num_segment, style.FrameBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 1380
        window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), num_segment, style.FrameBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 1381
    }
1193b4be Poul Sander 2025-02-03 21:28 1382
1193b4be Poul Sander 2025-02-03 21:28 1383
    ImVec2 label_pos = ImVec2(check_bb.Max.x + style.ItemInnerSpacing.x, check_bb.Min.y + style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1384
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1385
        LogRenderedText(&label_pos, active ? "(x)" : "( )");
1193b4be Poul Sander 2025-02-03 21:28 1386
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1387
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 1388
1193b4be Poul Sander 2025-02-03 21:28 1389
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 1390
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1391
}
1193b4be Poul Sander 2025-02-03 21:28 1392
1193b4be Poul Sander 2025-02-03 21:28 1393
// 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..
1193b4be Poul Sander 2025-02-03 21:28 1394
bool ImGui::RadioButton(const char* label, int* v, int v_button)
1193b4be Poul Sander 2025-02-03 21:28 1395
{
1193b4be Poul Sander 2025-02-03 21:28 1396
    const bool pressed = RadioButton(label, *v == v_button);
1193b4be Poul Sander 2025-02-03 21:28 1397
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 1398
        *v = v_button;
1193b4be Poul Sander 2025-02-03 21:28 1399
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1400
}
1193b4be Poul Sander 2025-02-03 21:28 1401
1193b4be Poul Sander 2025-02-03 21:28 1402
// size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
1193b4be Poul Sander 2025-02-03 21:28 1403
void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
1193b4be Poul Sander 2025-02-03 21:28 1404
{
1193b4be Poul Sander 2025-02-03 21:28 1405
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1406
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1407
        return;
1193b4be Poul Sander 2025-02-03 21:28 1408
1193b4be Poul Sander 2025-02-03 21:28 1409
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1410
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1411
1193b4be Poul Sander 2025-02-03 21:28 1412
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1413
    ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1414
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 1415
    ItemSize(size, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1416
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1417
        return;
1193b4be Poul Sander 2025-02-03 21:28 1418
1193b4be Poul Sander 2025-02-03 21:28 1419
    // Fraction < 0.0f will display an indeterminate progress bar animation
1193b4be Poul Sander 2025-02-03 21:28 1420
    // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works.
1193b4be Poul Sander 2025-02-03 21:28 1421
    const bool is_indeterminate = (fraction < 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1422
    if (!is_indeterminate)
1193b4be Poul Sander 2025-02-03 21:28 1423
        fraction = ImSaturate(fraction);
1193b4be Poul Sander 2025-02-03 21:28 1424
1193b4be Poul Sander 2025-02-03 21:28 1425
    // Out of courtesy we accept a NaN fraction without crashing
1193b4be Poul Sander 2025-02-03 21:28 1426
    float fill_n0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1427
    float fill_n1 = (fraction == fraction) ? fraction : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1428
1193b4be Poul Sander 2025-02-03 21:28 1429
    if (is_indeterminate)
1193b4be Poul Sander 2025-02-03 21:28 1430
    {
1193b4be Poul Sander 2025-02-03 21:28 1431
        const float fill_width_n = 0.2f;
1193b4be Poul Sander 2025-02-03 21:28 1432
        fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n;
1193b4be Poul Sander 2025-02-03 21:28 1433
        fill_n1 = ImSaturate(fill_n0 + fill_width_n);
1193b4be Poul Sander 2025-02-03 21:28 1434
        fill_n0 = ImSaturate(fill_n0);
1193b4be Poul Sander 2025-02-03 21:28 1435
    }
1193b4be Poul Sander 2025-02-03 21:28 1436
1193b4be Poul Sander 2025-02-03 21:28 1437
    // Render
1193b4be Poul Sander 2025-02-03 21:28 1438
    RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1439
    bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
1193b4be Poul Sander 2025-02-03 21:28 1440
    RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1441
1193b4be Poul Sander 2025-02-03 21:28 1442
    // Default displaying the fraction as percentage string, but user can override it
1193b4be Poul Sander 2025-02-03 21:28 1443
    // Don't display text for indeterminate bars by default
1193b4be Poul Sander 2025-02-03 21:28 1444
    char overlay_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 1445
    if (!is_indeterminate || overlay != NULL)
1193b4be Poul Sander 2025-02-03 21:28 1446
    {
1193b4be Poul Sander 2025-02-03 21:28 1447
        if (!overlay)
1193b4be Poul Sander 2025-02-03 21:28 1448
        {
1193b4be Poul Sander 2025-02-03 21:28 1449
            ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
1193b4be Poul Sander 2025-02-03 21:28 1450
            overlay = overlay_buf;
1193b4be Poul Sander 2025-02-03 21:28 1451
        }
1193b4be Poul Sander 2025-02-03 21:28 1452
1193b4be Poul Sander 2025-02-03 21:28 1453
        ImVec2 overlay_size = CalcTextSize(overlay, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1454
        if (overlay_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1455
        {
1193b4be Poul Sander 2025-02-03 21:28 1456
            float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 1457
            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);
1193b4be Poul Sander 2025-02-03 21:28 1458
        }
1193b4be Poul Sander 2025-02-03 21:28 1459
    }
1193b4be Poul Sander 2025-02-03 21:28 1460
}
1193b4be Poul Sander 2025-02-03 21:28 1461
1193b4be Poul Sander 2025-02-03 21:28 1462
void ImGui::Bullet()
1193b4be Poul Sander 2025-02-03 21:28 1463
{
1193b4be Poul Sander 2025-02-03 21:28 1464
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1465
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1466
        return;
1193b4be Poul Sander 2025-02-03 21:28 1467
1193b4be Poul Sander 2025-02-03 21:28 1468
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1469
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1470
    const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 1471
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
1193b4be Poul Sander 2025-02-03 21:28 1472
    ItemSize(bb);
1193b4be Poul Sander 2025-02-03 21:28 1473
    if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1474
    {
1193b4be Poul Sander 2025-02-03 21:28 1475
        SameLine(0, style.FramePadding.x * 2);
1193b4be Poul Sander 2025-02-03 21:28 1476
        return;
1193b4be Poul Sander 2025-02-03 21:28 1477
    }
1193b4be Poul Sander 2025-02-03 21:28 1478
1193b4be Poul Sander 2025-02-03 21:28 1479
    // Render and stay on same line
1193b4be Poul Sander 2025-02-03 21:28 1480
    ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 1481
    RenderBullet(window->DrawList, bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f), text_col);
1193b4be Poul Sander 2025-02-03 21:28 1482
    SameLine(0, style.FramePadding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1483
}
1193b4be Poul Sander 2025-02-03 21:28 1484
1193b4be Poul Sander 2025-02-03 21:28 1485
// This is provided as a convenience for being an often requested feature.
1193b4be Poul Sander 2025-02-03 21:28 1486
// FIXME-STYLE: we delayed adding as there is a larger plan to revamp the styling system.
1193b4be Poul Sander 2025-02-03 21:28 1487
// Because of this we currently don't provide many styling options for this widget
1193b4be Poul Sander 2025-02-03 21:28 1488
// (e.g. hovered/active colors are automatically inferred from a single color).
1193b4be Poul Sander 2025-02-03 21:28 1489
bool ImGui::TextLink(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 1490
{
1193b4be Poul Sander 2025-02-03 21:28 1491
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1492
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1493
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1494
1193b4be Poul Sander 2025-02-03 21:28 1495
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1496
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1497
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 1498
4e9e2555 Poul Sander 2025-10-05 10:15 1499
    ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 1500
    ImVec2 size = CalcTextSize(label, label_end, true);
1193b4be Poul Sander 2025-02-03 21:28 1501
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 1502
    ItemSize(size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1503
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1504
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1505
1193b4be Poul Sander 2025-02-03 21:28 1506
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1507
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1508
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1509
1193b4be Poul Sander 2025-02-03 21:28 1510
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 1511
        SetMouseCursor(ImGuiMouseCursor_Hand);
1193b4be Poul Sander 2025-02-03 21:28 1512
1193b4be Poul Sander 2025-02-03 21:28 1513
    ImVec4 text_colf = g.Style.Colors[ImGuiCol_TextLink];
1193b4be Poul Sander 2025-02-03 21:28 1514
    ImVec4 line_colf = text_colf;
1193b4be Poul Sander 2025-02-03 21:28 1515
    {
1193b4be Poul Sander 2025-02-03 21:28 1516
        // FIXME-STYLE: Read comments above. This widget is NOT written in the same style as some earlier widgets,
1193b4be Poul Sander 2025-02-03 21:28 1517
        // as we are currently experimenting/planning a different styling system.
1193b4be Poul Sander 2025-02-03 21:28 1518
        float h, s, v;
1193b4be Poul Sander 2025-02-03 21:28 1519
        ColorConvertRGBtoHSV(text_colf.x, text_colf.y, text_colf.z, h, s, v);
1193b4be Poul Sander 2025-02-03 21:28 1520
        if (held || hovered)
1193b4be Poul Sander 2025-02-03 21:28 1521
        {
1193b4be Poul Sander 2025-02-03 21:28 1522
            v = ImSaturate(v + (held ? 0.4f : 0.3f));
1193b4be Poul Sander 2025-02-03 21:28 1523
            h = ImFmod(h + 0.02f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1524
        }
1193b4be Poul Sander 2025-02-03 21:28 1525
        ColorConvertHSVtoRGB(h, s, v, text_colf.x, text_colf.y, text_colf.z);
1193b4be Poul Sander 2025-02-03 21:28 1526
        v = ImSaturate(v - 0.20f);
1193b4be Poul Sander 2025-02-03 21:28 1527
        ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
1193b4be Poul Sander 2025-02-03 21:28 1528
    }
1193b4be Poul Sander 2025-02-03 21:28 1529
4e9e2555 Poul Sander 2025-10-05 10:15 1530
    float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f);
4e9e2555 Poul Sander 2025-10-05 10:15 1531
    window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI
1193b4be Poul Sander 2025-02-03 21:28 1532
1193b4be Poul Sander 2025-02-03 21:28 1533
    PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
1193b4be Poul Sander 2025-02-03 21:28 1534
    RenderText(bb.Min, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 1535
    PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 1536
1193b4be Poul Sander 2025-02-03 21:28 1537
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 1538
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1539
}
1193b4be Poul Sander 2025-02-03 21:28 1540
4e9e2555 Poul Sander 2025-10-05 10:15 1541
bool ImGui::TextLinkOpenURL(const char* label, const char* url)
1193b4be Poul Sander 2025-02-03 21:28 1542
{
1193b4be Poul Sander 2025-02-03 21:28 1543
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1544
    if (url == NULL)
1193b4be Poul Sander 2025-02-03 21:28 1545
        url = label;
4e9e2555 Poul Sander 2025-10-05 10:15 1546
    bool pressed = TextLink(label);
4e9e2555 Poul Sander 2025-10-05 10:15 1547
    if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 1548
        g.PlatformIO.Platform_OpenInShellFn(&g, url);
1193b4be Poul Sander 2025-02-03 21:28 1549
    SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label
1193b4be Poul Sander 2025-02-03 21:28 1550
    if (BeginPopupContextItem())
1193b4be Poul Sander 2025-02-03 21:28 1551
    {
1193b4be Poul Sander 2025-02-03 21:28 1552
        if (MenuItem(LocalizeGetMsg(ImGuiLocKey_CopyLink)))
1193b4be Poul Sander 2025-02-03 21:28 1553
            SetClipboardText(url);
1193b4be Poul Sander 2025-02-03 21:28 1554
        EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 1555
    }
4e9e2555 Poul Sander 2025-10-05 10:15 1556
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 1557
}
1193b4be Poul Sander 2025-02-03 21:28 1558
1193b4be Poul Sander 2025-02-03 21:28 1559
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1560
// [SECTION] Widgets: Low-level Layout helpers
1193b4be Poul Sander 2025-02-03 21:28 1561
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1562
// - Spacing()
1193b4be Poul Sander 2025-02-03 21:28 1563
// - Dummy()
1193b4be Poul Sander 2025-02-03 21:28 1564
// - NewLine()
1193b4be Poul Sander 2025-02-03 21:28 1565
// - AlignTextToFramePadding()
1193b4be Poul Sander 2025-02-03 21:28 1566
// - SeparatorEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1567
// - Separator()
1193b4be Poul Sander 2025-02-03 21:28 1568
// - SplitterBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1569
// - ShrinkWidths() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1570
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1571
1193b4be Poul Sander 2025-02-03 21:28 1572
void ImGui::Spacing()
1193b4be Poul Sander 2025-02-03 21:28 1573
{
1193b4be Poul Sander 2025-02-03 21:28 1574
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1575
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1576
        return;
1193b4be Poul Sander 2025-02-03 21:28 1577
    ItemSize(ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 1578
}
1193b4be Poul Sander 2025-02-03 21:28 1579
1193b4be Poul Sander 2025-02-03 21:28 1580
void ImGui::Dummy(const ImVec2& size)
1193b4be Poul Sander 2025-02-03 21:28 1581
{
1193b4be Poul Sander 2025-02-03 21:28 1582
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1583
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1584
        return;
1193b4be Poul Sander 2025-02-03 21:28 1585
1193b4be Poul Sander 2025-02-03 21:28 1586
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 1587
    ItemSize(size);
1193b4be Poul Sander 2025-02-03 21:28 1588
    ItemAdd(bb, 0);
1193b4be Poul Sander 2025-02-03 21:28 1589
}
1193b4be Poul Sander 2025-02-03 21:28 1590
1193b4be Poul Sander 2025-02-03 21:28 1591
void ImGui::NewLine()
1193b4be Poul Sander 2025-02-03 21:28 1592
{
1193b4be Poul Sander 2025-02-03 21:28 1593
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1594
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1595
        return;
1193b4be Poul Sander 2025-02-03 21:28 1596
1193b4be Poul Sander 2025-02-03 21:28 1597
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1598
    const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
1193b4be Poul Sander 2025-02-03 21:28 1599
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1193b4be Poul Sander 2025-02-03 21:28 1600
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 1601
    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.
1193b4be Poul Sander 2025-02-03 21:28 1602
        ItemSize(ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 1603
    else
1193b4be Poul Sander 2025-02-03 21:28 1604
        ItemSize(ImVec2(0.0f, g.FontSize));
1193b4be Poul Sander 2025-02-03 21:28 1605
    window->DC.LayoutType = backup_layout_type;
1193b4be Poul Sander 2025-02-03 21:28 1606
}
1193b4be Poul Sander 2025-02-03 21:28 1607
1193b4be Poul Sander 2025-02-03 21:28 1608
void ImGui::AlignTextToFramePadding()
1193b4be Poul Sander 2025-02-03 21:28 1609
{
1193b4be Poul Sander 2025-02-03 21:28 1610
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1611
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1612
        return;
1193b4be Poul Sander 2025-02-03 21:28 1613
1193b4be Poul Sander 2025-02-03 21:28 1614
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1615
    window->DC.CurrLineSize.y = ImMax(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2);
1193b4be Poul Sander 2025-02-03 21:28 1616
    window->DC.CurrLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, g.Style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1617
}
1193b4be Poul Sander 2025-02-03 21:28 1618
1193b4be Poul Sander 2025-02-03 21:28 1619
// Horizontal/vertical separating line
1193b4be Poul Sander 2025-02-03 21:28 1620
// FIXME: Surprisingly, this seemingly trivial widget is a victim of many different legacy/tricky layout issues.
1193b4be Poul Sander 2025-02-03 21:28 1621
// Note how thickness == 1.0f is handled specifically as not moving CursorPos by 'thickness', but other values are.
1193b4be Poul Sander 2025-02-03 21:28 1622
void ImGui::SeparatorEx(ImGuiSeparatorFlags flags, float thickness)
1193b4be Poul Sander 2025-02-03 21:28 1623
{
1193b4be Poul Sander 2025-02-03 21:28 1624
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1625
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1626
        return;
1193b4be Poul Sander 2025-02-03 21:28 1627
1193b4be Poul Sander 2025-02-03 21:28 1628
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1629
    IM_ASSERT(ImIsPowerOfTwo(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)));   // Check that only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 1630
    IM_ASSERT(thickness > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1631
1193b4be Poul Sander 2025-02-03 21:28 1632
    if (flags & ImGuiSeparatorFlags_Vertical)
1193b4be Poul Sander 2025-02-03 21:28 1633
    {
1193b4be Poul Sander 2025-02-03 21:28 1634
        // Vertical separator, for menu bars (use current line height).
1193b4be Poul Sander 2025-02-03 21:28 1635
        float y1 = window->DC.CursorPos.y;
1193b4be Poul Sander 2025-02-03 21:28 1636
        float y2 = window->DC.CursorPos.y + window->DC.CurrLineSize.y;
1193b4be Poul Sander 2025-02-03 21:28 1637
        const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + thickness, y2));
1193b4be Poul Sander 2025-02-03 21:28 1638
        ItemSize(ImVec2(thickness, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 1639
        if (!ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1640
            return;
1193b4be Poul Sander 2025-02-03 21:28 1641
1193b4be Poul Sander 2025-02-03 21:28 1642
        // Draw
1193b4be Poul Sander 2025-02-03 21:28 1643
        window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1193b4be Poul Sander 2025-02-03 21:28 1644
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1645
            LogText(" |");
1193b4be Poul Sander 2025-02-03 21:28 1646
    }
1193b4be Poul Sander 2025-02-03 21:28 1647
    else if (flags & ImGuiSeparatorFlags_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 1648
    {
1193b4be Poul Sander 2025-02-03 21:28 1649
        // Horizontal Separator
1193b4be Poul Sander 2025-02-03 21:28 1650
        float x1 = window->DC.CursorPos.x;
1193b4be Poul Sander 2025-02-03 21:28 1651
        float x2 = window->WorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 1652
1193b4be Poul Sander 2025-02-03 21:28 1653
        // Preserve legacy behavior inside Columns()
1193b4be Poul Sander 2025-02-03 21:28 1654
        // Before Tables API happened, we relied on Separator() to span all columns of a Columns() set.
1193b4be Poul Sander 2025-02-03 21:28 1655
        // We currently don't need to provide the same feature for tables because tables naturally have border features.
1193b4be Poul Sander 2025-02-03 21:28 1656
        ImGuiOldColumns* columns = (flags & ImGuiSeparatorFlags_SpanAllColumns) ? window->DC.CurrentColumns : NULL;
1193b4be Poul Sander 2025-02-03 21:28 1657
        if (columns)
1193b4be Poul Sander 2025-02-03 21:28 1658
        {
1193b4be Poul Sander 2025-02-03 21:28 1659
            x1 = window->Pos.x + window->DC.Indent.x; // Used to be Pos.x before 2023/10/03
1193b4be Poul Sander 2025-02-03 21:28 1660
            x2 = window->Pos.x + window->Size.x;
1193b4be Poul Sander 2025-02-03 21:28 1661
            PushColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 1662
        }
1193b4be Poul Sander 2025-02-03 21:28 1663
1193b4be Poul Sander 2025-02-03 21:28 1664
        // We don't provide our width to the layout so that it doesn't get feed back into AutoFit
1193b4be Poul Sander 2025-02-03 21:28 1665
        // FIXME: This prevents ->CursorMaxPos based bounding box evaluation from working (e.g. TableEndCell)
1193b4be Poul Sander 2025-02-03 21:28 1666
        const float thickness_for_layout = (thickness == 1.0f) ? 0.0f : thickness; // FIXME: See 1.70/1.71 Separator() change: makes legacy 1-px separator not affect layout yet. Should change.
1193b4be Poul Sander 2025-02-03 21:28 1667
        const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + thickness));
1193b4be Poul Sander 2025-02-03 21:28 1668
        ItemSize(ImVec2(0.0f, thickness_for_layout));
1193b4be Poul Sander 2025-02-03 21:28 1669
1193b4be Poul Sander 2025-02-03 21:28 1670
        if (ItemAdd(bb, 0))
1193b4be Poul Sander 2025-02-03 21:28 1671
        {
1193b4be Poul Sander 2025-02-03 21:28 1672
            // Draw
1193b4be Poul Sander 2025-02-03 21:28 1673
            window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator));
1193b4be Poul Sander 2025-02-03 21:28 1674
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1675
                LogRenderedText(&bb.Min, "--------------------------------\n");
1193b4be Poul Sander 2025-02-03 21:28 1676
1193b4be Poul Sander 2025-02-03 21:28 1677
        }
1193b4be Poul Sander 2025-02-03 21:28 1678
        if (columns)
1193b4be Poul Sander 2025-02-03 21:28 1679
        {
1193b4be Poul Sander 2025-02-03 21:28 1680
            PopColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 1681
            columns->LineMinY = window->DC.CursorPos.y;
1193b4be Poul Sander 2025-02-03 21:28 1682
        }
1193b4be Poul Sander 2025-02-03 21:28 1683
    }
1193b4be Poul Sander 2025-02-03 21:28 1684
}
1193b4be Poul Sander 2025-02-03 21:28 1685
1193b4be Poul Sander 2025-02-03 21:28 1686
void ImGui::Separator()
1193b4be Poul Sander 2025-02-03 21:28 1687
{
1193b4be Poul Sander 2025-02-03 21:28 1688
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1689
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1690
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1691
        return;
1193b4be Poul Sander 2025-02-03 21:28 1692
1193b4be Poul Sander 2025-02-03 21:28 1693
    // Those flags should eventually be configurable by the user
1193b4be Poul Sander 2025-02-03 21:28 1694
    // FIXME: We cannot g.Style.SeparatorTextBorderSize for thickness as it relates to SeparatorText() which is a decorated separator, not defaulting to 1.0f.
1193b4be Poul Sander 2025-02-03 21:28 1695
    ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 1696
1193b4be Poul Sander 2025-02-03 21:28 1697
    // Only applies to legacy Columns() api as they relied on Separator() a lot.
1193b4be Poul Sander 2025-02-03 21:28 1698
    if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 1699
        flags |= ImGuiSeparatorFlags_SpanAllColumns;
1193b4be Poul Sander 2025-02-03 21:28 1700
1193b4be Poul Sander 2025-02-03 21:28 1701
    SeparatorEx(flags, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1702
}
1193b4be Poul Sander 2025-02-03 21:28 1703
1193b4be Poul Sander 2025-02-03 21:28 1704
void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_w)
1193b4be Poul Sander 2025-02-03 21:28 1705
{
1193b4be Poul Sander 2025-02-03 21:28 1706
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1707
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1708
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1709
1193b4be Poul Sander 2025-02-03 21:28 1710
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1193b4be Poul Sander 2025-02-03 21:28 1711
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 1712
    const ImVec2 padding = style.SeparatorTextPadding;
1193b4be Poul Sander 2025-02-03 21:28 1713
1193b4be Poul Sander 2025-02-03 21:28 1714
    const float separator_thickness = style.SeparatorTextBorderSize;
1193b4be Poul Sander 2025-02-03 21:28 1715
    const ImVec2 min_size(label_size.x + extra_w + padding.x * 2.0f, ImMax(label_size.y + padding.y * 2.0f, separator_thickness));
1193b4be Poul Sander 2025-02-03 21:28 1716
    const ImRect bb(pos, ImVec2(window->WorkRect.Max.x, pos.y + min_size.y));
1193b4be Poul Sander 2025-02-03 21:28 1717
    const float text_baseline_y = ImTrunc((bb.GetHeight() - label_size.y) * style.SeparatorTextAlign.y + 0.99999f); //ImMax(padding.y, ImTrunc((style.SeparatorTextSize - label_size.y) * 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 1718
    ItemSize(min_size, text_baseline_y);
1193b4be Poul Sander 2025-02-03 21:28 1719
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 1720
        return;
1193b4be Poul Sander 2025-02-03 21:28 1721
1193b4be Poul Sander 2025-02-03 21:28 1722
    const float sep1_x1 = pos.x;
1193b4be Poul Sander 2025-02-03 21:28 1723
    const float sep2_x2 = bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 1724
    const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.99999f);
1193b4be Poul Sander 2025-02-03 21:28 1725
1193b4be Poul Sander 2025-02-03 21:28 1726
    const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 1727
    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
1193b4be Poul Sander 2025-02-03 21:28 1728
1193b4be Poul Sander 2025-02-03 21:28 1729
    // This allows using SameLine() to position something in the 'extra_w'
1193b4be Poul Sander 2025-02-03 21:28 1730
    window->DC.CursorPosPrevLine.x = label_pos.x + label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 1731
1193b4be Poul Sander 2025-02-03 21:28 1732
    const ImU32 separator_col = GetColorU32(ImGuiCol_Separator);
1193b4be Poul Sander 2025-02-03 21:28 1733
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1734
    {
1193b4be Poul Sander 2025-02-03 21:28 1735
        const float sep1_x2 = label_pos.x - style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 1736
        const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 1737
        if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1738
            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep1_x2, seps_y), separator_col, separator_thickness);
1193b4be Poul Sander 2025-02-03 21:28 1739
        if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1740
            window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1193b4be Poul Sander 2025-02-03 21:28 1741
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1742
            LogSetNextTextDecoration("---", NULL);
4e9e2555 Poul Sander 2025-10-05 10:15 1743
        RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 1744
    }
1193b4be Poul Sander 2025-02-03 21:28 1745
    else
1193b4be Poul Sander 2025-02-03 21:28 1746
    {
1193b4be Poul Sander 2025-02-03 21:28 1747
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1748
            LogText("---");
1193b4be Poul Sander 2025-02-03 21:28 1749
        if (separator_thickness > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1750
            window->DrawList->AddLine(ImVec2(sep1_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
1193b4be Poul Sander 2025-02-03 21:28 1751
    }
1193b4be Poul Sander 2025-02-03 21:28 1752
}
1193b4be Poul Sander 2025-02-03 21:28 1753
1193b4be Poul Sander 2025-02-03 21:28 1754
void ImGui::SeparatorText(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 1755
{
1193b4be Poul Sander 2025-02-03 21:28 1756
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1757
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1758
        return;
1193b4be Poul Sander 2025-02-03 21:28 1759
1193b4be Poul Sander 2025-02-03 21:28 1760
    // The SeparatorText() vs SeparatorTextEx() distinction is designed to be considerate that we may want:
1193b4be Poul Sander 2025-02-03 21:28 1761
    // - allow separator-text to be draggable items (would require a stable ID + a noticeable highlight)
1193b4be Poul Sander 2025-02-03 21:28 1762
    // - this high-level entry point to allow formatting? (which in turns may require ID separate from formatted string)
1193b4be Poul Sander 2025-02-03 21:28 1763
    // - because of this we probably can't turn 'const char* label' into 'const char* fmt, ...'
1193b4be Poul Sander 2025-02-03 21:28 1764
    // Otherwise, we can decide that users wanting to drag this would layout a dedicated drag-item,
1193b4be Poul Sander 2025-02-03 21:28 1765
    // and then we can turn this into a format function.
1193b4be Poul Sander 2025-02-03 21:28 1766
    SeparatorTextEx(0, label, FindRenderedTextEnd(label), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1767
}
1193b4be Poul Sander 2025-02-03 21:28 1768
1193b4be Poul Sander 2025-02-03 21:28 1769
// 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.
1193b4be Poul Sander 2025-02-03 21:28 1770
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)
1193b4be Poul Sander 2025-02-03 21:28 1771
{
1193b4be Poul Sander 2025-02-03 21:28 1772
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1773
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 1774
1193b4be Poul Sander 2025-02-03 21:28 1775
    if (!ItemAdd(bb, id, NULL, ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 1776
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1777
1193b4be Poul Sander 2025-02-03 21:28 1778
    // FIXME: AFAIK the only leftover reason for passing ImGuiButtonFlags_AllowOverlap here is
1193b4be Poul Sander 2025-02-03 21:28 1779
    // to allow caller of SplitterBehavior() to call SetItemAllowOverlap() after the item.
1193b4be Poul Sander 2025-02-03 21:28 1780
    // Nowadays we would instead want to use SetNextItemAllowOverlap() before the item.
1193b4be Poul Sander 2025-02-03 21:28 1781
    ImGuiButtonFlags button_flags = ImGuiButtonFlags_FlattenChildren;
1193b4be Poul Sander 2025-02-03 21:28 1782
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 1783
    button_flags |= ImGuiButtonFlags_AllowOverlap;
1193b4be Poul Sander 2025-02-03 21:28 1784
#endif
1193b4be Poul Sander 2025-02-03 21:28 1785
1193b4be Poul Sander 2025-02-03 21:28 1786
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1787
    ImRect bb_interact = bb;
1193b4be Poul Sander 2025-02-03 21:28 1788
    bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 1789
    ButtonBehavior(bb_interact, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 1790
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 1791
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; // for IsItemHovered(), because bb_interact is larger than bb
1193b4be Poul Sander 2025-02-03 21:28 1792
1193b4be Poul Sander 2025-02-03 21:28 1793
    if (held || (hovered && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
1193b4be Poul Sander 2025-02-03 21:28 1794
        SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
1193b4be Poul Sander 2025-02-03 21:28 1795
1193b4be Poul Sander 2025-02-03 21:28 1796
    ImRect bb_render = bb;
1193b4be Poul Sander 2025-02-03 21:28 1797
    if (held)
1193b4be Poul Sander 2025-02-03 21:28 1798
    {
1193b4be Poul Sander 2025-02-03 21:28 1799
        float mouse_delta = (g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min)[axis];
1193b4be Poul Sander 2025-02-03 21:28 1800
1193b4be Poul Sander 2025-02-03 21:28 1801
        // Minimum pane size
1193b4be Poul Sander 2025-02-03 21:28 1802
        float size_1_maximum_delta = ImMax(0.0f, *size1 - min_size1);
1193b4be Poul Sander 2025-02-03 21:28 1803
        float size_2_maximum_delta = ImMax(0.0f, *size2 - min_size2);
1193b4be Poul Sander 2025-02-03 21:28 1804
        if (mouse_delta < -size_1_maximum_delta)
1193b4be Poul Sander 2025-02-03 21:28 1805
            mouse_delta = -size_1_maximum_delta;
1193b4be Poul Sander 2025-02-03 21:28 1806
        if (mouse_delta > size_2_maximum_delta)
1193b4be Poul Sander 2025-02-03 21:28 1807
            mouse_delta = size_2_maximum_delta;
1193b4be Poul Sander 2025-02-03 21:28 1808
1193b4be Poul Sander 2025-02-03 21:28 1809
        // Apply resize
1193b4be Poul Sander 2025-02-03 21:28 1810
        if (mouse_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1811
        {
1193b4be Poul Sander 2025-02-03 21:28 1812
            *size1 = ImMax(*size1 + mouse_delta, min_size1);
1193b4be Poul Sander 2025-02-03 21:28 1813
            *size2 = ImMax(*size2 - mouse_delta, min_size2);
1193b4be Poul Sander 2025-02-03 21:28 1814
            bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
1193b4be Poul Sander 2025-02-03 21:28 1815
            MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 1816
        }
1193b4be Poul Sander 2025-02-03 21:28 1817
    }
1193b4be Poul Sander 2025-02-03 21:28 1818
1193b4be Poul Sander 2025-02-03 21:28 1819
    // Render at new position
1193b4be Poul Sander 2025-02-03 21:28 1820
    if (bg_col & IM_COL32_A_MASK)
1193b4be Poul Sander 2025-02-03 21:28 1821
        window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, bg_col, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1822
    const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
1193b4be Poul Sander 2025-02-03 21:28 1823
    window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 1824
1193b4be Poul Sander 2025-02-03 21:28 1825
    return held;
1193b4be Poul Sander 2025-02-03 21:28 1826
}
1193b4be Poul Sander 2025-02-03 21:28 1827
1193b4be Poul Sander 2025-02-03 21:28 1828
static int IMGUI_CDECL ShrinkWidthItemComparer(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 1829
{
1193b4be Poul Sander 2025-02-03 21:28 1830
    const ImGuiShrinkWidthItem* a = (const ImGuiShrinkWidthItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 1831
    const ImGuiShrinkWidthItem* b = (const ImGuiShrinkWidthItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 1832
    if (int d = (int)(b->Width - a->Width))
1193b4be Poul Sander 2025-02-03 21:28 1833
        return d;
1193b4be Poul Sander 2025-02-03 21:28 1834
    return (b->Index - a->Index);
1193b4be Poul Sander 2025-02-03 21:28 1835
}
1193b4be Poul Sander 2025-02-03 21:28 1836
1193b4be Poul Sander 2025-02-03 21:28 1837
// Shrink excess width from a set of item, by removing width from the larger items first.
1193b4be Poul Sander 2025-02-03 21:28 1838
// Set items Width to -1.0f to disable shrinking this item.
4e9e2555 Poul Sander 2025-10-05 10:15 1839
void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min)
1193b4be Poul Sander 2025-02-03 21:28 1840
{
1193b4be Poul Sander 2025-02-03 21:28 1841
    if (count == 1)
1193b4be Poul Sander 2025-02-03 21:28 1842
    {
1193b4be Poul Sander 2025-02-03 21:28 1843
        if (items[0].Width >= 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 1844
            items[0].Width = ImMax(items[0].Width - width_excess, width_min);
1193b4be Poul Sander 2025-02-03 21:28 1845
        return;
1193b4be Poul Sander 2025-02-03 21:28 1846
    }
4e9e2555 Poul Sander 2025-10-05 10:15 1847
    ImQsort(items, (size_t)count, sizeof(ImGuiShrinkWidthItem), ShrinkWidthItemComparer); // Sort largest first, smallest last.
1193b4be Poul Sander 2025-02-03 21:28 1848
    int count_same_width = 1;
4e9e2555 Poul Sander 2025-10-05 10:15 1849
    while (width_excess > 0.001f && count_same_width < count)
1193b4be Poul Sander 2025-02-03 21:28 1850
    {
1193b4be Poul Sander 2025-02-03 21:28 1851
        while (count_same_width < count && items[0].Width <= items[count_same_width].Width)
1193b4be Poul Sander 2025-02-03 21:28 1852
            count_same_width++;
1193b4be Poul Sander 2025-02-03 21:28 1853
        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);
4e9e2555 Poul Sander 2025-10-05 10:15 1854
        max_width_to_remove_per_item = ImMin(items[0].Width - width_min, max_width_to_remove_per_item);
1193b4be Poul Sander 2025-02-03 21:28 1855
        if (max_width_to_remove_per_item <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1856
            break;
4e9e2555 Poul Sander 2025-10-05 10:15 1857
        float base_width_to_remove_per_item = ImMin(width_excess / count_same_width, max_width_to_remove_per_item);
1193b4be Poul Sander 2025-02-03 21:28 1858
        for (int item_n = 0; item_n < count_same_width; item_n++)
4e9e2555 Poul Sander 2025-10-05 10:15 1859
        {
4e9e2555 Poul Sander 2025-10-05 10:15 1860
            float width_to_remove_for_this_item = ImMin(base_width_to_remove_per_item, items[item_n].Width - width_min);
4e9e2555 Poul Sander 2025-10-05 10:15 1861
            items[item_n].Width -= width_to_remove_for_this_item;
4e9e2555 Poul Sander 2025-10-05 10:15 1862
            width_excess -= width_to_remove_for_this_item;
4e9e2555 Poul Sander 2025-10-05 10:15 1863
        }
1193b4be Poul Sander 2025-02-03 21:28 1864
    }
1193b4be Poul Sander 2025-02-03 21:28 1865
1193b4be Poul Sander 2025-02-03 21:28 1866
    // Round width and redistribute remainder
1193b4be Poul Sander 2025-02-03 21:28 1867
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 1868
    width_excess = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1869
    for (int n = 0; n < count; n++)
1193b4be Poul Sander 2025-02-03 21:28 1870
    {
1193b4be Poul Sander 2025-02-03 21:28 1871
        float width_rounded = ImTrunc(items[n].Width);
1193b4be Poul Sander 2025-02-03 21:28 1872
        width_excess += items[n].Width - width_rounded;
1193b4be Poul Sander 2025-02-03 21:28 1873
        items[n].Width = width_rounded;
1193b4be Poul Sander 2025-02-03 21:28 1874
    }
1193b4be Poul Sander 2025-02-03 21:28 1875
    while (width_excess > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 1876
        for (int n = 0; n < count && width_excess > 0.0f; n++)
1193b4be Poul Sander 2025-02-03 21:28 1877
        {
1193b4be Poul Sander 2025-02-03 21:28 1878
            float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1879
            items[n].Width += width_to_add;
1193b4be Poul Sander 2025-02-03 21:28 1880
            width_excess -= width_to_add;
1193b4be Poul Sander 2025-02-03 21:28 1881
        }
1193b4be Poul Sander 2025-02-03 21:28 1882
}
1193b4be Poul Sander 2025-02-03 21:28 1883
1193b4be Poul Sander 2025-02-03 21:28 1884
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1885
// [SECTION] Widgets: ComboBox
1193b4be Poul Sander 2025-02-03 21:28 1886
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1887
// - CalcMaxPopupHeightFromItemCount() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1888
// - BeginCombo()
1193b4be Poul Sander 2025-02-03 21:28 1889
// - BeginComboPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1890
// - EndCombo()
1193b4be Poul Sander 2025-02-03 21:28 1891
// - BeginComboPreview() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1892
// - EndComboPreview() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 1893
// - Combo()
1193b4be Poul Sander 2025-02-03 21:28 1894
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 1895
1193b4be Poul Sander 2025-02-03 21:28 1896
static float CalcMaxPopupHeightFromItemCount(int items_count)
1193b4be Poul Sander 2025-02-03 21:28 1897
{
1193b4be Poul Sander 2025-02-03 21:28 1898
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1899
    if (items_count <= 0)
1193b4be Poul Sander 2025-02-03 21:28 1900
        return FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 1901
    return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
1193b4be Poul Sander 2025-02-03 21:28 1902
}
1193b4be Poul Sander 2025-02-03 21:28 1903
1193b4be Poul Sander 2025-02-03 21:28 1904
bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 1905
{
1193b4be Poul Sander 2025-02-03 21:28 1906
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1907
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 1908
4e9e2555 Poul Sander 2025-10-05 10:15 1909
    ImGuiNextWindowDataFlags backup_next_window_data_flags = g.NextWindowData.HasFlags;
1193b4be Poul Sander 2025-02-03 21:28 1910
    g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 1911
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 1912
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1913
1193b4be Poul Sander 2025-02-03 21:28 1914
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 1915
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 1916
    IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
1193b4be Poul Sander 2025-02-03 21:28 1917
    if (flags & ImGuiComboFlags_WidthFitPreview)
1193b4be Poul Sander 2025-02-03 21:28 1918
        IM_ASSERT((flags & (ImGuiComboFlags_NoPreview | (ImGuiComboFlags)ImGuiComboFlags_CustomPreview)) == 0);
1193b4be Poul Sander 2025-02-03 21:28 1919
1193b4be Poul Sander 2025-02-03 21:28 1920
    const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 1921
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 1922
    const float preview_width = ((flags & ImGuiComboFlags_WidthFitPreview) && (preview_value != NULL)) ? CalcTextSize(preview_value, NULL, true).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 1923
    const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : ((flags & ImGuiComboFlags_WidthFitPreview) ? (arrow_size + preview_width + style.FramePadding.x * 2.0f) : CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 1924
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 1925
    const ImRect total_bb(bb.Min, bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 1926
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 1927
    if (!ItemAdd(total_bb, id, &bb))
1193b4be Poul Sander 2025-02-03 21:28 1928
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1929
1193b4be Poul Sander 2025-02-03 21:28 1930
    // Open on click
1193b4be Poul Sander 2025-02-03 21:28 1931
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 1932
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 1933
    const ImGuiID popup_id = ImHashStr("##ComboPopup", 0, id);
1193b4be Poul Sander 2025-02-03 21:28 1934
    bool popup_open = IsPopupOpen(popup_id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 1935
    if (pressed && !popup_open)
1193b4be Poul Sander 2025-02-03 21:28 1936
    {
1193b4be Poul Sander 2025-02-03 21:28 1937
        OpenPopupEx(popup_id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 1938
        popup_open = true;
1193b4be Poul Sander 2025-02-03 21:28 1939
    }
1193b4be Poul Sander 2025-02-03 21:28 1940
1193b4be Poul Sander 2025-02-03 21:28 1941
    // Render shape
1193b4be Poul Sander 2025-02-03 21:28 1942
    const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 1943
    const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size);
1193b4be Poul Sander 2025-02-03 21:28 1944
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 1945
    if (!(flags & ImGuiComboFlags_NoPreview))
1193b4be Poul Sander 2025-02-03 21:28 1946
        window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft);
1193b4be Poul Sander 2025-02-03 21:28 1947
    if (!(flags & ImGuiComboFlags_NoArrowButton))
1193b4be Poul Sander 2025-02-03 21:28 1948
    {
1193b4be Poul Sander 2025-02-03 21:28 1949
        ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
1193b4be Poul Sander 2025-02-03 21:28 1950
        ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 1951
        window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight);
1193b4be Poul Sander 2025-02-03 21:28 1952
        if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 1953
            RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 1954
    }
1193b4be Poul Sander 2025-02-03 21:28 1955
    RenderFrameBorder(bb.Min, bb.Max, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 1956
1193b4be Poul Sander 2025-02-03 21:28 1957
    // Custom preview
1193b4be Poul Sander 2025-02-03 21:28 1958
    if (flags & ImGuiComboFlags_CustomPreview)
1193b4be Poul Sander 2025-02-03 21:28 1959
    {
1193b4be Poul Sander 2025-02-03 21:28 1960
        g.ComboPreviewData.PreviewRect = ImRect(bb.Min.x, bb.Min.y, value_x2, bb.Max.y);
1193b4be Poul Sander 2025-02-03 21:28 1961
        IM_ASSERT(preview_value == NULL || preview_value[0] == 0);
1193b4be Poul Sander 2025-02-03 21:28 1962
        preview_value = NULL;
1193b4be Poul Sander 2025-02-03 21:28 1963
    }
1193b4be Poul Sander 2025-02-03 21:28 1964
1193b4be Poul Sander 2025-02-03 21:28 1965
    // Render preview and label
1193b4be Poul Sander 2025-02-03 21:28 1966
    if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
1193b4be Poul Sander 2025-02-03 21:28 1967
    {
1193b4be Poul Sander 2025-02-03 21:28 1968
        if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 1969
            LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 1970
        RenderTextClipped(bb.Min + style.FramePadding, ImVec2(value_x2, bb.Max.y), preview_value, NULL, NULL);
1193b4be Poul Sander 2025-02-03 21:28 1971
    }
1193b4be Poul Sander 2025-02-03 21:28 1972
    if (label_size.x > 0)
1193b4be Poul Sander 2025-02-03 21:28 1973
        RenderText(ImVec2(bb.Max.x + style.ItemInnerSpacing.x, bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 1974
1193b4be Poul Sander 2025-02-03 21:28 1975
    if (!popup_open)
1193b4be Poul Sander 2025-02-03 21:28 1976
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1977
4e9e2555 Poul Sander 2025-10-05 10:15 1978
    g.NextWindowData.HasFlags = backup_next_window_data_flags;
1193b4be Poul Sander 2025-02-03 21:28 1979
    return BeginComboPopup(popup_id, bb, flags);
1193b4be Poul Sander 2025-02-03 21:28 1980
}
1193b4be Poul Sander 2025-02-03 21:28 1981
1193b4be Poul Sander 2025-02-03 21:28 1982
bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 1983
{
1193b4be Poul Sander 2025-02-03 21:28 1984
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 1985
    if (!IsPopupOpen(popup_id, ImGuiPopupFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 1986
    {
1193b4be Poul Sander 2025-02-03 21:28 1987
        g.NextWindowData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 1988
        return false;
1193b4be Poul Sander 2025-02-03 21:28 1989
    }
1193b4be Poul Sander 2025-02-03 21:28 1990
1193b4be Poul Sander 2025-02-03 21:28 1991
    // Set popup size
1193b4be Poul Sander 2025-02-03 21:28 1992
    float w = bb.GetWidth();
4e9e2555 Poul Sander 2025-10-05 10:15 1993
    if (g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint)
1193b4be Poul Sander 2025-02-03 21:28 1994
    {
1193b4be Poul Sander 2025-02-03 21:28 1995
        g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
1193b4be Poul Sander 2025-02-03 21:28 1996
    }
1193b4be Poul Sander 2025-02-03 21:28 1997
    else
1193b4be Poul Sander 2025-02-03 21:28 1998
    {
1193b4be Poul Sander 2025-02-03 21:28 1999
        if ((flags & ImGuiComboFlags_HeightMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 2000
            flags |= ImGuiComboFlags_HeightRegular;
1193b4be Poul Sander 2025-02-03 21:28 2001
        IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
1193b4be Poul Sander 2025-02-03 21:28 2002
        int popup_max_height_in_items = -1;
1193b4be Poul Sander 2025-02-03 21:28 2003
        if (flags & ImGuiComboFlags_HeightRegular)     popup_max_height_in_items = 8;
1193b4be Poul Sander 2025-02-03 21:28 2004
        else if (flags & ImGuiComboFlags_HeightSmall)  popup_max_height_in_items = 4;
1193b4be Poul Sander 2025-02-03 21:28 2005
        else if (flags & ImGuiComboFlags_HeightLarge)  popup_max_height_in_items = 20;
1193b4be Poul Sander 2025-02-03 21:28 2006
        ImVec2 constraint_min(0.0f, 0.0f), constraint_max(FLT_MAX, FLT_MAX);
4e9e2555 Poul Sander 2025-10-05 10:15 2007
        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.x <= 0.0f) // Don't apply constraints if user specified a size
1193b4be Poul Sander 2025-02-03 21:28 2008
            constraint_min.x = w;
4e9e2555 Poul Sander 2025-10-05 10:15 2009
        if ((g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSize) == 0 || g.NextWindowData.SizeVal.y <= 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2010
            constraint_max.y = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2011
        SetNextWindowSizeConstraints(constraint_min, constraint_max);
1193b4be Poul Sander 2025-02-03 21:28 2012
    }
1193b4be Poul Sander 2025-02-03 21:28 2013
1193b4be Poul Sander 2025-02-03 21:28 2014
    // This is essentially a specialized version of BeginPopupEx()
1193b4be Poul Sander 2025-02-03 21:28 2015
    char name[16];
1193b4be Poul Sander 2025-02-03 21:28 2016
    ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth
1193b4be Poul Sander 2025-02-03 21:28 2017
1193b4be Poul Sander 2025-02-03 21:28 2018
    // Set position given a custom constraint (peak into expected window size so we can position it)
1193b4be Poul Sander 2025-02-03 21:28 2019
    // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function?
1193b4be Poul Sander 2025-02-03 21:28 2020
    // FIXME: This might be moved to Begin() or at least around the same spot where Tooltips and other Popups are calling FindBestWindowPosForPopupEx()?
1193b4be Poul Sander 2025-02-03 21:28 2021
    if (ImGuiWindow* popup_window = FindWindowByName(name))
1193b4be Poul Sander 2025-02-03 21:28 2022
        if (popup_window->WasActive)
1193b4be Poul Sander 2025-02-03 21:28 2023
        {
1193b4be Poul Sander 2025-02-03 21:28 2024
            // Always override 'AutoPosLastDirection' to not leave a chance for a past value to affect us.
1193b4be Poul Sander 2025-02-03 21:28 2025
            ImVec2 size_expected = CalcWindowNextAutoFitSize(popup_window);
1193b4be Poul Sander 2025-02-03 21:28 2026
            popup_window->AutoPosLastDirection = (flags & ImGuiComboFlags_PopupAlignLeft) ? ImGuiDir_Left : ImGuiDir_Down; // Left = "Below, Toward Left", Down = "Below, Toward Right (default)"
1193b4be Poul Sander 2025-02-03 21:28 2027
            ImRect r_outer = GetPopupAllowedExtentRect(popup_window);
1193b4be Poul Sander 2025-02-03 21:28 2028
            ImVec2 pos = FindBestWindowPosForPopupEx(bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, bb, ImGuiPopupPositionPolicy_ComboBox);
1193b4be Poul Sander 2025-02-03 21:28 2029
            SetNextWindowPos(pos);
1193b4be Poul Sander 2025-02-03 21:28 2030
        }
1193b4be Poul Sander 2025-02-03 21:28 2031
1193b4be Poul Sander 2025-02-03 21:28 2032
    // We don't use BeginPopupEx() solely because we have a custom name string, which we could make an argument to BeginPopupEx()
1193b4be Poul Sander 2025-02-03 21:28 2033
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoMove;
1193b4be Poul Sander 2025-02-03 21:28 2034
    PushStyleVarX(ImGuiStyleVar_WindowPadding, g.Style.FramePadding.x); // Horizontally align ourselves with the framed text
1193b4be Poul Sander 2025-02-03 21:28 2035
    bool ret = Begin(name, NULL, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 2036
    PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 2037
    if (!ret)
1193b4be Poul Sander 2025-02-03 21:28 2038
    {
1193b4be Poul Sander 2025-02-03 21:28 2039
        EndPopup();
4e9e2555 Poul Sander 2025-10-05 10:15 2040
        if (!g.IO.ConfigDebugBeginReturnValueOnce && !g.IO.ConfigDebugBeginReturnValueLoop) // Begin may only return false with those debug tools activated.
4e9e2555 Poul Sander 2025-10-05 10:15 2041
            IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
1193b4be Poul Sander 2025-02-03 21:28 2042
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2043
    }
1193b4be Poul Sander 2025-02-03 21:28 2044
    g.BeginComboDepth++;
1193b4be Poul Sander 2025-02-03 21:28 2045
    return true;
1193b4be Poul Sander 2025-02-03 21:28 2046
}
1193b4be Poul Sander 2025-02-03 21:28 2047
1193b4be Poul Sander 2025-02-03 21:28 2048
void ImGui::EndCombo()
1193b4be Poul Sander 2025-02-03 21:28 2049
{
1193b4be Poul Sander 2025-02-03 21:28 2050
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2051
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 2052
    g.BeginComboDepth--;
1193b4be Poul Sander 2025-02-03 21:28 2053
}
1193b4be Poul Sander 2025-02-03 21:28 2054
1193b4be Poul Sander 2025-02-03 21:28 2055
// Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements
1193b4be Poul Sander 2025-02-03 21:28 2056
// (Experimental, see GitHub issues: #1658, #4168)
1193b4be Poul Sander 2025-02-03 21:28 2057
bool ImGui::BeginComboPreview()
1193b4be Poul Sander 2025-02-03 21:28 2058
{
1193b4be Poul Sander 2025-02-03 21:28 2059
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2060
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 2061
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1193b4be Poul Sander 2025-02-03 21:28 2062
1193b4be Poul Sander 2025-02-03 21:28 2063
    if (window->SkipItems || !(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible))
1193b4be Poul Sander 2025-02-03 21:28 2064
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2065
    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?
1193b4be Poul Sander 2025-02-03 21:28 2066
    if (!window->ClipRect.Overlaps(preview_data->PreviewRect)) // Narrower test (optional)
1193b4be Poul Sander 2025-02-03 21:28 2067
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2068
1193b4be Poul Sander 2025-02-03 21:28 2069
    // FIXME: This could be contained in a PushWorkRect() api
1193b4be Poul Sander 2025-02-03 21:28 2070
    preview_data->BackupCursorPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 2071
    preview_data->BackupCursorMaxPos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 2072
    preview_data->BackupCursorPosPrevLine = window->DC.CursorPosPrevLine;
1193b4be Poul Sander 2025-02-03 21:28 2073
    preview_data->BackupPrevLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 2074
    preview_data->BackupLayout = window->DC.LayoutType;
1193b4be Poul Sander 2025-02-03 21:28 2075
    window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 2076
    window->DC.CursorMaxPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 2077
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 2078
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 2079
    PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
1193b4be Poul Sander 2025-02-03 21:28 2080
1193b4be Poul Sander 2025-02-03 21:28 2081
    return true;
1193b4be Poul Sander 2025-02-03 21:28 2082
}
1193b4be Poul Sander 2025-02-03 21:28 2083
1193b4be Poul Sander 2025-02-03 21:28 2084
void ImGui::EndComboPreview()
1193b4be Poul Sander 2025-02-03 21:28 2085
{
1193b4be Poul Sander 2025-02-03 21:28 2086
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2087
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 2088
    ImGuiComboPreviewData* preview_data = &g.ComboPreviewData;
1193b4be Poul Sander 2025-02-03 21:28 2089
1193b4be Poul Sander 2025-02-03 21:28 2090
    // FIXME: Using CursorMaxPos approximation instead of correct AABB which we will store in ImDrawCmd in the future
1193b4be Poul Sander 2025-02-03 21:28 2091
    ImDrawList* draw_list = window->DrawList;
1193b4be Poul Sander 2025-02-03 21:28 2092
    if (window->DC.CursorMaxPos.x < preview_data->PreviewRect.Max.x && window->DC.CursorMaxPos.y < preview_data->PreviewRect.Max.y)
1193b4be Poul Sander 2025-02-03 21:28 2093
        if (draw_list->CmdBuffer.Size > 1) // Unlikely case that the PushClipRect() didn't create a command
1193b4be Poul Sander 2025-02-03 21:28 2094
        {
1193b4be Poul Sander 2025-02-03 21:28 2095
            draw_list->_CmdHeader.ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ClipRect = draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 2].ClipRect;
1193b4be Poul Sander 2025-02-03 21:28 2096
            draw_list->_TryMergeDrawCmds();
1193b4be Poul Sander 2025-02-03 21:28 2097
        }
1193b4be Poul Sander 2025-02-03 21:28 2098
    PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 2099
    window->DC.CursorPos = preview_data->BackupCursorPos;
1193b4be Poul Sander 2025-02-03 21:28 2100
    window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, preview_data->BackupCursorMaxPos);
1193b4be Poul Sander 2025-02-03 21:28 2101
    window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
1193b4be Poul Sander 2025-02-03 21:28 2102
    window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 2103
    window->DC.LayoutType = preview_data->BackupLayout;
1193b4be Poul Sander 2025-02-03 21:28 2104
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 2105
    preview_data->PreviewRect = ImRect();
1193b4be Poul Sander 2025-02-03 21:28 2106
}
1193b4be Poul Sander 2025-02-03 21:28 2107
1193b4be Poul Sander 2025-02-03 21:28 2108
// Getter for the old Combo() API: const char*[]
1193b4be Poul Sander 2025-02-03 21:28 2109
static const char* Items_ArrayGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2110
{
1193b4be Poul Sander 2025-02-03 21:28 2111
    const char* const* items = (const char* const*)data;
1193b4be Poul Sander 2025-02-03 21:28 2112
    return items[idx];
1193b4be Poul Sander 2025-02-03 21:28 2113
}
1193b4be Poul Sander 2025-02-03 21:28 2114
1193b4be Poul Sander 2025-02-03 21:28 2115
// Getter for the old Combo() API: "item1\0item2\0item3\0"
1193b4be Poul Sander 2025-02-03 21:28 2116
static const char* Items_SingleStringGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2117
{
1193b4be Poul Sander 2025-02-03 21:28 2118
    const char* items_separated_by_zeros = (const char*)data;
1193b4be Poul Sander 2025-02-03 21:28 2119
    int items_count = 0;
1193b4be Poul Sander 2025-02-03 21:28 2120
    const char* p = items_separated_by_zeros;
1193b4be Poul Sander 2025-02-03 21:28 2121
    while (*p)
1193b4be Poul Sander 2025-02-03 21:28 2122
    {
1193b4be Poul Sander 2025-02-03 21:28 2123
        if (idx == items_count)
1193b4be Poul Sander 2025-02-03 21:28 2124
            break;
4e9e2555 Poul Sander 2025-10-05 10:15 2125
        p += ImStrlen(p) + 1;
1193b4be Poul Sander 2025-02-03 21:28 2126
        items_count++;
1193b4be Poul Sander 2025-02-03 21:28 2127
    }
1193b4be Poul Sander 2025-02-03 21:28 2128
    return *p ? p : NULL;
1193b4be Poul Sander 2025-02-03 21:28 2129
}
1193b4be Poul Sander 2025-02-03 21:28 2130
1193b4be Poul Sander 2025-02-03 21:28 2131
// Old API, prefer using BeginCombo() nowadays if you can.
1193b4be Poul Sander 2025-02-03 21:28 2132
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)
1193b4be Poul Sander 2025-02-03 21:28 2133
{
1193b4be Poul Sander 2025-02-03 21:28 2134
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2135
1193b4be Poul Sander 2025-02-03 21:28 2136
    // Call the getter to obtain the preview string which is a parameter to BeginCombo()
1193b4be Poul Sander 2025-02-03 21:28 2137
    const char* preview_value = NULL;
1193b4be Poul Sander 2025-02-03 21:28 2138
    if (*current_item >= 0 && *current_item < items_count)
1193b4be Poul Sander 2025-02-03 21:28 2139
        preview_value = getter(user_data, *current_item);
1193b4be Poul Sander 2025-02-03 21:28 2140
1193b4be Poul Sander 2025-02-03 21:28 2141
    // 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.
4e9e2555 Poul Sander 2025-10-05 10:15 2142
    if (popup_max_height_in_items != -1 && !(g.NextWindowData.HasFlags & ImGuiNextWindowDataFlags_HasSizeConstraint))
1193b4be Poul Sander 2025-02-03 21:28 2143
        SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
1193b4be Poul Sander 2025-02-03 21:28 2144
1193b4be Poul Sander 2025-02-03 21:28 2145
    if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 2146
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2147
1193b4be Poul Sander 2025-02-03 21:28 2148
    // Display items
1193b4be Poul Sander 2025-02-03 21:28 2149
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 2150
    ImGuiListClipper clipper;
1193b4be Poul Sander 2025-02-03 21:28 2151
    clipper.Begin(items_count);
1193b4be Poul Sander 2025-02-03 21:28 2152
    clipper.IncludeItemByIndex(*current_item);
1193b4be Poul Sander 2025-02-03 21:28 2153
    while (clipper.Step())
1193b4be Poul Sander 2025-02-03 21:28 2154
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
1193b4be Poul Sander 2025-02-03 21:28 2155
        {
1193b4be Poul Sander 2025-02-03 21:28 2156
            const char* item_text = getter(user_data, i);
1193b4be Poul Sander 2025-02-03 21:28 2157
            if (item_text == NULL)
1193b4be Poul Sander 2025-02-03 21:28 2158
                item_text = "*Unknown item*";
1193b4be Poul Sander 2025-02-03 21:28 2159
1193b4be Poul Sander 2025-02-03 21:28 2160
            PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 2161
            const bool item_selected = (i == *current_item);
1193b4be Poul Sander 2025-02-03 21:28 2162
            if (Selectable(item_text, item_selected) && *current_item != i)
1193b4be Poul Sander 2025-02-03 21:28 2163
            {
1193b4be Poul Sander 2025-02-03 21:28 2164
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 2165
                *current_item = i;
1193b4be Poul Sander 2025-02-03 21:28 2166
            }
1193b4be Poul Sander 2025-02-03 21:28 2167
            if (item_selected)
1193b4be Poul Sander 2025-02-03 21:28 2168
                SetItemDefaultFocus();
1193b4be Poul Sander 2025-02-03 21:28 2169
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 2170
        }
1193b4be Poul Sander 2025-02-03 21:28 2171
1193b4be Poul Sander 2025-02-03 21:28 2172
    EndCombo();
1193b4be Poul Sander 2025-02-03 21:28 2173
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 2174
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 2175
1193b4be Poul Sander 2025-02-03 21:28 2176
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2177
}
1193b4be Poul Sander 2025-02-03 21:28 2178
1193b4be Poul Sander 2025-02-03 21:28 2179
// Combo box helper allowing to pass an array of strings.
1193b4be Poul Sander 2025-02-03 21:28 2180
bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
1193b4be Poul Sander 2025-02-03 21:28 2181
{
1193b4be Poul Sander 2025-02-03 21:28 2182
    const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2183
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2184
}
1193b4be Poul Sander 2025-02-03 21:28 2185
1193b4be Poul Sander 2025-02-03 21:28 2186
// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
1193b4be Poul Sander 2025-02-03 21:28 2187
bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
1193b4be Poul Sander 2025-02-03 21:28 2188
{
1193b4be Poul Sander 2025-02-03 21:28 2189
    int items_count = 0;
1193b4be Poul Sander 2025-02-03 21:28 2190
    const char* p = items_separated_by_zeros;       // FIXME-OPT: Avoid computing this, or at least only when combo is open
1193b4be Poul Sander 2025-02-03 21:28 2191
    while (*p)
1193b4be Poul Sander 2025-02-03 21:28 2192
    {
4e9e2555 Poul Sander 2025-10-05 10:15 2193
        p += ImStrlen(p) + 1;
1193b4be Poul Sander 2025-02-03 21:28 2194
        items_count++;
1193b4be Poul Sander 2025-02-03 21:28 2195
    }
1193b4be Poul Sander 2025-02-03 21:28 2196
    bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2197
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2198
}
1193b4be Poul Sander 2025-02-03 21:28 2199
1193b4be Poul Sander 2025-02-03 21:28 2200
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1193b4be Poul Sander 2025-02-03 21:28 2201
1193b4be Poul Sander 2025-02-03 21:28 2202
struct ImGuiGetNameFromIndexOldToNewCallbackData { void* UserData; bool (*OldCallback)(void*, int, const char**); };
1193b4be Poul Sander 2025-02-03 21:28 2203
static const char* ImGuiGetNameFromIndexOldToNewCallback(void* user_data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 2204
{
1193b4be Poul Sander 2025-02-03 21:28 2205
    ImGuiGetNameFromIndexOldToNewCallbackData* data = (ImGuiGetNameFromIndexOldToNewCallbackData*)user_data;
1193b4be Poul Sander 2025-02-03 21:28 2206
    const char* s = NULL;
1193b4be Poul Sander 2025-02-03 21:28 2207
    data->OldCallback(data->UserData, idx, &s);
1193b4be Poul Sander 2025-02-03 21:28 2208
    return s;
1193b4be Poul Sander 2025-02-03 21:28 2209
}
1193b4be Poul Sander 2025-02-03 21:28 2210
1193b4be Poul Sander 2025-02-03 21:28 2211
bool ImGui::ListBox(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int height_in_items)
1193b4be Poul Sander 2025-02-03 21:28 2212
{
1193b4be Poul Sander 2025-02-03 21:28 2213
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1193b4be Poul Sander 2025-02-03 21:28 2214
    return ListBox(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2215
}
1193b4be Poul Sander 2025-02-03 21:28 2216
bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void*, int, const char**), void* user_data, int items_count, int popup_max_height_in_items)
1193b4be Poul Sander 2025-02-03 21:28 2217
{
1193b4be Poul Sander 2025-02-03 21:28 2218
    ImGuiGetNameFromIndexOldToNewCallbackData old_to_new_data = { user_data, old_getter };
1193b4be Poul Sander 2025-02-03 21:28 2219
    return Combo(label, current_item, ImGuiGetNameFromIndexOldToNewCallback, &old_to_new_data, items_count, popup_max_height_in_items);
1193b4be Poul Sander 2025-02-03 21:28 2220
}
1193b4be Poul Sander 2025-02-03 21:28 2221
1193b4be Poul Sander 2025-02-03 21:28 2222
#endif
1193b4be Poul Sander 2025-02-03 21:28 2223
1193b4be Poul Sander 2025-02-03 21:28 2224
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2225
// [SECTION] Data Type and Data Formatting Helpers [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2226
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2227
// - DataTypeGetInfo()
1193b4be Poul Sander 2025-02-03 21:28 2228
// - DataTypeFormatString()
1193b4be Poul Sander 2025-02-03 21:28 2229
// - DataTypeApplyOp()
1193b4be Poul Sander 2025-02-03 21:28 2230
// - DataTypeApplyFromText()
1193b4be Poul Sander 2025-02-03 21:28 2231
// - DataTypeCompare()
1193b4be Poul Sander 2025-02-03 21:28 2232
// - DataTypeClamp()
1193b4be Poul Sander 2025-02-03 21:28 2233
// - GetMinimumStepAtDecimalPrecision
1193b4be Poul Sander 2025-02-03 21:28 2234
// - RoundScalarWithFormat<>()
1193b4be Poul Sander 2025-02-03 21:28 2235
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2236
1193b4be Poul Sander 2025-02-03 21:28 2237
static const ImGuiDataTypeInfo GDataTypeInfo[] =
1193b4be Poul Sander 2025-02-03 21:28 2238
{
1193b4be Poul Sander 2025-02-03 21:28 2239
    { sizeof(char),             "S8",   "%d",   "%d"    },  // ImGuiDataType_S8
1193b4be Poul Sander 2025-02-03 21:28 2240
    { sizeof(unsigned char),    "U8",   "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2241
    { sizeof(short),            "S16",  "%d",   "%d"    },  // ImGuiDataType_S16
1193b4be Poul Sander 2025-02-03 21:28 2242
    { sizeof(unsigned short),   "U16",  "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2243
    { sizeof(int),              "S32",  "%d",   "%d"    },  // ImGuiDataType_S32
1193b4be Poul Sander 2025-02-03 21:28 2244
    { sizeof(unsigned int),     "U32",  "%u",   "%u"    },
1193b4be Poul Sander 2025-02-03 21:28 2245
#ifdef _MSC_VER
1193b4be Poul Sander 2025-02-03 21:28 2246
    { sizeof(ImS64),            "S64",  "%I64d","%I64d" },  // ImGuiDataType_S64
1193b4be Poul Sander 2025-02-03 21:28 2247
    { sizeof(ImU64),            "U64",  "%I64u","%I64u" },
1193b4be Poul Sander 2025-02-03 21:28 2248
#else
1193b4be Poul Sander 2025-02-03 21:28 2249
    { sizeof(ImS64),            "S64",  "%lld", "%lld"  },  // ImGuiDataType_S64
1193b4be Poul Sander 2025-02-03 21:28 2250
    { sizeof(ImU64),            "U64",  "%llu", "%llu"  },
1193b4be Poul Sander 2025-02-03 21:28 2251
#endif
1193b4be Poul Sander 2025-02-03 21:28 2252
    { sizeof(float),            "float", "%.3f","%f"    },  // ImGuiDataType_Float (float are promoted to double in va_arg)
1193b4be Poul Sander 2025-02-03 21:28 2253
    { sizeof(double),           "double","%f",  "%lf"   },  // ImGuiDataType_Double
1193b4be Poul Sander 2025-02-03 21:28 2254
    { sizeof(bool),             "bool", "%d",   "%d"    },  // ImGuiDataType_Bool
4e9e2555 Poul Sander 2025-10-05 10:15 2255
    { 0,                        "char*","%s",   "%s"    },  // ImGuiDataType_String
1193b4be Poul Sander 2025-02-03 21:28 2256
};
1193b4be Poul Sander 2025-02-03 21:28 2257
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
1193b4be Poul Sander 2025-02-03 21:28 2258
1193b4be Poul Sander 2025-02-03 21:28 2259
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
1193b4be Poul Sander 2025-02-03 21:28 2260
{
1193b4be Poul Sander 2025-02-03 21:28 2261
    IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
1193b4be Poul Sander 2025-02-03 21:28 2262
    return &GDataTypeInfo[data_type];
1193b4be Poul Sander 2025-02-03 21:28 2263
}
1193b4be Poul Sander 2025-02-03 21:28 2264
1193b4be Poul Sander 2025-02-03 21:28 2265
int ImGui::DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format)
1193b4be Poul Sander 2025-02-03 21:28 2266
{
1193b4be Poul Sander 2025-02-03 21:28 2267
    // Signedness doesn't matter when pushing integer arguments
1193b4be Poul Sander 2025-02-03 21:28 2268
    if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32)
1193b4be Poul Sander 2025-02-03 21:28 2269
        return ImFormatString(buf, buf_size, format, *(const ImU32*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2270
    if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
1193b4be Poul Sander 2025-02-03 21:28 2271
        return ImFormatString(buf, buf_size, format, *(const ImU64*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2272
    if (data_type == ImGuiDataType_Float)
1193b4be Poul Sander 2025-02-03 21:28 2273
        return ImFormatString(buf, buf_size, format, *(const float*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2274
    if (data_type == ImGuiDataType_Double)
1193b4be Poul Sander 2025-02-03 21:28 2275
        return ImFormatString(buf, buf_size, format, *(const double*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2276
    if (data_type == ImGuiDataType_S8)
1193b4be Poul Sander 2025-02-03 21:28 2277
        return ImFormatString(buf, buf_size, format, *(const ImS8*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2278
    if (data_type == ImGuiDataType_U8)
1193b4be Poul Sander 2025-02-03 21:28 2279
        return ImFormatString(buf, buf_size, format, *(const ImU8*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2280
    if (data_type == ImGuiDataType_S16)
1193b4be Poul Sander 2025-02-03 21:28 2281
        return ImFormatString(buf, buf_size, format, *(const ImS16*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2282
    if (data_type == ImGuiDataType_U16)
1193b4be Poul Sander 2025-02-03 21:28 2283
        return ImFormatString(buf, buf_size, format, *(const ImU16*)p_data);
1193b4be Poul Sander 2025-02-03 21:28 2284
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2285
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2286
}
1193b4be Poul Sander 2025-02-03 21:28 2287
1193b4be Poul Sander 2025-02-03 21:28 2288
void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg1, const void* arg2)
1193b4be Poul Sander 2025-02-03 21:28 2289
{
1193b4be Poul Sander 2025-02-03 21:28 2290
    IM_ASSERT(op == '+' || op == '-');
1193b4be Poul Sander 2025-02-03 21:28 2291
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2292
    {
1193b4be Poul Sander 2025-02-03 21:28 2293
        case ImGuiDataType_S8:
1193b4be Poul Sander 2025-02-03 21:28 2294
            if (op == '+') { *(ImS8*)output  = ImAddClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2295
            if (op == '-') { *(ImS8*)output  = ImSubClampOverflow(*(const ImS8*)arg1,  *(const ImS8*)arg2,  IM_S8_MIN,  IM_S8_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2296
            return;
1193b4be Poul Sander 2025-02-03 21:28 2297
        case ImGuiDataType_U8:
1193b4be Poul Sander 2025-02-03 21:28 2298
            if (op == '+') { *(ImU8*)output  = ImAddClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2299
            if (op == '-') { *(ImU8*)output  = ImSubClampOverflow(*(const ImU8*)arg1,  *(const ImU8*)arg2,  IM_U8_MIN,  IM_U8_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2300
            return;
1193b4be Poul Sander 2025-02-03 21:28 2301
        case ImGuiDataType_S16:
1193b4be Poul Sander 2025-02-03 21:28 2302
            if (op == '+') { *(ImS16*)output = ImAddClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2303
            if (op == '-') { *(ImS16*)output = ImSubClampOverflow(*(const ImS16*)arg1, *(const ImS16*)arg2, IM_S16_MIN, IM_S16_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2304
            return;
1193b4be Poul Sander 2025-02-03 21:28 2305
        case ImGuiDataType_U16:
1193b4be Poul Sander 2025-02-03 21:28 2306
            if (op == '+') { *(ImU16*)output = ImAddClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2307
            if (op == '-') { *(ImU16*)output = ImSubClampOverflow(*(const ImU16*)arg1, *(const ImU16*)arg2, IM_U16_MIN, IM_U16_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2308
            return;
1193b4be Poul Sander 2025-02-03 21:28 2309
        case ImGuiDataType_S32:
1193b4be Poul Sander 2025-02-03 21:28 2310
            if (op == '+') { *(ImS32*)output = ImAddClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2311
            if (op == '-') { *(ImS32*)output = ImSubClampOverflow(*(const ImS32*)arg1, *(const ImS32*)arg2, IM_S32_MIN, IM_S32_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2312
            return;
1193b4be Poul Sander 2025-02-03 21:28 2313
        case ImGuiDataType_U32:
1193b4be Poul Sander 2025-02-03 21:28 2314
            if (op == '+') { *(ImU32*)output = ImAddClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2315
            if (op == '-') { *(ImU32*)output = ImSubClampOverflow(*(const ImU32*)arg1, *(const ImU32*)arg2, IM_U32_MIN, IM_U32_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2316
            return;
1193b4be Poul Sander 2025-02-03 21:28 2317
        case ImGuiDataType_S64:
1193b4be Poul Sander 2025-02-03 21:28 2318
            if (op == '+') { *(ImS64*)output = ImAddClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2319
            if (op == '-') { *(ImS64*)output = ImSubClampOverflow(*(const ImS64*)arg1, *(const ImS64*)arg2, IM_S64_MIN, IM_S64_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2320
            return;
1193b4be Poul Sander 2025-02-03 21:28 2321
        case ImGuiDataType_U64:
1193b4be Poul Sander 2025-02-03 21:28 2322
            if (op == '+') { *(ImU64*)output = ImAddClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2323
            if (op == '-') { *(ImU64*)output = ImSubClampOverflow(*(const ImU64*)arg1, *(const ImU64*)arg2, IM_U64_MIN, IM_U64_MAX); }
1193b4be Poul Sander 2025-02-03 21:28 2324
            return;
1193b4be Poul Sander 2025-02-03 21:28 2325
        case ImGuiDataType_Float:
1193b4be Poul Sander 2025-02-03 21:28 2326
            if (op == '+') { *(float*)output = *(const float*)arg1 + *(const float*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2327
            if (op == '-') { *(float*)output = *(const float*)arg1 - *(const float*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2328
            return;
1193b4be Poul Sander 2025-02-03 21:28 2329
        case ImGuiDataType_Double:
1193b4be Poul Sander 2025-02-03 21:28 2330
            if (op == '+') { *(double*)output = *(const double*)arg1 + *(const double*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2331
            if (op == '-') { *(double*)output = *(const double*)arg1 - *(const double*)arg2; }
1193b4be Poul Sander 2025-02-03 21:28 2332
            return;
1193b4be Poul Sander 2025-02-03 21:28 2333
        case ImGuiDataType_COUNT: break;
1193b4be Poul Sander 2025-02-03 21:28 2334
    }
1193b4be Poul Sander 2025-02-03 21:28 2335
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2336
}
1193b4be Poul Sander 2025-02-03 21:28 2337
1193b4be Poul Sander 2025-02-03 21:28 2338
// User can input math operators (e.g. +100) to edit a numerical values.
1193b4be Poul Sander 2025-02-03 21:28 2339
// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
1193b4be Poul Sander 2025-02-03 21:28 2340
bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)
1193b4be Poul Sander 2025-02-03 21:28 2341
{
1193b4be Poul Sander 2025-02-03 21:28 2342
    // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
1193b4be Poul Sander 2025-02-03 21:28 2343
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2344
    ImGuiDataTypeStorage data_backup;
1193b4be Poul Sander 2025-02-03 21:28 2345
    memcpy(&data_backup, p_data, type_info->Size);
1193b4be Poul Sander 2025-02-03 21:28 2346
1193b4be Poul Sander 2025-02-03 21:28 2347
    while (ImCharIsBlankA(*buf))
1193b4be Poul Sander 2025-02-03 21:28 2348
        buf++;
1193b4be Poul Sander 2025-02-03 21:28 2349
    if (!buf[0])
1193b4be Poul Sander 2025-02-03 21:28 2350
    {
1193b4be Poul Sander 2025-02-03 21:28 2351
        if (p_data_when_empty != NULL)
1193b4be Poul Sander 2025-02-03 21:28 2352
        {
1193b4be Poul Sander 2025-02-03 21:28 2353
            memcpy(p_data, p_data_when_empty, type_info->Size);
1193b4be Poul Sander 2025-02-03 21:28 2354
            return memcmp(&data_backup, p_data, type_info->Size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2355
        }
1193b4be Poul Sander 2025-02-03 21:28 2356
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2357
    }
1193b4be Poul Sander 2025-02-03 21:28 2358
1193b4be Poul Sander 2025-02-03 21:28 2359
    // Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 2360
    // - 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
1193b4be Poul Sander 2025-02-03 21:28 2361
    // - In theory could treat empty format as using default, but this would only cover rare/bizarre case of using InputScalar() + integer + format string without %.
1193b4be Poul Sander 2025-02-03 21:28 2362
    char format_sanitized[32];
1193b4be Poul Sander 2025-02-03 21:28 2363
    if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
1193b4be Poul Sander 2025-02-03 21:28 2364
        format = type_info->ScanFmt;
1193b4be Poul Sander 2025-02-03 21:28 2365
    else
1193b4be Poul Sander 2025-02-03 21:28 2366
        format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
1193b4be Poul Sander 2025-02-03 21:28 2367
1193b4be Poul Sander 2025-02-03 21:28 2368
    // Small types need a 32-bit buffer to receive the result from scanf()
1193b4be Poul Sander 2025-02-03 21:28 2369
    int v32 = 0;
1193b4be Poul Sander 2025-02-03 21:28 2370
    if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
1193b4be Poul Sander 2025-02-03 21:28 2371
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2372
    if (type_info->Size < 4)
1193b4be Poul Sander 2025-02-03 21:28 2373
    {
1193b4be Poul Sander 2025-02-03 21:28 2374
        if (data_type == ImGuiDataType_S8)
1193b4be Poul Sander 2025-02-03 21:28 2375
            *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2376
        else if (data_type == ImGuiDataType_U8)
1193b4be Poul Sander 2025-02-03 21:28 2377
            *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2378
        else if (data_type == ImGuiDataType_S16)
1193b4be Poul Sander 2025-02-03 21:28 2379
            *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2380
        else if (data_type == ImGuiDataType_U16)
1193b4be Poul Sander 2025-02-03 21:28 2381
            *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX);
1193b4be Poul Sander 2025-02-03 21:28 2382
        else
1193b4be Poul Sander 2025-02-03 21:28 2383
            IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2384
    }
1193b4be Poul Sander 2025-02-03 21:28 2385
1193b4be Poul Sander 2025-02-03 21:28 2386
    return memcmp(&data_backup, p_data, type_info->Size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2387
}
1193b4be Poul Sander 2025-02-03 21:28 2388
1193b4be Poul Sander 2025-02-03 21:28 2389
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 2390
static int DataTypeCompareT(const T* lhs, const T* rhs)
1193b4be Poul Sander 2025-02-03 21:28 2391
{
1193b4be Poul Sander 2025-02-03 21:28 2392
    if (*lhs < *rhs) return -1;
1193b4be Poul Sander 2025-02-03 21:28 2393
    if (*lhs > *rhs) return +1;
1193b4be Poul Sander 2025-02-03 21:28 2394
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2395
}
1193b4be Poul Sander 2025-02-03 21:28 2396
1193b4be Poul Sander 2025-02-03 21:28 2397
int ImGui::DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2)
1193b4be Poul Sander 2025-02-03 21:28 2398
{
1193b4be Poul Sander 2025-02-03 21:28 2399
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2400
    {
1193b4be Poul Sander 2025-02-03 21:28 2401
    case ImGuiDataType_S8:     return DataTypeCompareT<ImS8  >((const ImS8*  )arg_1, (const ImS8*  )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2402
    case ImGuiDataType_U8:     return DataTypeCompareT<ImU8  >((const ImU8*  )arg_1, (const ImU8*  )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2403
    case ImGuiDataType_S16:    return DataTypeCompareT<ImS16 >((const ImS16* )arg_1, (const ImS16* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2404
    case ImGuiDataType_U16:    return DataTypeCompareT<ImU16 >((const ImU16* )arg_1, (const ImU16* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2405
    case ImGuiDataType_S32:    return DataTypeCompareT<ImS32 >((const ImS32* )arg_1, (const ImS32* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2406
    case ImGuiDataType_U32:    return DataTypeCompareT<ImU32 >((const ImU32* )arg_1, (const ImU32* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2407
    case ImGuiDataType_S64:    return DataTypeCompareT<ImS64 >((const ImS64* )arg_1, (const ImS64* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2408
    case ImGuiDataType_U64:    return DataTypeCompareT<ImU64 >((const ImU64* )arg_1, (const ImU64* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2409
    case ImGuiDataType_Float:  return DataTypeCompareT<float >((const float* )arg_1, (const float* )arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2410
    case ImGuiDataType_Double: return DataTypeCompareT<double>((const double*)arg_1, (const double*)arg_2);
1193b4be Poul Sander 2025-02-03 21:28 2411
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2412
    }
1193b4be Poul Sander 2025-02-03 21:28 2413
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2414
    return 0;
1193b4be Poul Sander 2025-02-03 21:28 2415
}
1193b4be Poul Sander 2025-02-03 21:28 2416
1193b4be Poul Sander 2025-02-03 21:28 2417
template<typename T>
1193b4be Poul Sander 2025-02-03 21:28 2418
static bool DataTypeClampT(T* v, const T* v_min, const T* v_max)
1193b4be Poul Sander 2025-02-03 21:28 2419
{
1193b4be Poul Sander 2025-02-03 21:28 2420
    // Clamp, both sides are optional, return true if modified
1193b4be Poul Sander 2025-02-03 21:28 2421
    if (v_min && *v < *v_min) { *v = *v_min; return true; }
1193b4be Poul Sander 2025-02-03 21:28 2422
    if (v_max && *v > *v_max) { *v = *v_max; return true; }
1193b4be Poul Sander 2025-02-03 21:28 2423
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2424
}
1193b4be Poul Sander 2025-02-03 21:28 2425
1193b4be Poul Sander 2025-02-03 21:28 2426
bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max)
1193b4be Poul Sander 2025-02-03 21:28 2427
{
1193b4be Poul Sander 2025-02-03 21:28 2428
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2429
    {
1193b4be Poul Sander 2025-02-03 21:28 2430
    case ImGuiDataType_S8:     return DataTypeClampT<ImS8  >((ImS8*  )p_data, (const ImS8*  )p_min, (const ImS8*  )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2431
    case ImGuiDataType_U8:     return DataTypeClampT<ImU8  >((ImU8*  )p_data, (const ImU8*  )p_min, (const ImU8*  )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2432
    case ImGuiDataType_S16:    return DataTypeClampT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2433
    case ImGuiDataType_U16:    return DataTypeClampT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2434
    case ImGuiDataType_S32:    return DataTypeClampT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2435
    case ImGuiDataType_U32:    return DataTypeClampT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2436
    case ImGuiDataType_S64:    return DataTypeClampT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2437
    case ImGuiDataType_U64:    return DataTypeClampT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2438
    case ImGuiDataType_Float:  return DataTypeClampT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
1193b4be Poul Sander 2025-02-03 21:28 2439
    case ImGuiDataType_Double: return DataTypeClampT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
1193b4be Poul Sander 2025-02-03 21:28 2440
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2441
    }
1193b4be Poul Sander 2025-02-03 21:28 2442
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2443
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2444
}
1193b4be Poul Sander 2025-02-03 21:28 2445
1193b4be Poul Sander 2025-02-03 21:28 2446
bool ImGui::DataTypeIsZero(ImGuiDataType data_type, const void* p_data)
1193b4be Poul Sander 2025-02-03 21:28 2447
{
1193b4be Poul Sander 2025-02-03 21:28 2448
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2449
    return DataTypeCompare(data_type, p_data, &g.DataTypeZeroValue) == 0;
1193b4be Poul Sander 2025-02-03 21:28 2450
}
1193b4be Poul Sander 2025-02-03 21:28 2451
1193b4be Poul Sander 2025-02-03 21:28 2452
static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
1193b4be Poul Sander 2025-02-03 21:28 2453
{
1193b4be Poul Sander 2025-02-03 21:28 2454
    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 };
1193b4be Poul Sander 2025-02-03 21:28 2455
    if (decimal_precision < 0)
1193b4be Poul Sander 2025-02-03 21:28 2456
        return FLT_MIN;
1193b4be Poul Sander 2025-02-03 21:28 2457
    return (decimal_precision < IM_ARRAYSIZE(min_steps)) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
1193b4be Poul Sander 2025-02-03 21:28 2458
}
1193b4be Poul Sander 2025-02-03 21:28 2459
1193b4be Poul Sander 2025-02-03 21:28 2460
template<typename TYPE>
1193b4be Poul Sander 2025-02-03 21:28 2461
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
1193b4be Poul Sander 2025-02-03 21:28 2462
{
1193b4be Poul Sander 2025-02-03 21:28 2463
    IM_UNUSED(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2464
    IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2465
    const char* fmt_start = ImParseFormatFindStart(format);
1193b4be Poul Sander 2025-02-03 21:28 2466
    if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
1193b4be Poul Sander 2025-02-03 21:28 2467
        return v;
1193b4be Poul Sander 2025-02-03 21:28 2468
1193b4be Poul Sander 2025-02-03 21:28 2469
    // Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 2470
    char fmt_sanitized[32];
1193b4be Poul Sander 2025-02-03 21:28 2471
    ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
1193b4be Poul Sander 2025-02-03 21:28 2472
    fmt_start = fmt_sanitized;
1193b4be Poul Sander 2025-02-03 21:28 2473
1193b4be Poul Sander 2025-02-03 21:28 2474
    // Format value with our rounding, and read back
1193b4be Poul Sander 2025-02-03 21:28 2475
    char v_str[64];
1193b4be Poul Sander 2025-02-03 21:28 2476
    ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
1193b4be Poul Sander 2025-02-03 21:28 2477
    const char* p = v_str;
1193b4be Poul Sander 2025-02-03 21:28 2478
    while (*p == ' ')
1193b4be Poul Sander 2025-02-03 21:28 2479
        p++;
1193b4be Poul Sander 2025-02-03 21:28 2480
    v = (TYPE)ImAtof(p);
1193b4be Poul Sander 2025-02-03 21:28 2481
1193b4be Poul Sander 2025-02-03 21:28 2482
    return v;
1193b4be Poul Sander 2025-02-03 21:28 2483
}
1193b4be Poul Sander 2025-02-03 21:28 2484
1193b4be Poul Sander 2025-02-03 21:28 2485
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2486
// [SECTION] Widgets: DragScalar, DragFloat, DragInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 2487
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2488
// - DragBehaviorT<>() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2489
// - DragBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2490
// - DragScalar()
1193b4be Poul Sander 2025-02-03 21:28 2491
// - DragScalarN()
1193b4be Poul Sander 2025-02-03 21:28 2492
// - DragFloat()
1193b4be Poul Sander 2025-02-03 21:28 2493
// - DragFloat2()
1193b4be Poul Sander 2025-02-03 21:28 2494
// - DragFloat3()
1193b4be Poul Sander 2025-02-03 21:28 2495
// - DragFloat4()
1193b4be Poul Sander 2025-02-03 21:28 2496
// - DragFloatRange2()
1193b4be Poul Sander 2025-02-03 21:28 2497
// - DragInt()
1193b4be Poul Sander 2025-02-03 21:28 2498
// - DragInt2()
1193b4be Poul Sander 2025-02-03 21:28 2499
// - DragInt3()
1193b4be Poul Sander 2025-02-03 21:28 2500
// - DragInt4()
1193b4be Poul Sander 2025-02-03 21:28 2501
// - DragIntRange2()
1193b4be Poul Sander 2025-02-03 21:28 2502
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2503
1193b4be Poul Sander 2025-02-03 21:28 2504
// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
1193b4be Poul Sander 2025-02-03 21:28 2505
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2506
bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2507
{
1193b4be Poul Sander 2025-02-03 21:28 2508
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2509
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 2510
    const bool is_bounded = (v_min < v_max) || ((v_min == v_max) && (v_min != 0.0f || (flags & ImGuiSliderFlags_ClampZeroRange)));
1193b4be Poul Sander 2025-02-03 21:28 2511
    const bool is_wrapped = is_bounded && (flags & ImGuiSliderFlags_WrapAround);
1193b4be Poul Sander 2025-02-03 21:28 2512
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
1193b4be Poul Sander 2025-02-03 21:28 2513
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 2514
1193b4be Poul Sander 2025-02-03 21:28 2515
    // Default tweak speed
1193b4be Poul Sander 2025-02-03 21:28 2516
    if (v_speed == 0.0f && is_bounded && (v_max - v_min < FLT_MAX))
1193b4be Poul Sander 2025-02-03 21:28 2517
        v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
1193b4be Poul Sander 2025-02-03 21:28 2518
1193b4be Poul Sander 2025-02-03 21:28 2519
    // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
1193b4be Poul Sander 2025-02-03 21:28 2520
    float adjust_delta = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2521
    if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
1193b4be Poul Sander 2025-02-03 21:28 2522
    {
1193b4be Poul Sander 2025-02-03 21:28 2523
        adjust_delta = g.IO.MouseDelta[axis];
4e9e2555 Poul Sander 2025-10-05 10:15 2524
        if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
1193b4be Poul Sander 2025-02-03 21:28 2525
            adjust_delta *= 1.0f / 100.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 2526
        if (g.IO.KeyShift && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
1193b4be Poul Sander 2025-02-03 21:28 2527
            adjust_delta *= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 2528
    }
1193b4be Poul Sander 2025-02-03 21:28 2529
    else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 2530
    {
1193b4be Poul Sander 2025-02-03 21:28 2531
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
1193b4be Poul Sander 2025-02-03 21:28 2532
        const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
1193b4be Poul Sander 2025-02-03 21:28 2533
        const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
4e9e2555 Poul Sander 2025-10-05 10:15 2534
        const float tweak_factor = (flags & ImGuiSliderFlags_NoSpeedTweaks) ? 1.0f : tweak_slow ? 1.0f / 10.0f : tweak_fast ? 10.0f : 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 2535
        adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
1193b4be Poul Sander 2025-02-03 21:28 2536
        v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
1193b4be Poul Sander 2025-02-03 21:28 2537
    }
1193b4be Poul Sander 2025-02-03 21:28 2538
    adjust_delta *= v_speed;
1193b4be Poul Sander 2025-02-03 21:28 2539
1193b4be Poul Sander 2025-02-03 21:28 2540
    // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
1193b4be Poul Sander 2025-02-03 21:28 2541
    if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 2542
        adjust_delta = -adjust_delta;
1193b4be Poul Sander 2025-02-03 21:28 2543
1193b4be Poul Sander 2025-02-03 21:28 2544
    // For logarithmic use our range is effectively 0..1 so scale the delta into that range
1193b4be Poul Sander 2025-02-03 21:28 2545
    if (is_logarithmic && (v_max - v_min < FLT_MAX) && ((v_max - v_min) > 0.000001f)) // Epsilon to avoid /0
1193b4be Poul Sander 2025-02-03 21:28 2546
        adjust_delta /= (float)(v_max - v_min);
1193b4be Poul Sander 2025-02-03 21:28 2547
1193b4be Poul Sander 2025-02-03 21:28 2548
    // Clear current value on activation
1193b4be Poul Sander 2025-02-03 21:28 2549
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 2550
    const bool is_just_activated = g.ActiveIdIsJustActivated;
1193b4be Poul Sander 2025-02-03 21:28 2551
    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));
1193b4be Poul Sander 2025-02-03 21:28 2552
    if (is_just_activated || is_already_past_limits_and_pushing_outward)
1193b4be Poul Sander 2025-02-03 21:28 2553
    {
1193b4be Poul Sander 2025-02-03 21:28 2554
        g.DragCurrentAccum = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2555
        g.DragCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 2556
    }
1193b4be Poul Sander 2025-02-03 21:28 2557
    else if (adjust_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2558
    {
1193b4be Poul Sander 2025-02-03 21:28 2559
        g.DragCurrentAccum += adjust_delta;
1193b4be Poul Sander 2025-02-03 21:28 2560
        g.DragCurrentAccumDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 2561
    }
1193b4be Poul Sander 2025-02-03 21:28 2562
1193b4be Poul Sander 2025-02-03 21:28 2563
    if (!g.DragCurrentAccumDirty)
1193b4be Poul Sander 2025-02-03 21:28 2564
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2565
1193b4be Poul Sander 2025-02-03 21:28 2566
    TYPE v_cur = *v;
1193b4be Poul Sander 2025-02-03 21:28 2567
    FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2568
1193b4be Poul Sander 2025-02-03 21:28 2569
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 2570
    const float zero_deadzone_halfsize = 0.0f; // Drag widgets have no deadzone (as it doesn't make sense)
1193b4be Poul Sander 2025-02-03 21:28 2571
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2572
    {
1193b4be Poul Sander 2025-02-03 21:28 2573
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 2574
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
1193b4be Poul Sander 2025-02-03 21:28 2575
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
1193b4be Poul Sander 2025-02-03 21:28 2576
1193b4be Poul Sander 2025-02-03 21:28 2577
        // Convert to parametric space, apply delta, convert back
1193b4be Poul Sander 2025-02-03 21:28 2578
        float v_old_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 2579
        float v_new_parametric = v_old_parametric + g.DragCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 2580
        v_cur = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new_parametric, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 2581
        v_old_ref_for_accum_remainder = v_old_parametric;
1193b4be Poul Sander 2025-02-03 21:28 2582
    }
1193b4be Poul Sander 2025-02-03 21:28 2583
    else
1193b4be Poul Sander 2025-02-03 21:28 2584
    {
1193b4be Poul Sander 2025-02-03 21:28 2585
        v_cur += (SIGNEDTYPE)g.DragCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 2586
    }
1193b4be Poul Sander 2025-02-03 21:28 2587
1193b4be Poul Sander 2025-02-03 21:28 2588
    // Round to user desired precision based on format string
1193b4be Poul Sander 2025-02-03 21:28 2589
    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 2590
        v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
1193b4be Poul Sander 2025-02-03 21:28 2591
1193b4be Poul Sander 2025-02-03 21:28 2592
    // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
1193b4be Poul Sander 2025-02-03 21:28 2593
    g.DragCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 2594
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2595
    {
1193b4be Poul Sander 2025-02-03 21:28 2596
        // Convert to parametric space, apply delta, convert back
1193b4be Poul Sander 2025-02-03 21:28 2597
        float v_new_parametric = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_cur, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 2598
        g.DragCurrentAccum -= (float)(v_new_parametric - v_old_ref_for_accum_remainder);
1193b4be Poul Sander 2025-02-03 21:28 2599
    }
1193b4be Poul Sander 2025-02-03 21:28 2600
    else
1193b4be Poul Sander 2025-02-03 21:28 2601
    {
1193b4be Poul Sander 2025-02-03 21:28 2602
        g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
1193b4be Poul Sander 2025-02-03 21:28 2603
    }
1193b4be Poul Sander 2025-02-03 21:28 2604
1193b4be Poul Sander 2025-02-03 21:28 2605
    // Lose zero sign for float/double
1193b4be Poul Sander 2025-02-03 21:28 2606
    if (v_cur == (TYPE)-0)
1193b4be Poul Sander 2025-02-03 21:28 2607
        v_cur = (TYPE)0;
1193b4be Poul Sander 2025-02-03 21:28 2608
1193b4be Poul Sander 2025-02-03 21:28 2609
    if (*v != v_cur && is_bounded)
1193b4be Poul Sander 2025-02-03 21:28 2610
    {
1193b4be Poul Sander 2025-02-03 21:28 2611
        if (is_wrapped)
1193b4be Poul Sander 2025-02-03 21:28 2612
        {
1193b4be Poul Sander 2025-02-03 21:28 2613
            // Wrap values
1193b4be Poul Sander 2025-02-03 21:28 2614
            if (v_cur < v_min)
1193b4be Poul Sander 2025-02-03 21:28 2615
                v_cur += v_max - v_min + (is_floating_point ? 0 : 1);
1193b4be Poul Sander 2025-02-03 21:28 2616
            if (v_cur > v_max)
1193b4be Poul Sander 2025-02-03 21:28 2617
                v_cur -= v_max - v_min + (is_floating_point ? 0 : 1);
1193b4be Poul Sander 2025-02-03 21:28 2618
        }
1193b4be Poul Sander 2025-02-03 21:28 2619
        else
1193b4be Poul Sander 2025-02-03 21:28 2620
        {
1193b4be Poul Sander 2025-02-03 21:28 2621
            // Clamp values + handle overflow/wrap-around for integer types.
1193b4be Poul Sander 2025-02-03 21:28 2622
            if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_floating_point))
1193b4be Poul Sander 2025-02-03 21:28 2623
                v_cur = v_min;
1193b4be Poul Sander 2025-02-03 21:28 2624
            if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_floating_point))
1193b4be Poul Sander 2025-02-03 21:28 2625
                v_cur = v_max;
1193b4be Poul Sander 2025-02-03 21:28 2626
        }
1193b4be Poul Sander 2025-02-03 21:28 2627
    }
1193b4be Poul Sander 2025-02-03 21:28 2628
1193b4be Poul Sander 2025-02-03 21:28 2629
    // Apply result
1193b4be Poul Sander 2025-02-03 21:28 2630
    if (*v == v_cur)
1193b4be Poul Sander 2025-02-03 21:28 2631
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2632
    *v = v_cur;
1193b4be Poul Sander 2025-02-03 21:28 2633
    return true;
1193b4be Poul Sander 2025-02-03 21:28 2634
}
1193b4be Poul Sander 2025-02-03 21:28 2635
1193b4be Poul Sander 2025-02-03 21:28 2636
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)
1193b4be Poul Sander 2025-02-03 21:28 2637
{
1193b4be Poul Sander 2025-02-03 21:28 2638
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
1193b4be Poul Sander 2025-02-03 21:28 2639
    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.");
1193b4be Poul Sander 2025-02-03 21:28 2640
1193b4be Poul Sander 2025-02-03 21:28 2641
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2642
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 2643
    {
1193b4be Poul Sander 2025-02-03 21:28 2644
        // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
1193b4be Poul Sander 2025-02-03 21:28 2645
        if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 2646
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 2647
        else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 2648
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 2649
    }
1193b4be Poul Sander 2025-02-03 21:28 2650
    if (g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 2651
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2652
    if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
1193b4be Poul Sander 2025-02-03 21:28 2653
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2654
1193b4be Poul Sander 2025-02-03 21:28 2655
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 2656
    {
1193b4be Poul Sander 2025-02-03 21:28 2657
    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; }
1193b4be Poul Sander 2025-02-03 21:28 2658
    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; }
1193b4be Poul Sander 2025-02-03 21:28 2659
    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; }
1193b4be Poul Sander 2025-02-03 21:28 2660
    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; }
1193b4be Poul Sander 2025-02-03 21:28 2661
    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);
1193b4be Poul Sander 2025-02-03 21:28 2662
    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);
1193b4be Poul Sander 2025-02-03 21:28 2663
    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);
1193b4be Poul Sander 2025-02-03 21:28 2664
    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);
1193b4be Poul Sander 2025-02-03 21:28 2665
    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);
1193b4be Poul Sander 2025-02-03 21:28 2666
    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);
1193b4be Poul Sander 2025-02-03 21:28 2667
    case ImGuiDataType_COUNT:  break;
1193b4be Poul Sander 2025-02-03 21:28 2668
    }
1193b4be Poul Sander 2025-02-03 21:28 2669
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 2670
    return false;
1193b4be Poul Sander 2025-02-03 21:28 2671
}
1193b4be Poul Sander 2025-02-03 21:28 2672
1193b4be Poul Sander 2025-02-03 21:28 2673
// 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.
1193b4be Poul Sander 2025-02-03 21:28 2674
// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
1193b4be Poul Sander 2025-02-03 21:28 2675
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)
1193b4be Poul Sander 2025-02-03 21:28 2676
{
1193b4be Poul Sander 2025-02-03 21:28 2677
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2678
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2679
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2680
1193b4be Poul Sander 2025-02-03 21:28 2681
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2682
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 2683
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 2684
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2685
1193b4be Poul Sander 2025-02-03 21:28 2686
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 2687
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 2688
    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));
1193b4be Poul Sander 2025-02-03 21:28 2689
1193b4be Poul Sander 2025-02-03 21:28 2690
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
1193b4be Poul Sander 2025-02-03 21:28 2691
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 2692
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
1193b4be Poul Sander 2025-02-03 21:28 2693
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2694
1193b4be Poul Sander 2025-02-03 21:28 2695
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 2696
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 2697
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 2698
1193b4be Poul Sander 2025-02-03 21:28 2699
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 2700
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
1193b4be Poul Sander 2025-02-03 21:28 2701
    if (!temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2702
    {
4e9e2555 Poul Sander 2025-10-05 10:15 2703
        // Tabbing or CTRL+click on Drag turns it into an InputText
1193b4be Poul Sander 2025-02-03 21:28 2704
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 2705
        const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
1193b4be Poul Sander 2025-02-03 21:28 2706
        const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 2707
        if (make_active && (clicked || double_clicked))
1193b4be Poul Sander 2025-02-03 21:28 2708
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 2709
        if (make_active && temp_input_allowed)
1193b4be Poul Sander 2025-02-03 21:28 2710
            if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
1193b4be Poul Sander 2025-02-03 21:28 2711
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 2712
1193b4be Poul Sander 2025-02-03 21:28 2713
        // (Optional) simple click (without moving) turns Drag into an InputText
1193b4be Poul Sander 2025-02-03 21:28 2714
        if (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2715
            if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
1193b4be Poul Sander 2025-02-03 21:28 2716
            {
1193b4be Poul Sander 2025-02-03 21:28 2717
                g.NavActivateId = id;
1193b4be Poul Sander 2025-02-03 21:28 2718
                g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
1193b4be Poul Sander 2025-02-03 21:28 2719
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 2720
            }
1193b4be Poul Sander 2025-02-03 21:28 2721
4e9e2555 Poul Sander 2025-10-05 10:15 2722
        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
4e9e2555 Poul Sander 2025-10-05 10:15 2723
        if (make_active)
4e9e2555 Poul Sander 2025-10-05 10:15 2724
            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
4e9e2555 Poul Sander 2025-10-05 10:15 2725
1193b4be Poul Sander 2025-02-03 21:28 2726
        if (make_active && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2727
        {
1193b4be Poul Sander 2025-02-03 21:28 2728
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 2729
            SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 2730
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 2731
            g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 2732
        }
1193b4be Poul Sander 2025-02-03 21:28 2733
    }
1193b4be Poul Sander 2025-02-03 21:28 2734
1193b4be Poul Sander 2025-02-03 21:28 2735
    if (temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 2736
    {
1193b4be Poul Sander 2025-02-03 21:28 2737
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
1193b4be Poul Sander 2025-02-03 21:28 2738
        bool clamp_enabled = false;
1193b4be Poul Sander 2025-02-03 21:28 2739
        if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
1193b4be Poul Sander 2025-02-03 21:28 2740
        {
1193b4be Poul Sander 2025-02-03 21:28 2741
            const int clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_max
1193b4be Poul Sander 2025-02-03 21:28 2742
            if (p_min == NULL || p_max == NULL || clamp_range_dir < 0)
1193b4be Poul Sander 2025-02-03 21:28 2743
                clamp_enabled = true;
1193b4be Poul Sander 2025-02-03 21:28 2744
            else if (clamp_range_dir == 0)
1193b4be Poul Sander 2025-02-03 21:28 2745
                clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 2746
        }
1193b4be Poul Sander 2025-02-03 21:28 2747
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
1193b4be Poul Sander 2025-02-03 21:28 2748
    }
1193b4be Poul Sander 2025-02-03 21:28 2749
1193b4be Poul Sander 2025-02-03 21:28 2750
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 2751
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 2752
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 2753
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 2754
1193b4be Poul Sander 2025-02-03 21:28 2755
    // Drag behavior
1193b4be Poul Sander 2025-02-03 21:28 2756
    const bool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2757
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 2758
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 2759
1193b4be Poul Sander 2025-02-03 21:28 2760
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1193b4be Poul Sander 2025-02-03 21:28 2761
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 2762
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 2763
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 2764
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 2765
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 2766
1193b4be Poul Sander 2025-02-03 21:28 2767
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2768
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 2769
1193b4be Poul Sander 2025-02-03 21:28 2770
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
1193b4be Poul Sander 2025-02-03 21:28 2771
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2772
}
1193b4be Poul Sander 2025-02-03 21:28 2773
1193b4be Poul Sander 2025-02-03 21:28 2774
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)
1193b4be Poul Sander 2025-02-03 21:28 2775
{
1193b4be Poul Sander 2025-02-03 21:28 2776
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2777
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2778
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2779
1193b4be Poul Sander 2025-02-03 21:28 2780
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2781
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 2782
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2783
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2784
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2785
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 2786
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 2787
    {
1193b4be Poul Sander 2025-02-03 21:28 2788
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 2789
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 2790
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2791
        value_changed |= DragScalar("", data_type, p_data, v_speed, p_min, p_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2792
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 2793
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2794
        p_data = (void*)((char*)p_data + type_size);
1193b4be Poul Sander 2025-02-03 21:28 2795
    }
1193b4be Poul Sander 2025-02-03 21:28 2796
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2797
1193b4be Poul Sander 2025-02-03 21:28 2798
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 2799
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 2800
    {
1193b4be Poul Sander 2025-02-03 21:28 2801
        SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2802
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 2803
    }
1193b4be Poul Sander 2025-02-03 21:28 2804
1193b4be Poul Sander 2025-02-03 21:28 2805
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2806
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2807
}
1193b4be Poul Sander 2025-02-03 21:28 2808
1193b4be Poul Sander 2025-02-03 21:28 2809
bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2810
{
1193b4be Poul Sander 2025-02-03 21:28 2811
    return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2812
}
1193b4be Poul Sander 2025-02-03 21:28 2813
1193b4be Poul Sander 2025-02-03 21:28 2814
bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2815
{
1193b4be Poul Sander 2025-02-03 21:28 2816
    return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2817
}
1193b4be Poul Sander 2025-02-03 21:28 2818
1193b4be Poul Sander 2025-02-03 21:28 2819
bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2820
{
1193b4be Poul Sander 2025-02-03 21:28 2821
    return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2822
}
1193b4be Poul Sander 2025-02-03 21:28 2823
1193b4be Poul Sander 2025-02-03 21:28 2824
bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2825
{
1193b4be Poul Sander 2025-02-03 21:28 2826
    return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2827
}
1193b4be Poul Sander 2025-02-03 21:28 2828
1193b4be Poul Sander 2025-02-03 21:28 2829
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
1193b4be Poul Sander 2025-02-03 21:28 2830
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)
1193b4be Poul Sander 2025-02-03 21:28 2831
{
1193b4be Poul Sander 2025-02-03 21:28 2832
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2833
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2834
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2835
1193b4be Poul Sander 2025-02-03 21:28 2836
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2837
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2838
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2839
    PushMultiItemsWidths(2, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2840
1193b4be Poul Sander 2025-02-03 21:28 2841
    float min_min = (v_min >= v_max) ? -FLT_MAX : v_min;
1193b4be Poul Sander 2025-02-03 21:28 2842
    float min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
1193b4be Poul Sander 2025-02-03 21:28 2843
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2844
    bool value_changed = DragScalar("##min", ImGuiDataType_Float, v_current_min, v_speed, &min_min, &min_max, format, min_flags);
1193b4be Poul Sander 2025-02-03 21:28 2845
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2846
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2847
1193b4be Poul Sander 2025-02-03 21:28 2848
    float max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
1193b4be Poul Sander 2025-02-03 21:28 2849
    float max_max = (v_min >= v_max) ? FLT_MAX : v_max;
1193b4be Poul Sander 2025-02-03 21:28 2850
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2851
    value_changed |= DragScalar("##max", ImGuiDataType_Float, v_current_max, v_speed, &max_min, &max_max, format_max ? format_max : format, max_flags);
1193b4be Poul Sander 2025-02-03 21:28 2852
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2853
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2854
1193b4be Poul Sander 2025-02-03 21:28 2855
    TextEx(label, FindRenderedTextEnd(label));
1193b4be Poul Sander 2025-02-03 21:28 2856
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2857
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2858
1193b4be Poul Sander 2025-02-03 21:28 2859
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2860
}
1193b4be Poul Sander 2025-02-03 21:28 2861
1193b4be Poul Sander 2025-02-03 21:28 2862
// NB: v_speed is float to allow adjusting the drag speed with more precision
1193b4be Poul Sander 2025-02-03 21:28 2863
bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2864
{
1193b4be Poul Sander 2025-02-03 21:28 2865
    return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2866
}
1193b4be Poul Sander 2025-02-03 21:28 2867
1193b4be Poul Sander 2025-02-03 21:28 2868
bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2869
{
1193b4be Poul Sander 2025-02-03 21:28 2870
    return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2871
}
1193b4be Poul Sander 2025-02-03 21:28 2872
1193b4be Poul Sander 2025-02-03 21:28 2873
bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2874
{
1193b4be Poul Sander 2025-02-03 21:28 2875
    return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2876
}
1193b4be Poul Sander 2025-02-03 21:28 2877
1193b4be Poul Sander 2025-02-03 21:28 2878
bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 2879
{
1193b4be Poul Sander 2025-02-03 21:28 2880
    return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 2881
}
1193b4be Poul Sander 2025-02-03 21:28 2882
1193b4be Poul Sander 2025-02-03 21:28 2883
// NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this.
1193b4be Poul Sander 2025-02-03 21:28 2884
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)
1193b4be Poul Sander 2025-02-03 21:28 2885
{
1193b4be Poul Sander 2025-02-03 21:28 2886
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 2887
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 2888
        return false;
1193b4be Poul Sander 2025-02-03 21:28 2889
1193b4be Poul Sander 2025-02-03 21:28 2890
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 2891
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 2892
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 2893
    PushMultiItemsWidths(2, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 2894
1193b4be Poul Sander 2025-02-03 21:28 2895
    int min_min = (v_min >= v_max) ? INT_MIN : v_min;
1193b4be Poul Sander 2025-02-03 21:28 2896
    int min_max = (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max);
1193b4be Poul Sander 2025-02-03 21:28 2897
    ImGuiSliderFlags min_flags = flags | ((min_min == min_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2898
    bool value_changed = DragInt("##min", v_current_min, v_speed, min_min, min_max, format, min_flags);
1193b4be Poul Sander 2025-02-03 21:28 2899
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2900
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2901
1193b4be Poul Sander 2025-02-03 21:28 2902
    int max_min = (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min);
1193b4be Poul Sander 2025-02-03 21:28 2903
    int max_max = (v_min >= v_max) ? INT_MAX : v_max;
1193b4be Poul Sander 2025-02-03 21:28 2904
    ImGuiSliderFlags max_flags = flags | ((max_min == max_max) ? ImGuiSliderFlags_ReadOnly : 0);
1193b4be Poul Sander 2025-02-03 21:28 2905
    value_changed |= DragInt("##max", v_current_max, v_speed, max_min, max_max, format_max ? format_max : format, max_flags);
1193b4be Poul Sander 2025-02-03 21:28 2906
    PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 2907
    SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 2908
1193b4be Poul Sander 2025-02-03 21:28 2909
    TextEx(label, FindRenderedTextEnd(label));
1193b4be Poul Sander 2025-02-03 21:28 2910
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 2911
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 2912
1193b4be Poul Sander 2025-02-03 21:28 2913
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 2914
}
1193b4be Poul Sander 2025-02-03 21:28 2915
1193b4be Poul Sander 2025-02-03 21:28 2916
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2917
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 2918
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2919
// - ScaleRatioFromValueT<> [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2920
// - ScaleValueFromRatioT<> [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2921
// - SliderBehaviorT<>() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2922
// - SliderBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 2923
// - SliderScalar()
1193b4be Poul Sander 2025-02-03 21:28 2924
// - SliderScalarN()
1193b4be Poul Sander 2025-02-03 21:28 2925
// - SliderFloat()
1193b4be Poul Sander 2025-02-03 21:28 2926
// - SliderFloat2()
1193b4be Poul Sander 2025-02-03 21:28 2927
// - SliderFloat3()
1193b4be Poul Sander 2025-02-03 21:28 2928
// - SliderFloat4()
1193b4be Poul Sander 2025-02-03 21:28 2929
// - SliderAngle()
1193b4be Poul Sander 2025-02-03 21:28 2930
// - SliderInt()
1193b4be Poul Sander 2025-02-03 21:28 2931
// - SliderInt2()
1193b4be Poul Sander 2025-02-03 21:28 2932
// - SliderInt3()
1193b4be Poul Sander 2025-02-03 21:28 2933
// - SliderInt4()
1193b4be Poul Sander 2025-02-03 21:28 2934
// - VSliderScalar()
1193b4be Poul Sander 2025-02-03 21:28 2935
// - VSliderFloat()
1193b4be Poul Sander 2025-02-03 21:28 2936
// - VSliderInt()
1193b4be Poul Sander 2025-02-03 21:28 2937
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 2938
1193b4be Poul Sander 2025-02-03 21:28 2939
// Convert a value v in the output space of a slider into a parametric position on the slider itself (the logical opposite of ScaleValueFromRatioT)
1193b4be Poul Sander 2025-02-03 21:28 2940
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2941
float ImGui::ScaleRatioFromValueT(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
1193b4be Poul Sander 2025-02-03 21:28 2942
{
1193b4be Poul Sander 2025-02-03 21:28 2943
    if (v_min == v_max)
1193b4be Poul Sander 2025-02-03 21:28 2944
        return 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 2945
    IM_UNUSED(data_type);
1193b4be Poul Sander 2025-02-03 21:28 2946
1193b4be Poul Sander 2025-02-03 21:28 2947
    const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
1193b4be Poul Sander 2025-02-03 21:28 2948
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 2949
    {
1193b4be Poul Sander 2025-02-03 21:28 2950
        bool flipped = v_max < v_min;
1193b4be Poul Sander 2025-02-03 21:28 2951
1193b4be Poul Sander 2025-02-03 21:28 2952
        if (flipped) // Handle the case where the range is backwards
1193b4be Poul Sander 2025-02-03 21:28 2953
            ImSwap(v_min, v_max);
1193b4be Poul Sander 2025-02-03 21:28 2954
1193b4be Poul Sander 2025-02-03 21:28 2955
        // Fudge min/max to avoid getting close to log(0)
1193b4be Poul Sander 2025-02-03 21:28 2956
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
1193b4be Poul Sander 2025-02-03 21:28 2957
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
1193b4be Poul Sander 2025-02-03 21:28 2958
1193b4be Poul Sander 2025-02-03 21:28 2959
        // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
1193b4be Poul Sander 2025-02-03 21:28 2960
        if ((v_min == 0.0f) && (v_max < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 2961
            v_min_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 2962
        else if ((v_max == 0.0f) && (v_min < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 2963
            v_max_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 2964
1193b4be Poul Sander 2025-02-03 21:28 2965
        float result;
1193b4be Poul Sander 2025-02-03 21:28 2966
        if (v_clamped <= v_min_fudged)
1193b4be Poul Sander 2025-02-03 21:28 2967
            result = 0.0f; // Workaround for values that are in-range but below our fudge
1193b4be Poul Sander 2025-02-03 21:28 2968
        else if (v_clamped >= v_max_fudged)
1193b4be Poul Sander 2025-02-03 21:28 2969
            result = 1.0f; // Workaround for values that are in-range but above our fudge
1193b4be Poul Sander 2025-02-03 21:28 2970
        else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions
1193b4be Poul Sander 2025-02-03 21:28 2971
        {
1193b4be Poul Sander 2025-02-03 21:28 2972
            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)
1193b4be Poul Sander 2025-02-03 21:28 2973
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2974
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 2975
            if (v == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2976
                result = zero_point_center; // Special case for exactly zero
1193b4be Poul Sander 2025-02-03 21:28 2977
            else if (v < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 2978
                result = (1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / logarithmic_zero_epsilon) / ImLog(-v_min_fudged / logarithmic_zero_epsilon))) * zero_point_snap_L;
1193b4be Poul Sander 2025-02-03 21:28 2979
            else
1193b4be Poul Sander 2025-02-03 21:28 2980
                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));
1193b4be Poul Sander 2025-02-03 21:28 2981
        }
1193b4be Poul Sander 2025-02-03 21:28 2982
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
1193b4be Poul Sander 2025-02-03 21:28 2983
            result = 1.0f - (float)(ImLog(-(FLOATTYPE)v_clamped / -v_max_fudged) / ImLog(-v_min_fudged / -v_max_fudged));
1193b4be Poul Sander 2025-02-03 21:28 2984
        else
1193b4be Poul Sander 2025-02-03 21:28 2985
            result = (float)(ImLog((FLOATTYPE)v_clamped / v_min_fudged) / ImLog(v_max_fudged / v_min_fudged));
1193b4be Poul Sander 2025-02-03 21:28 2986
1193b4be Poul Sander 2025-02-03 21:28 2987
        return flipped ? (1.0f - result) : result;
1193b4be Poul Sander 2025-02-03 21:28 2988
    }
1193b4be Poul Sander 2025-02-03 21:28 2989
    else
1193b4be Poul Sander 2025-02-03 21:28 2990
    {
1193b4be Poul Sander 2025-02-03 21:28 2991
        // Linear slider
1193b4be Poul Sander 2025-02-03 21:28 2992
        return (float)((FLOATTYPE)(SIGNEDTYPE)(v_clamped - v_min) / (FLOATTYPE)(SIGNEDTYPE)(v_max - v_min));
1193b4be Poul Sander 2025-02-03 21:28 2993
    }
1193b4be Poul Sander 2025-02-03 21:28 2994
}
1193b4be Poul Sander 2025-02-03 21:28 2995
1193b4be Poul Sander 2025-02-03 21:28 2996
// Convert a parametric position on a slider into a value v in the output space (the logical opposite of ScaleRatioFromValueT)
1193b4be Poul Sander 2025-02-03 21:28 2997
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 2998
TYPE ImGui::ScaleValueFromRatioT(ImGuiDataType data_type, float t, TYPE v_min, TYPE v_max, bool is_logarithmic, float logarithmic_zero_epsilon, float zero_deadzone_halfsize)
1193b4be Poul Sander 2025-02-03 21:28 2999
{
1193b4be Poul Sander 2025-02-03 21:28 3000
    // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct"
1193b4be Poul Sander 2025-02-03 21:28 3001
    // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler.
1193b4be Poul Sander 2025-02-03 21:28 3002
    if (t <= 0.0f || v_min == v_max)
1193b4be Poul Sander 2025-02-03 21:28 3003
        return v_min;
1193b4be Poul Sander 2025-02-03 21:28 3004
    if (t >= 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 3005
        return v_max;
1193b4be Poul Sander 2025-02-03 21:28 3006
1193b4be Poul Sander 2025-02-03 21:28 3007
    TYPE result = (TYPE)0;
1193b4be Poul Sander 2025-02-03 21:28 3008
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 3009
    {
1193b4be Poul Sander 2025-02-03 21:28 3010
        // Fudge min/max to avoid getting silly results close to zero
1193b4be Poul Sander 2025-02-03 21:28 3011
        FLOATTYPE v_min_fudged = (ImAbs((FLOATTYPE)v_min) < logarithmic_zero_epsilon) ? ((v_min < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_min;
1193b4be Poul Sander 2025-02-03 21:28 3012
        FLOATTYPE v_max_fudged = (ImAbs((FLOATTYPE)v_max) < logarithmic_zero_epsilon) ? ((v_max < 0.0f) ? -logarithmic_zero_epsilon : logarithmic_zero_epsilon) : (FLOATTYPE)v_max;
1193b4be Poul Sander 2025-02-03 21:28 3013
1193b4be Poul Sander 2025-02-03 21:28 3014
        const bool flipped = v_max < v_min; // Check if range is "backwards"
1193b4be Poul Sander 2025-02-03 21:28 3015
        if (flipped)
1193b4be Poul Sander 2025-02-03 21:28 3016
            ImSwap(v_min_fudged, v_max_fudged);
1193b4be Poul Sander 2025-02-03 21:28 3017
1193b4be Poul Sander 2025-02-03 21:28 3018
        // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon)
1193b4be Poul Sander 2025-02-03 21:28 3019
        if ((v_max == 0.0f) && (v_min < 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 3020
            v_max_fudged = -logarithmic_zero_epsilon;
1193b4be Poul Sander 2025-02-03 21:28 3021
1193b4be Poul Sander 2025-02-03 21:28 3022
        float t_with_flip = flipped ? (1.0f - t) : t; // t, but flipped if necessary to account for us flipping the range
1193b4be Poul Sander 2025-02-03 21:28 3023
1193b4be Poul Sander 2025-02-03 21:28 3024
        if ((v_min * v_max) < 0.0f) // Range crosses zero, so we have to do this in two parts
1193b4be Poul Sander 2025-02-03 21:28 3025
        {
1193b4be Poul Sander 2025-02-03 21:28 3026
            float zero_point_center = (-(float)ImMin(v_min, v_max)) / ImAbs((float)v_max - (float)v_min); // The zero point in parametric space
1193b4be Poul Sander 2025-02-03 21:28 3027
            float zero_point_snap_L = zero_point_center - zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 3028
            float zero_point_snap_R = zero_point_center + zero_deadzone_halfsize;
1193b4be Poul Sander 2025-02-03 21:28 3029
            if (t_with_flip >= zero_point_snap_L && t_with_flip <= zero_point_snap_R)
1193b4be Poul Sander 2025-02-03 21:28 3030
                result = (TYPE)0.0f; // Special case to make getting exactly zero possible (the epsilon prevents it otherwise)
1193b4be Poul Sander 2025-02-03 21:28 3031
            else if (t_with_flip < zero_point_center)
1193b4be Poul Sander 2025-02-03 21:28 3032
                result = (TYPE)-(logarithmic_zero_epsilon * ImPow(-v_min_fudged / logarithmic_zero_epsilon, (FLOATTYPE)(1.0f - (t_with_flip / zero_point_snap_L))));
1193b4be Poul Sander 2025-02-03 21:28 3033
            else
1193b4be Poul Sander 2025-02-03 21:28 3034
                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))));
1193b4be Poul Sander 2025-02-03 21:28 3035
        }
1193b4be Poul Sander 2025-02-03 21:28 3036
        else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider
1193b4be Poul Sander 2025-02-03 21:28 3037
            result = (TYPE)-(-v_max_fudged * ImPow(-v_min_fudged / -v_max_fudged, (FLOATTYPE)(1.0f - t_with_flip)));
1193b4be Poul Sander 2025-02-03 21:28 3038
        else
1193b4be Poul Sander 2025-02-03 21:28 3039
            result = (TYPE)(v_min_fudged * ImPow(v_max_fudged / v_min_fudged, (FLOATTYPE)t_with_flip));
1193b4be Poul Sander 2025-02-03 21:28 3040
    }
1193b4be Poul Sander 2025-02-03 21:28 3041
    else
1193b4be Poul Sander 2025-02-03 21:28 3042
    {
1193b4be Poul Sander 2025-02-03 21:28 3043
        // Linear slider
1193b4be Poul Sander 2025-02-03 21:28 3044
        const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 3045
        if (is_floating_point)
1193b4be Poul Sander 2025-02-03 21:28 3046
        {
1193b4be Poul Sander 2025-02-03 21:28 3047
            result = ImLerp(v_min, v_max, t);
1193b4be Poul Sander 2025-02-03 21:28 3048
        }
1193b4be Poul Sander 2025-02-03 21:28 3049
        else if (t < 1.0)
1193b4be Poul Sander 2025-02-03 21:28 3050
        {
1193b4be Poul Sander 2025-02-03 21:28 3051
            // - For integer values we want the clicking position to match the grab box so we round above
1193b4be Poul Sander 2025-02-03 21:28 3052
            //   This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
1193b4be Poul Sander 2025-02-03 21:28 3053
            // - 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
1193b4be Poul Sander 2025-02-03 21:28 3054
            //   range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits.
1193b4be Poul Sander 2025-02-03 21:28 3055
            FLOATTYPE v_new_off_f = (SIGNEDTYPE)(v_max - v_min) * t;
1193b4be Poul Sander 2025-02-03 21:28 3056
            result = (TYPE)((SIGNEDTYPE)v_min + (SIGNEDTYPE)(v_new_off_f + (FLOATTYPE)(v_min > v_max ? -0.5 : 0.5)));
1193b4be Poul Sander 2025-02-03 21:28 3057
        }
1193b4be Poul Sander 2025-02-03 21:28 3058
    }
1193b4be Poul Sander 2025-02-03 21:28 3059
1193b4be Poul Sander 2025-02-03 21:28 3060
    return result;
1193b4be Poul Sander 2025-02-03 21:28 3061
}
1193b4be Poul Sander 2025-02-03 21:28 3062
1193b4be Poul Sander 2025-02-03 21:28 3063
// FIXME: Try to move more of the code into shared SliderBehavior()
1193b4be Poul Sander 2025-02-03 21:28 3064
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
1193b4be Poul Sander 2025-02-03 21:28 3065
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)
1193b4be Poul Sander 2025-02-03 21:28 3066
{
1193b4be Poul Sander 2025-02-03 21:28 3067
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3068
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3069
1193b4be Poul Sander 2025-02-03 21:28 3070
    const ImGuiAxis axis = (flags & ImGuiSliderFlags_Vertical) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 3071
    const bool is_logarithmic = (flags & ImGuiSliderFlags_Logarithmic) != 0;
1193b4be Poul Sander 2025-02-03 21:28 3072
    const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
1193b4be Poul Sander 2025-02-03 21:28 3073
    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.
1193b4be Poul Sander 2025-02-03 21:28 3074
1193b4be Poul Sander 2025-02-03 21:28 3075
    // Calculate bounds
1193b4be Poul Sander 2025-02-03 21:28 3076
    const float grab_padding = 2.0f; // FIXME: Should be part of style.
1193b4be Poul Sander 2025-02-03 21:28 3077
    const float slider_sz = (bb.Max[axis] - bb.Min[axis]) - grab_padding * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 3078
    float grab_sz = style.GrabMinSize;
1193b4be Poul Sander 2025-02-03 21:28 3079
    if (!is_floating_point && v_range_f >= 0.0f)                         // v_range_f < 0 may happen on integer overflows
1193b4be Poul Sander 2025-02-03 21:28 3080
        grab_sz = ImMax(slider_sz / (v_range_f + 1), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
1193b4be Poul Sander 2025-02-03 21:28 3081
    grab_sz = ImMin(grab_sz, slider_sz);
1193b4be Poul Sander 2025-02-03 21:28 3082
    const float slider_usable_sz = slider_sz - grab_sz;
1193b4be Poul Sander 2025-02-03 21:28 3083
    const float slider_usable_pos_min = bb.Min[axis] + grab_padding + grab_sz * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 3084
    const float slider_usable_pos_max = bb.Max[axis] - grab_padding - grab_sz * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 3085
1193b4be Poul Sander 2025-02-03 21:28 3086
    float logarithmic_zero_epsilon = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 3087
    float zero_deadzone_halfsize = 0.0f; // Only valid when is_logarithmic is true
1193b4be Poul Sander 2025-02-03 21:28 3088
    if (is_logarithmic)
1193b4be Poul Sander 2025-02-03 21:28 3089
    {
1193b4be Poul Sander 2025-02-03 21:28 3090
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 3091
        const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 1;
1193b4be Poul Sander 2025-02-03 21:28 3092
        logarithmic_zero_epsilon = ImPow(0.1f, (float)decimal_precision);
1193b4be Poul Sander 2025-02-03 21:28 3093
        zero_deadzone_halfsize = (style.LogSliderDeadzone * 0.5f) / ImMax(slider_usable_sz, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 3094
    }
1193b4be Poul Sander 2025-02-03 21:28 3095
1193b4be Poul Sander 2025-02-03 21:28 3096
    // Process interacting with the slider
1193b4be Poul Sander 2025-02-03 21:28 3097
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3098
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 3099
    {
1193b4be Poul Sander 2025-02-03 21:28 3100
        bool set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3101
        float clicked_t = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3102
        if (g.ActiveIdSource == ImGuiInputSource_Mouse)
1193b4be Poul Sander 2025-02-03 21:28 3103
        {
1193b4be Poul Sander 2025-02-03 21:28 3104
            if (!g.IO.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 3105
            {
1193b4be Poul Sander 2025-02-03 21:28 3106
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3107
            }
1193b4be Poul Sander 2025-02-03 21:28 3108
            else
1193b4be Poul Sander 2025-02-03 21:28 3109
            {
1193b4be Poul Sander 2025-02-03 21:28 3110
                const float mouse_abs_pos = g.IO.MousePos[axis];
1193b4be Poul Sander 2025-02-03 21:28 3111
                if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3112
                {
1193b4be Poul Sander 2025-02-03 21:28 3113
                    float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3114
                    if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3115
                        grab_t = 1.0f - grab_t;
1193b4be Poul Sander 2025-02-03 21:28 3116
                    const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
1193b4be Poul Sander 2025-02-03 21:28 3117
                    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.
1193b4be Poul Sander 2025-02-03 21:28 3118
                    g.SliderGrabClickOffset = (clicked_around_grab && is_floating_point) ? mouse_abs_pos - grab_pos : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3119
                }
1193b4be Poul Sander 2025-02-03 21:28 3120
                if (slider_usable_sz > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3121
                    clicked_t = ImSaturate((mouse_abs_pos - g.SliderGrabClickOffset - slider_usable_pos_min) / slider_usable_sz);
1193b4be Poul Sander 2025-02-03 21:28 3122
                if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3123
                    clicked_t = 1.0f - clicked_t;
1193b4be Poul Sander 2025-02-03 21:28 3124
                set_new_value = true;
1193b4be Poul Sander 2025-02-03 21:28 3125
            }
1193b4be Poul Sander 2025-02-03 21:28 3126
        }
1193b4be Poul Sander 2025-02-03 21:28 3127
        else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
1193b4be Poul Sander 2025-02-03 21:28 3128
        {
1193b4be Poul Sander 2025-02-03 21:28 3129
            if (g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3130
            {
1193b4be Poul Sander 2025-02-03 21:28 3131
                g.SliderCurrentAccum = 0.0f; // Reset any stored nav delta upon activation
1193b4be Poul Sander 2025-02-03 21:28 3132
                g.SliderCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 3133
            }
1193b4be Poul Sander 2025-02-03 21:28 3134
1193b4be Poul Sander 2025-02-03 21:28 3135
            float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
1193b4be Poul Sander 2025-02-03 21:28 3136
            if (input_delta != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3137
            {
1193b4be Poul Sander 2025-02-03 21:28 3138
                const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
1193b4be Poul Sander 2025-02-03 21:28 3139
                const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
1193b4be Poul Sander 2025-02-03 21:28 3140
                const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
1193b4be Poul Sander 2025-02-03 21:28 3141
                if (decimal_precision > 0)
1193b4be Poul Sander 2025-02-03 21:28 3142
                {
1193b4be Poul Sander 2025-02-03 21:28 3143
                    input_delta /= 100.0f; // Keyboard/Gamepad tweak speeds in % of slider bounds
1193b4be Poul Sander 2025-02-03 21:28 3144
                    if (tweak_slow)
1193b4be Poul Sander 2025-02-03 21:28 3145
                        input_delta /= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 3146
                }
1193b4be Poul Sander 2025-02-03 21:28 3147
                else
1193b4be Poul Sander 2025-02-03 21:28 3148
                {
1193b4be Poul Sander 2025-02-03 21:28 3149
                    if ((v_range_f >= -100.0f && v_range_f <= 100.0f && v_range_f != 0.0f) || tweak_slow)
1193b4be Poul Sander 2025-02-03 21:28 3150
                        input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / v_range_f; // Keyboard/Gamepad tweak speeds in integer steps
1193b4be Poul Sander 2025-02-03 21:28 3151
                    else
1193b4be Poul Sander 2025-02-03 21:28 3152
                        input_delta /= 100.0f;
1193b4be Poul Sander 2025-02-03 21:28 3153
                }
1193b4be Poul Sander 2025-02-03 21:28 3154
                if (tweak_fast)
1193b4be Poul Sander 2025-02-03 21:28 3155
                    input_delta *= 10.0f;
1193b4be Poul Sander 2025-02-03 21:28 3156
1193b4be Poul Sander 2025-02-03 21:28 3157
                g.SliderCurrentAccum += input_delta;
1193b4be Poul Sander 2025-02-03 21:28 3158
                g.SliderCurrentAccumDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 3159
            }
1193b4be Poul Sander 2025-02-03 21:28 3160
1193b4be Poul Sander 2025-02-03 21:28 3161
            float delta = g.SliderCurrentAccum;
1193b4be Poul Sander 2025-02-03 21:28 3162
            if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 3163
            {
1193b4be Poul Sander 2025-02-03 21:28 3164
                ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3165
            }
1193b4be Poul Sander 2025-02-03 21:28 3166
            else if (g.SliderCurrentAccumDirty)
1193b4be Poul Sander 2025-02-03 21:28 3167
            {
1193b4be Poul Sander 2025-02-03 21:28 3168
                clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3169
1193b4be Poul Sander 2025-02-03 21:28 3170
                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
1193b4be Poul Sander 2025-02-03 21:28 3171
                {
1193b4be Poul Sander 2025-02-03 21:28 3172
                    set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3173
                    g.SliderCurrentAccum = 0.0f; // If pushing up against the limits, don't continue to accumulate
1193b4be Poul Sander 2025-02-03 21:28 3174
                }
1193b4be Poul Sander 2025-02-03 21:28 3175
                else
1193b4be Poul Sander 2025-02-03 21:28 3176
                {
1193b4be Poul Sander 2025-02-03 21:28 3177
                    set_new_value = true;
1193b4be Poul Sander 2025-02-03 21:28 3178
                    float old_clicked_t = clicked_t;
1193b4be Poul Sander 2025-02-03 21:28 3179
                    clicked_t = ImSaturate(clicked_t + delta);
1193b4be Poul Sander 2025-02-03 21:28 3180
1193b4be Poul Sander 2025-02-03 21:28 3181
                    // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
1193b4be Poul Sander 2025-02-03 21:28 3182
                    TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3183
                    if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 3184
                        v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
1193b4be Poul Sander 2025-02-03 21:28 3185
                    float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3186
1193b4be Poul Sander 2025-02-03 21:28 3187
                    if (delta > 0)
1193b4be Poul Sander 2025-02-03 21:28 3188
                        g.SliderCurrentAccum -= ImMin(new_clicked_t - old_clicked_t, delta);
1193b4be Poul Sander 2025-02-03 21:28 3189
                    else
1193b4be Poul Sander 2025-02-03 21:28 3190
                        g.SliderCurrentAccum -= ImMax(new_clicked_t - old_clicked_t, delta);
1193b4be Poul Sander 2025-02-03 21:28 3191
                }
1193b4be Poul Sander 2025-02-03 21:28 3192
1193b4be Poul Sander 2025-02-03 21:28 3193
                g.SliderCurrentAccumDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 3194
            }
1193b4be Poul Sander 2025-02-03 21:28 3195
        }
1193b4be Poul Sander 2025-02-03 21:28 3196
1193b4be Poul Sander 2025-02-03 21:28 3197
        if (set_new_value)
1193b4be Poul Sander 2025-02-03 21:28 3198
            if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
1193b4be Poul Sander 2025-02-03 21:28 3199
                set_new_value = false;
1193b4be Poul Sander 2025-02-03 21:28 3200
1193b4be Poul Sander 2025-02-03 21:28 3201
        if (set_new_value)
1193b4be Poul Sander 2025-02-03 21:28 3202
        {
1193b4be Poul Sander 2025-02-03 21:28 3203
            TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3204
1193b4be Poul Sander 2025-02-03 21:28 3205
            // Round to user desired precision based on format string
1193b4be Poul Sander 2025-02-03 21:28 3206
            if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
1193b4be Poul Sander 2025-02-03 21:28 3207
                v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
1193b4be Poul Sander 2025-02-03 21:28 3208
1193b4be Poul Sander 2025-02-03 21:28 3209
            // Apply result
1193b4be Poul Sander 2025-02-03 21:28 3210
            if (*v != v_new)
1193b4be Poul Sander 2025-02-03 21:28 3211
            {
1193b4be Poul Sander 2025-02-03 21:28 3212
                *v = v_new;
1193b4be Poul Sander 2025-02-03 21:28 3213
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3214
            }
1193b4be Poul Sander 2025-02-03 21:28 3215
        }
1193b4be Poul Sander 2025-02-03 21:28 3216
    }
1193b4be Poul Sander 2025-02-03 21:28 3217
1193b4be Poul Sander 2025-02-03 21:28 3218
    if (slider_sz < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 3219
    {
1193b4be Poul Sander 2025-02-03 21:28 3220
        *out_grab_bb = ImRect(bb.Min, bb.Min);
1193b4be Poul Sander 2025-02-03 21:28 3221
    }
1193b4be Poul Sander 2025-02-03 21:28 3222
    else
1193b4be Poul Sander 2025-02-03 21:28 3223
    {
1193b4be Poul Sander 2025-02-03 21:28 3224
        // Output grab position so it can be displayed by the caller
1193b4be Poul Sander 2025-02-03 21:28 3225
        float grab_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, *v, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
1193b4be Poul Sander 2025-02-03 21:28 3226
        if (axis == ImGuiAxis_Y)
1193b4be Poul Sander 2025-02-03 21:28 3227
            grab_t = 1.0f - grab_t;
1193b4be Poul Sander 2025-02-03 21:28 3228
        const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
1193b4be Poul Sander 2025-02-03 21:28 3229
        if (axis == ImGuiAxis_X)
1193b4be Poul Sander 2025-02-03 21:28 3230
            *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);
1193b4be Poul Sander 2025-02-03 21:28 3231
        else
1193b4be Poul Sander 2025-02-03 21:28 3232
            *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);
1193b4be Poul Sander 2025-02-03 21:28 3233
    }
1193b4be Poul Sander 2025-02-03 21:28 3234
1193b4be Poul Sander 2025-02-03 21:28 3235
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3236
}
1193b4be Poul Sander 2025-02-03 21:28 3237
1193b4be Poul Sander 2025-02-03 21:28 3238
// For 32-bit and larger types, slider bounds are limited to half the natural type range.
1193b4be Poul Sander 2025-02-03 21:28 3239
// 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.
1193b4be Poul Sander 2025-02-03 21:28 3240
// It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
1193b4be Poul Sander 2025-02-03 21:28 3241
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)
1193b4be Poul Sander 2025-02-03 21:28 3242
{
1193b4be Poul Sander 2025-02-03 21:28 3243
    // Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
1193b4be Poul Sander 2025-02-03 21:28 3244
    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.");
1193b4be Poul Sander 2025-02-03 21:28 3245
    IM_ASSERT((flags & ImGuiSliderFlags_WrapAround) == 0); // Not supported by SliderXXX(), only by DragXXX()
1193b4be Poul Sander 2025-02-03 21:28 3246
1193b4be Poul Sander 2025-02-03 21:28 3247
    switch (data_type)
1193b4be Poul Sander 2025-02-03 21:28 3248
    {
1193b4be Poul Sander 2025-02-03 21:28 3249
    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; }
1193b4be Poul Sander 2025-02-03 21:28 3250
    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; }
1193b4be Poul Sander 2025-02-03 21:28 3251
    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; }
1193b4be Poul Sander 2025-02-03 21:28 3252
    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; }
1193b4be Poul Sander 2025-02-03 21:28 3253
    case ImGuiDataType_S32:
1193b4be Poul Sander 2025-02-03 21:28 3254
        IM_ASSERT(*(const ImS32*)p_min >= IM_S32_MIN / 2 && *(const ImS32*)p_max <= IM_S32_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3255
        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);
1193b4be Poul Sander 2025-02-03 21:28 3256
    case ImGuiDataType_U32:
1193b4be Poul Sander 2025-02-03 21:28 3257
        IM_ASSERT(*(const ImU32*)p_max <= IM_U32_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3258
        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);
1193b4be Poul Sander 2025-02-03 21:28 3259
    case ImGuiDataType_S64:
1193b4be Poul Sander 2025-02-03 21:28 3260
        IM_ASSERT(*(const ImS64*)p_min >= IM_S64_MIN / 2 && *(const ImS64*)p_max <= IM_S64_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3261
        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);
1193b4be Poul Sander 2025-02-03 21:28 3262
    case ImGuiDataType_U64:
1193b4be Poul Sander 2025-02-03 21:28 3263
        IM_ASSERT(*(const ImU64*)p_max <= IM_U64_MAX / 2);
1193b4be Poul Sander 2025-02-03 21:28 3264
        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);
1193b4be Poul Sander 2025-02-03 21:28 3265
    case ImGuiDataType_Float:
1193b4be Poul Sander 2025-02-03 21:28 3266
        IM_ASSERT(*(const float*)p_min >= -FLT_MAX / 2.0f && *(const float*)p_max <= FLT_MAX / 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 3267
        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);
1193b4be Poul Sander 2025-02-03 21:28 3268
    case ImGuiDataType_Double:
1193b4be Poul Sander 2025-02-03 21:28 3269
        IM_ASSERT(*(const double*)p_min >= -DBL_MAX / 2.0f && *(const double*)p_max <= DBL_MAX / 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 3270
        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);
1193b4be Poul Sander 2025-02-03 21:28 3271
    case ImGuiDataType_COUNT: break;
1193b4be Poul Sander 2025-02-03 21:28 3272
    }
1193b4be Poul Sander 2025-02-03 21:28 3273
    IM_ASSERT(0);
1193b4be Poul Sander 2025-02-03 21:28 3274
    return false;
1193b4be Poul Sander 2025-02-03 21:28 3275
}
1193b4be Poul Sander 2025-02-03 21:28 3276
1193b4be Poul Sander 2025-02-03 21:28 3277
// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required.
1193b4be Poul Sander 2025-02-03 21:28 3278
// Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
1193b4be Poul Sander 2025-02-03 21:28 3279
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)
1193b4be Poul Sander 2025-02-03 21:28 3280
{
1193b4be Poul Sander 2025-02-03 21:28 3281
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3282
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3283
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3284
1193b4be Poul Sander 2025-02-03 21:28 3285
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3286
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3287
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 3288
    const float w = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3289
1193b4be Poul Sander 2025-02-03 21:28 3290
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 3291
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 3292
    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));
1193b4be Poul Sander 2025-02-03 21:28 3293
1193b4be Poul Sander 2025-02-03 21:28 3294
    const bool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
1193b4be Poul Sander 2025-02-03 21:28 3295
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 3296
    if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
1193b4be Poul Sander 2025-02-03 21:28 3297
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3298
1193b4be Poul Sander 2025-02-03 21:28 3299
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 3300
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3301
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3302
1193b4be Poul Sander 2025-02-03 21:28 3303
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 3304
    bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
1193b4be Poul Sander 2025-02-03 21:28 3305
    if (!temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3306
    {
4e9e2555 Poul Sander 2025-10-05 10:15 3307
        // Tabbing or CTRL+click on Slider turns it into an input box
1193b4be Poul Sander 2025-02-03 21:28 3308
        const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 3309
        const bool make_active = (clicked || g.NavActivateId == id);
1193b4be Poul Sander 2025-02-03 21:28 3310
        if (make_active && clicked)
1193b4be Poul Sander 2025-02-03 21:28 3311
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 3312
        if (make_active && temp_input_allowed)
1193b4be Poul Sander 2025-02-03 21:28 3313
            if ((clicked && g.IO.KeyCtrl) || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
1193b4be Poul Sander 2025-02-03 21:28 3314
                temp_input_is_active = true;
1193b4be Poul Sander 2025-02-03 21:28 3315
4e9e2555 Poul Sander 2025-10-05 10:15 3316
        // Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)
4e9e2555 Poul Sander 2025-10-05 10:15 3317
        if (make_active)
4e9e2555 Poul Sander 2025-10-05 10:15 3318
            memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size);
4e9e2555 Poul Sander 2025-10-05 10:15 3319
1193b4be Poul Sander 2025-02-03 21:28 3320
        if (make_active && !temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3321
        {
1193b4be Poul Sander 2025-02-03 21:28 3322
            SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3323
            SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3324
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 3325
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 3326
        }
1193b4be Poul Sander 2025-02-03 21:28 3327
    }
1193b4be Poul Sander 2025-02-03 21:28 3328
1193b4be Poul Sander 2025-02-03 21:28 3329
    if (temp_input_is_active)
1193b4be Poul Sander 2025-02-03 21:28 3330
    {
1193b4be Poul Sander 2025-02-03 21:28 3331
        // Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)
1193b4be Poul Sander 2025-02-03 21:28 3332
        const bool clamp_enabled = (flags & ImGuiSliderFlags_ClampOnInput) != 0;
1193b4be Poul Sander 2025-02-03 21:28 3333
        return TempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
1193b4be Poul Sander 2025-02-03 21:28 3334
    }
1193b4be Poul Sander 2025-02-03 21:28 3335
1193b4be Poul Sander 2025-02-03 21:28 3336
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 3337
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 3338
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 3339
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 3340
1193b4be Poul Sander 2025-02-03 21:28 3341
    // Slider behavior
1193b4be Poul Sander 2025-02-03 21:28 3342
    ImRect grab_bb;
1193b4be Poul Sander 2025-02-03 21:28 3343
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags, &grab_bb);
1193b4be Poul Sander 2025-02-03 21:28 3344
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3345
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3346
1193b4be Poul Sander 2025-02-03 21:28 3347
    // Render grab
1193b4be Poul Sander 2025-02-03 21:28 3348
    if (grab_bb.Max.x > grab_bb.Min.x)
1193b4be Poul Sander 2025-02-03 21:28 3349
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
1193b4be Poul Sander 2025-02-03 21:28 3350
1193b4be Poul Sander 2025-02-03 21:28 3351
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1193b4be Poul Sander 2025-02-03 21:28 3352
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3353
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3354
    if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 3355
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 3356
    RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 3357
1193b4be Poul Sander 2025-02-03 21:28 3358
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3359
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 3360
1193b4be Poul Sander 2025-02-03 21:28 3361
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
1193b4be Poul Sander 2025-02-03 21:28 3362
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3363
}
1193b4be Poul Sander 2025-02-03 21:28 3364
1193b4be Poul Sander 2025-02-03 21:28 3365
// Add multiple sliders on 1 line for compact edition of multiple components
1193b4be Poul Sander 2025-02-03 21:28 3366
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)
1193b4be Poul Sander 2025-02-03 21:28 3367
{
1193b4be Poul Sander 2025-02-03 21:28 3368
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3369
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3370
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3371
1193b4be Poul Sander 2025-02-03 21:28 3372
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3373
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3374
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 3375
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3376
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 3377
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 3378
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 3379
    {
1193b4be Poul Sander 2025-02-03 21:28 3380
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 3381
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 3382
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3383
        value_changed |= SliderScalar("", data_type, v, v_min, v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3384
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3385
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3386
        v = (void*)((char*)v + type_size);
1193b4be Poul Sander 2025-02-03 21:28 3387
    }
1193b4be Poul Sander 2025-02-03 21:28 3388
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 3389
1193b4be Poul Sander 2025-02-03 21:28 3390
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3391
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3392
    {
1193b4be Poul Sander 2025-02-03 21:28 3393
        SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3394
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3395
    }
1193b4be Poul Sander 2025-02-03 21:28 3396
1193b4be Poul Sander 2025-02-03 21:28 3397
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3398
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3399
}
1193b4be Poul Sander 2025-02-03 21:28 3400
1193b4be Poul Sander 2025-02-03 21:28 3401
bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3402
{
1193b4be Poul Sander 2025-02-03 21:28 3403
    return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3404
}
1193b4be Poul Sander 2025-02-03 21:28 3405
1193b4be Poul Sander 2025-02-03 21:28 3406
bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3407
{
1193b4be Poul Sander 2025-02-03 21:28 3408
    return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3409
}
1193b4be Poul Sander 2025-02-03 21:28 3410
1193b4be Poul Sander 2025-02-03 21:28 3411
bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3412
{
1193b4be Poul Sander 2025-02-03 21:28 3413
    return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3414
}
1193b4be Poul Sander 2025-02-03 21:28 3415
1193b4be Poul Sander 2025-02-03 21:28 3416
bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3417
{
1193b4be Poul Sander 2025-02-03 21:28 3418
    return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3419
}
1193b4be Poul Sander 2025-02-03 21:28 3420
1193b4be Poul Sander 2025-02-03 21:28 3421
bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3422
{
1193b4be Poul Sander 2025-02-03 21:28 3423
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3424
        format = "%.0f deg";
1193b4be Poul Sander 2025-02-03 21:28 3425
    float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 3426
    bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, format, flags);
4e9e2555 Poul Sander 2025-10-05 10:15 3427
    if (value_changed)
4e9e2555 Poul Sander 2025-10-05 10:15 3428
        *v_rad = v_deg * (2 * IM_PI) / 360.0f;
1193b4be Poul Sander 2025-02-03 21:28 3429
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3430
}
1193b4be Poul Sander 2025-02-03 21:28 3431
1193b4be Poul Sander 2025-02-03 21:28 3432
bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3433
{
1193b4be Poul Sander 2025-02-03 21:28 3434
    return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3435
}
1193b4be Poul Sander 2025-02-03 21:28 3436
1193b4be Poul Sander 2025-02-03 21:28 3437
bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3438
{
1193b4be Poul Sander 2025-02-03 21:28 3439
    return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3440
}
1193b4be Poul Sander 2025-02-03 21:28 3441
1193b4be Poul Sander 2025-02-03 21:28 3442
bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3443
{
1193b4be Poul Sander 2025-02-03 21:28 3444
    return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3445
}
1193b4be Poul Sander 2025-02-03 21:28 3446
1193b4be Poul Sander 2025-02-03 21:28 3447
bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3448
{
1193b4be Poul Sander 2025-02-03 21:28 3449
    return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3450
}
1193b4be Poul Sander 2025-02-03 21:28 3451
1193b4be Poul Sander 2025-02-03 21:28 3452
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)
1193b4be Poul Sander 2025-02-03 21:28 3453
{
1193b4be Poul Sander 2025-02-03 21:28 3454
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3455
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3456
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3457
1193b4be Poul Sander 2025-02-03 21:28 3458
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3459
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3460
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 3461
1193b4be Poul Sander 2025-02-03 21:28 3462
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 3463
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 3464
    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));
1193b4be Poul Sander 2025-02-03 21:28 3465
1193b4be Poul Sander 2025-02-03 21:28 3466
    ItemSize(bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 3467
    if (!ItemAdd(frame_bb, id))
1193b4be Poul Sander 2025-02-03 21:28 3468
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3469
1193b4be Poul Sander 2025-02-03 21:28 3470
    // Default format string when passing NULL
1193b4be Poul Sander 2025-02-03 21:28 3471
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3472
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3473
1193b4be Poul Sander 2025-02-03 21:28 3474
    const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
1193b4be Poul Sander 2025-02-03 21:28 3475
    const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
1193b4be Poul Sander 2025-02-03 21:28 3476
    if (clicked || g.NavActivateId == id)
1193b4be Poul Sander 2025-02-03 21:28 3477
    {
1193b4be Poul Sander 2025-02-03 21:28 3478
        if (clicked)
1193b4be Poul Sander 2025-02-03 21:28 3479
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 3480
        SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3481
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 3482
        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 3483
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1193b4be Poul Sander 2025-02-03 21:28 3484
    }
1193b4be Poul Sander 2025-02-03 21:28 3485
1193b4be Poul Sander 2025-02-03 21:28 3486
    // Draw frame
1193b4be Poul Sander 2025-02-03 21:28 3487
    const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
1193b4be Poul Sander 2025-02-03 21:28 3488
    RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 3489
    RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, g.Style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 3490
1193b4be Poul Sander 2025-02-03 21:28 3491
    // Slider behavior
1193b4be Poul Sander 2025-02-03 21:28 3492
    ImRect grab_bb;
1193b4be Poul Sander 2025-02-03 21:28 3493
    const bool value_changed = SliderBehavior(frame_bb, id, data_type, p_data, p_min, p_max, format, flags | ImGuiSliderFlags_Vertical, &grab_bb);
1193b4be Poul Sander 2025-02-03 21:28 3494
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3495
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3496
1193b4be Poul Sander 2025-02-03 21:28 3497
    // Render grab
1193b4be Poul Sander 2025-02-03 21:28 3498
    if (grab_bb.Max.y > grab_bb.Min.y)
1193b4be Poul Sander 2025-02-03 21:28 3499
        window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
1193b4be Poul Sander 2025-02-03 21:28 3500
1193b4be Poul Sander 2025-02-03 21:28 3501
    // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
1193b4be Poul Sander 2025-02-03 21:28 3502
    // For the vertical slider we allow centered text to overlap the frame padding
1193b4be Poul Sander 2025-02-03 21:28 3503
    char value_buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3504
    const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3505
    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));
1193b4be Poul Sander 2025-02-03 21:28 3506
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 3507
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 3508
1193b4be Poul Sander 2025-02-03 21:28 3509
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3510
}
1193b4be Poul Sander 2025-02-03 21:28 3511
1193b4be Poul Sander 2025-02-03 21:28 3512
bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3513
{
1193b4be Poul Sander 2025-02-03 21:28 3514
    return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3515
}
1193b4be Poul Sander 2025-02-03 21:28 3516
1193b4be Poul Sander 2025-02-03 21:28 3517
bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3518
{
1193b4be Poul Sander 2025-02-03 21:28 3519
    return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3520
}
1193b4be Poul Sander 2025-02-03 21:28 3521
1193b4be Poul Sander 2025-02-03 21:28 3522
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3523
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
1193b4be Poul Sander 2025-02-03 21:28 3524
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3525
// - ImParseFormatFindStart() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3526
// - ImParseFormatFindEnd() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3527
// - ImParseFormatTrimDecorations() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3528
// - ImParseFormatSanitizeForPrinting() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3529
// - ImParseFormatSanitizeForScanning() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3530
// - ImParseFormatPrecision() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3531
// - TempInputTextScalar() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3532
// - InputScalar()
1193b4be Poul Sander 2025-02-03 21:28 3533
// - InputScalarN()
1193b4be Poul Sander 2025-02-03 21:28 3534
// - InputFloat()
1193b4be Poul Sander 2025-02-03 21:28 3535
// - InputFloat2()
1193b4be Poul Sander 2025-02-03 21:28 3536
// - InputFloat3()
1193b4be Poul Sander 2025-02-03 21:28 3537
// - InputFloat4()
1193b4be Poul Sander 2025-02-03 21:28 3538
// - InputInt()
1193b4be Poul Sander 2025-02-03 21:28 3539
// - InputInt2()
1193b4be Poul Sander 2025-02-03 21:28 3540
// - InputInt3()
1193b4be Poul Sander 2025-02-03 21:28 3541
// - InputInt4()
1193b4be Poul Sander 2025-02-03 21:28 3542
// - InputDouble()
1193b4be Poul Sander 2025-02-03 21:28 3543
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3544
1193b4be Poul Sander 2025-02-03 21:28 3545
// We don't use strchr() because our strings are usually very short and often start with '%'
1193b4be Poul Sander 2025-02-03 21:28 3546
const char* ImParseFormatFindStart(const char* fmt)
1193b4be Poul Sander 2025-02-03 21:28 3547
{
1193b4be Poul Sander 2025-02-03 21:28 3548
    while (char c = fmt[0])
1193b4be Poul Sander 2025-02-03 21:28 3549
    {
1193b4be Poul Sander 2025-02-03 21:28 3550
        if (c == '%' && fmt[1] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3551
            return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3552
        else if (c == '%')
1193b4be Poul Sander 2025-02-03 21:28 3553
            fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3554
        fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3555
    }
1193b4be Poul Sander 2025-02-03 21:28 3556
    return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3557
}
1193b4be Poul Sander 2025-02-03 21:28 3558
1193b4be Poul Sander 2025-02-03 21:28 3559
const char* ImParseFormatFindEnd(const char* fmt)
1193b4be Poul Sander 2025-02-03 21:28 3560
{
1193b4be Poul Sander 2025-02-03 21:28 3561
    // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
1193b4be Poul Sander 2025-02-03 21:28 3562
    if (fmt[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3563
        return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3564
    const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
1193b4be Poul Sander 2025-02-03 21:28 3565
    const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
1193b4be Poul Sander 2025-02-03 21:28 3566
    for (char c; (c = *fmt) != 0; fmt++)
1193b4be Poul Sander 2025-02-03 21:28 3567
    {
1193b4be Poul Sander 2025-02-03 21:28 3568
        if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3569
            return fmt + 1;
1193b4be Poul Sander 2025-02-03 21:28 3570
        if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3571
            return fmt + 1;
1193b4be Poul Sander 2025-02-03 21:28 3572
    }
1193b4be Poul Sander 2025-02-03 21:28 3573
    return fmt;
1193b4be Poul Sander 2025-02-03 21:28 3574
}
1193b4be Poul Sander 2025-02-03 21:28 3575
1193b4be Poul Sander 2025-02-03 21:28 3576
// Extract the format out of a format string with leading or trailing decorations
1193b4be Poul Sander 2025-02-03 21:28 3577
//  fmt = "blah blah"  -> return ""
1193b4be Poul Sander 2025-02-03 21:28 3578
//  fmt = "%.3f"       -> return fmt
1193b4be Poul Sander 2025-02-03 21:28 3579
//  fmt = "hello %.3f" -> return fmt + 6
1193b4be Poul Sander 2025-02-03 21:28 3580
//  fmt = "%.3f hello" -> return buf written with "%.3f"
1193b4be Poul Sander 2025-02-03 21:28 3581
const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_size)
1193b4be Poul Sander 2025-02-03 21:28 3582
{
1193b4be Poul Sander 2025-02-03 21:28 3583
    const char* fmt_start = ImParseFormatFindStart(fmt);
1193b4be Poul Sander 2025-02-03 21:28 3584
    if (fmt_start[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3585
        return "";
1193b4be Poul Sander 2025-02-03 21:28 3586
    const char* fmt_end = ImParseFormatFindEnd(fmt_start);
1193b4be Poul Sander 2025-02-03 21:28 3587
    if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
1193b4be Poul Sander 2025-02-03 21:28 3588
        return fmt_start;
1193b4be Poul Sander 2025-02-03 21:28 3589
    ImStrncpy(buf, fmt_start, ImMin((size_t)(fmt_end - fmt_start) + 1, buf_size));
1193b4be Poul Sander 2025-02-03 21:28 3590
    return buf;
1193b4be Poul Sander 2025-02-03 21:28 3591
}
1193b4be Poul Sander 2025-02-03 21:28 3592
1193b4be Poul Sander 2025-02-03 21:28 3593
// Sanitize format
1193b4be Poul Sander 2025-02-03 21:28 3594
// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
1193b4be Poul Sander 2025-02-03 21:28 3595
// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
1193b4be Poul Sander 2025-02-03 21:28 3596
void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
1193b4be Poul Sander 2025-02-03 21:28 3597
{
1193b4be Poul Sander 2025-02-03 21:28 3598
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
1193b4be Poul Sander 2025-02-03 21:28 3599
    IM_UNUSED(fmt_out_size);
1193b4be Poul Sander 2025-02-03 21:28 3600
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
1193b4be Poul Sander 2025-02-03 21:28 3601
    while (fmt_in < fmt_end)
1193b4be Poul Sander 2025-02-03 21:28 3602
    {
1193b4be Poul Sander 2025-02-03 21:28 3603
        char c = *fmt_in++;
1193b4be Poul Sander 2025-02-03 21:28 3604
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
1193b4be Poul Sander 2025-02-03 21:28 3605
            *(fmt_out++) = c;
1193b4be Poul Sander 2025-02-03 21:28 3606
    }
1193b4be Poul Sander 2025-02-03 21:28 3607
    *fmt_out = 0; // Zero-terminate
1193b4be Poul Sander 2025-02-03 21:28 3608
}
1193b4be Poul Sander 2025-02-03 21:28 3609
1193b4be Poul Sander 2025-02-03 21:28 3610
// - 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"
1193b4be Poul Sander 2025-02-03 21:28 3611
const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
1193b4be Poul Sander 2025-02-03 21:28 3612
{
1193b4be Poul Sander 2025-02-03 21:28 3613
    const char* fmt_end = ImParseFormatFindEnd(fmt_in);
1193b4be Poul Sander 2025-02-03 21:28 3614
    const char* fmt_out_begin = fmt_out;
1193b4be Poul Sander 2025-02-03 21:28 3615
    IM_UNUSED(fmt_out_size);
1193b4be Poul Sander 2025-02-03 21:28 3616
    IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
1193b4be Poul Sander 2025-02-03 21:28 3617
    bool has_type = false;
1193b4be Poul Sander 2025-02-03 21:28 3618
    while (fmt_in < fmt_end)
1193b4be Poul Sander 2025-02-03 21:28 3619
    {
1193b4be Poul Sander 2025-02-03 21:28 3620
        char c = *fmt_in++;
1193b4be Poul Sander 2025-02-03 21:28 3621
        if (!has_type && ((c >= '0' && c <= '9') || c == '.' || c == '+' || c == '#'))
1193b4be Poul Sander 2025-02-03 21:28 3622
            continue;
1193b4be Poul Sander 2025-02-03 21:28 3623
        has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
1193b4be Poul Sander 2025-02-03 21:28 3624
        if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
1193b4be Poul Sander 2025-02-03 21:28 3625
            *(fmt_out++) = c;
1193b4be Poul Sander 2025-02-03 21:28 3626
    }
1193b4be Poul Sander 2025-02-03 21:28 3627
    *fmt_out = 0; // Zero-terminate
1193b4be Poul Sander 2025-02-03 21:28 3628
    return fmt_out_begin;
1193b4be Poul Sander 2025-02-03 21:28 3629
}
1193b4be Poul Sander 2025-02-03 21:28 3630
1193b4be Poul Sander 2025-02-03 21:28 3631
template<typename TYPE>
1193b4be Poul Sander 2025-02-03 21:28 3632
static const char* ImAtoi(const char* src, TYPE* output)
1193b4be Poul Sander 2025-02-03 21:28 3633
{
1193b4be Poul Sander 2025-02-03 21:28 3634
    int negative = 0;
1193b4be Poul Sander 2025-02-03 21:28 3635
    if (*src == '-') { negative = 1; src++; }
1193b4be Poul Sander 2025-02-03 21:28 3636
    if (*src == '+') { src++; }
1193b4be Poul Sander 2025-02-03 21:28 3637
    TYPE v = 0;
1193b4be Poul Sander 2025-02-03 21:28 3638
    while (*src >= '0' && *src <= '9')
1193b4be Poul Sander 2025-02-03 21:28 3639
        v = (v * 10) + (*src++ - '0');
1193b4be Poul Sander 2025-02-03 21:28 3640
    *output = negative ? -v : v;
1193b4be Poul Sander 2025-02-03 21:28 3641
    return src;
1193b4be Poul Sander 2025-02-03 21:28 3642
}
1193b4be Poul Sander 2025-02-03 21:28 3643
1193b4be Poul Sander 2025-02-03 21:28 3644
// Parse display precision back from the display format string
1193b4be Poul Sander 2025-02-03 21:28 3645
// 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.
1193b4be Poul Sander 2025-02-03 21:28 3646
int ImParseFormatPrecision(const char* fmt, int default_precision)
1193b4be Poul Sander 2025-02-03 21:28 3647
{
1193b4be Poul Sander 2025-02-03 21:28 3648
    fmt = ImParseFormatFindStart(fmt);
1193b4be Poul Sander 2025-02-03 21:28 3649
    if (fmt[0] != '%')
1193b4be Poul Sander 2025-02-03 21:28 3650
        return default_precision;
1193b4be Poul Sander 2025-02-03 21:28 3651
    fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3652
    while (*fmt >= '0' && *fmt <= '9')
1193b4be Poul Sander 2025-02-03 21:28 3653
        fmt++;
1193b4be Poul Sander 2025-02-03 21:28 3654
    int precision = INT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 3655
    if (*fmt == '.')
1193b4be Poul Sander 2025-02-03 21:28 3656
    {
1193b4be Poul Sander 2025-02-03 21:28 3657
        fmt = ImAtoi<int>(fmt + 1, &precision);
1193b4be Poul Sander 2025-02-03 21:28 3658
        if (precision < 0 || precision > 99)
1193b4be Poul Sander 2025-02-03 21:28 3659
            precision = default_precision;
1193b4be Poul Sander 2025-02-03 21:28 3660
    }
1193b4be Poul Sander 2025-02-03 21:28 3661
    if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
1193b4be Poul Sander 2025-02-03 21:28 3662
        precision = -1;
1193b4be Poul Sander 2025-02-03 21:28 3663
    if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 3664
        precision = -1;
1193b4be Poul Sander 2025-02-03 21:28 3665
    return (precision == INT_MAX) ? default_precision : precision;
1193b4be Poul Sander 2025-02-03 21:28 3666
}
1193b4be Poul Sander 2025-02-03 21:28 3667
1193b4be Poul Sander 2025-02-03 21:28 3668
// Create text input in place of another active widget (e.g. used when doing a CTRL+Click on drag/slider widgets)
1193b4be Poul Sander 2025-02-03 21:28 3669
// FIXME: Facilitate using this in variety of other situations.
1193b4be Poul Sander 2025-02-03 21:28 3670
// FIXME: Among other things, setting ImGuiItemFlags_AllowDuplicateId in LastItemData is currently correct but
1193b4be Poul Sander 2025-02-03 21:28 3671
// the expected relationship between TempInputXXX functions and LastItemData is a little fishy.
1193b4be Poul Sander 2025-02-03 21:28 3672
bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char* buf, int buf_size, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3673
{
1193b4be Poul Sander 2025-02-03 21:28 3674
    // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id.
1193b4be Poul Sander 2025-02-03 21:28 3675
    // We clear ActiveID on the first frame to allow the InputText() taking it back.
1193b4be Poul Sander 2025-02-03 21:28 3676
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3677
    const bool init = (g.TempInputId != id);
1193b4be Poul Sander 2025-02-03 21:28 3678
    if (init)
1193b4be Poul Sander 2025-02-03 21:28 3679
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 3680
1193b4be Poul Sander 2025-02-03 21:28 3681
    g.CurrentWindow->DC.CursorPos = bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 3682
    g.LastItemData.ItemFlags |= ImGuiItemFlags_AllowDuplicateId;
1193b4be Poul Sander 2025-02-03 21:28 3683
    bool value_changed = InputTextEx(label, NULL, buf, buf_size, bb.GetSize(), flags | ImGuiInputTextFlags_MergedItem);
1193b4be Poul Sander 2025-02-03 21:28 3684
    if (init)
1193b4be Poul Sander 2025-02-03 21:28 3685
    {
1193b4be Poul Sander 2025-02-03 21:28 3686
        // First frame we started displaying the InputText widget, we expect it to take the active id.
1193b4be Poul Sander 2025-02-03 21:28 3687
        IM_ASSERT(g.ActiveId == id);
1193b4be Poul Sander 2025-02-03 21:28 3688
        g.TempInputId = g.ActiveId;
1193b4be Poul Sander 2025-02-03 21:28 3689
    }
1193b4be Poul Sander 2025-02-03 21:28 3690
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3691
}
1193b4be Poul Sander 2025-02-03 21:28 3692
1193b4be Poul Sander 2025-02-03 21:28 3693
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
1193b4be Poul Sander 2025-02-03 21:28 3694
// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
1193b4be Poul Sander 2025-02-03 21:28 3695
// However this may not be ideal for all uses, as some user code may break on out of bound values.
1193b4be Poul Sander 2025-02-03 21:28 3696
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)
1193b4be Poul Sander 2025-02-03 21:28 3697
{
1193b4be Poul Sander 2025-02-03 21:28 3698
    // FIXME: May need to clarify display behavior if format doesn't contain %.
1193b4be Poul Sander 2025-02-03 21:28 3699
    // "%d" -> "%d" / "There are %d items" -> "%d" / "items" -> "%d" (fallback). Also see #6405
1193b4be Poul Sander 2025-02-03 21:28 3700
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3701
    const ImGuiDataTypeInfo* type_info = DataTypeGetInfo(data_type);
1193b4be Poul Sander 2025-02-03 21:28 3702
    char fmt_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 3703
    char data_buf[32];
1193b4be Poul Sander 2025-02-03 21:28 3704
    format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
1193b4be Poul Sander 2025-02-03 21:28 3705
    if (format[0] == 0)
1193b4be Poul Sander 2025-02-03 21:28 3706
        format = type_info->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3707
    DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3708
    ImStrTrimBlanks(data_buf);
1193b4be Poul Sander 2025-02-03 21:28 3709
1193b4be Poul Sander 2025-02-03 21:28 3710
    ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 3711
    g.LastItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited; // Because TempInputText() uses ImGuiInputTextFlags_MergedItem it doesn't submit a new item, so we poke LastItemData.
1193b4be Poul Sander 2025-02-03 21:28 3712
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3713
    if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
1193b4be Poul Sander 2025-02-03 21:28 3714
    {
1193b4be Poul Sander 2025-02-03 21:28 3715
        // Backup old value
1193b4be Poul Sander 2025-02-03 21:28 3716
        size_t data_type_size = type_info->Size;
1193b4be Poul Sander 2025-02-03 21:28 3717
        ImGuiDataTypeStorage data_backup;
1193b4be Poul Sander 2025-02-03 21:28 3718
        memcpy(&data_backup, p_data, data_type_size);
1193b4be Poul Sander 2025-02-03 21:28 3719
1193b4be Poul Sander 2025-02-03 21:28 3720
        // Apply new value (or operations) then clamp
1193b4be Poul Sander 2025-02-03 21:28 3721
        DataTypeApplyFromText(data_buf, data_type, p_data, format, NULL);
1193b4be Poul Sander 2025-02-03 21:28 3722
        if (p_clamp_min || p_clamp_max)
1193b4be Poul Sander 2025-02-03 21:28 3723
        {
1193b4be Poul Sander 2025-02-03 21:28 3724
            if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
1193b4be Poul Sander 2025-02-03 21:28 3725
                ImSwap(p_clamp_min, p_clamp_max);
1193b4be Poul Sander 2025-02-03 21:28 3726
            DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
1193b4be Poul Sander 2025-02-03 21:28 3727
        }
1193b4be Poul Sander 2025-02-03 21:28 3728
1193b4be Poul Sander 2025-02-03 21:28 3729
        // Only mark as edited if new value is different
1193b4be Poul Sander 2025-02-03 21:28 3730
        g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3731
        value_changed = memcmp(&data_backup, p_data, data_type_size) != 0;
1193b4be Poul Sander 2025-02-03 21:28 3732
        if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3733
            MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 3734
    }
1193b4be Poul Sander 2025-02-03 21:28 3735
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3736
}
1193b4be Poul Sander 2025-02-03 21:28 3737
1193b4be Poul Sander 2025-02-03 21:28 3738
void ImGui::SetNextItemRefVal(ImGuiDataType data_type, void* p_data)
1193b4be Poul Sander 2025-02-03 21:28 3739
{
1193b4be Poul Sander 2025-02-03 21:28 3740
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3741
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasRefVal;
1193b4be Poul Sander 2025-02-03 21:28 3742
    memcpy(&g.NextItemData.RefVal, p_data, DataTypeGetInfo(data_type)->Size);
1193b4be Poul Sander 2025-02-03 21:28 3743
}
1193b4be Poul Sander 2025-02-03 21:28 3744
1193b4be Poul Sander 2025-02-03 21:28 3745
// 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.
1193b4be Poul Sander 2025-02-03 21:28 3746
// Read code of e.g. InputFloat(), InputInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.
1193b4be Poul Sander 2025-02-03 21:28 3747
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)
1193b4be Poul Sander 2025-02-03 21:28 3748
{
1193b4be Poul Sander 2025-02-03 21:28 3749
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3750
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3751
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3752
1193b4be Poul Sander 2025-02-03 21:28 3753
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3754
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 3755
    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()!
1193b4be Poul Sander 2025-02-03 21:28 3756
1193b4be Poul Sander 2025-02-03 21:28 3757
    if (format == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3758
        format = DataTypeGetInfo(data_type)->PrintFmt;
1193b4be Poul Sander 2025-02-03 21:28 3759
1193b4be Poul Sander 2025-02-03 21:28 3760
    void* p_data_default = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasRefVal) ? &g.NextItemData.RefVal : &g.DataTypeZeroValue;
1193b4be Poul Sander 2025-02-03 21:28 3761
1193b4be Poul Sander 2025-02-03 21:28 3762
    char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 3763
    if ((flags & ImGuiInputTextFlags_DisplayEmptyRefVal) && DataTypeCompare(data_type, p_data, p_data_default) == 0)
1193b4be Poul Sander 2025-02-03 21:28 3764
        buf[0] = 0;
1193b4be Poul Sander 2025-02-03 21:28 3765
    else
1193b4be Poul Sander 2025-02-03 21:28 3766
        DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
1193b4be Poul Sander 2025-02-03 21:28 3767
1193b4be Poul Sander 2025-02-03 21:28 3768
    // Disable the MarkItemEdited() call in InputText but keep ImGuiItemStatusFlags_Edited.
1193b4be Poul Sander 2025-02-03 21:28 3769
    // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
1193b4be Poul Sander 2025-02-03 21:28 3770
    g.NextItemData.ItemFlags |= ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3771
    flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 3772
1193b4be Poul Sander 2025-02-03 21:28 3773
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3774
    if (p_step == NULL)
1193b4be Poul Sander 2025-02-03 21:28 3775
    {
1193b4be Poul Sander 2025-02-03 21:28 3776
        if (InputText(label, buf, IM_ARRAYSIZE(buf), flags))
1193b4be Poul Sander 2025-02-03 21:28 3777
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
1193b4be Poul Sander 2025-02-03 21:28 3778
    }
1193b4be Poul Sander 2025-02-03 21:28 3779
    else
1193b4be Poul Sander 2025-02-03 21:28 3780
    {
1193b4be Poul Sander 2025-02-03 21:28 3781
        const float button_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 3782
1193b4be Poul Sander 2025-02-03 21:28 3783
        BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
1193b4be Poul Sander 2025-02-03 21:28 3784
        PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3785
        SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
1193b4be Poul Sander 2025-02-03 21:28 3786
        if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
1193b4be Poul Sander 2025-02-03 21:28 3787
            value_changed = DataTypeApplyFromText(buf, data_type, p_data, format, (flags & ImGuiInputTextFlags_ParseEmptyRefVal) ? p_data_default : NULL);
1193b4be Poul Sander 2025-02-03 21:28 3788
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
1193b4be Poul Sander 2025-02-03 21:28 3789
1193b4be Poul Sander 2025-02-03 21:28 3790
        // Step buttons
1193b4be Poul Sander 2025-02-03 21:28 3791
        const ImVec2 backup_frame_padding = style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 3792
        style.FramePadding.x = style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 3793
        if (flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 3794
            BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 3795
        PushItemFlag(ImGuiItemFlags_ButtonRepeat, true);
1193b4be Poul Sander 2025-02-03 21:28 3796
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3797
        if (ButtonEx("-", ImVec2(button_size, button_size)))
1193b4be Poul Sander 2025-02-03 21:28 3798
        {
1193b4be Poul Sander 2025-02-03 21:28 3799
            DataTypeApplyOp(data_type, '-', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
1193b4be Poul Sander 2025-02-03 21:28 3800
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3801
        }
1193b4be Poul Sander 2025-02-03 21:28 3802
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3803
        if (ButtonEx("+", ImVec2(button_size, button_size)))
1193b4be Poul Sander 2025-02-03 21:28 3804
        {
1193b4be Poul Sander 2025-02-03 21:28 3805
            DataTypeApplyOp(data_type, '+', p_data, p_data, g.IO.KeyCtrl && p_step_fast ? p_step_fast : p_step);
1193b4be Poul Sander 2025-02-03 21:28 3806
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 3807
        }
1193b4be Poul Sander 2025-02-03 21:28 3808
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 3809
        if (flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 3810
            EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 3811
1193b4be Poul Sander 2025-02-03 21:28 3812
        const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3813
        if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3814
        {
1193b4be Poul Sander 2025-02-03 21:28 3815
            SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3816
            TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3817
        }
1193b4be Poul Sander 2025-02-03 21:28 3818
        style.FramePadding = backup_frame_padding;
1193b4be Poul Sander 2025-02-03 21:28 3819
1193b4be Poul Sander 2025-02-03 21:28 3820
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3821
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3822
    }
1193b4be Poul Sander 2025-02-03 21:28 3823
1193b4be Poul Sander 2025-02-03 21:28 3824
    g.LastItemData.ItemFlags &= ~ImGuiItemFlags_NoMarkEdited;
1193b4be Poul Sander 2025-02-03 21:28 3825
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 3826
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 3827
1193b4be Poul Sander 2025-02-03 21:28 3828
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3829
}
1193b4be Poul Sander 2025-02-03 21:28 3830
1193b4be Poul Sander 2025-02-03 21:28 3831
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)
1193b4be Poul Sander 2025-02-03 21:28 3832
{
1193b4be Poul Sander 2025-02-03 21:28 3833
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 3834
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 3835
        return false;
1193b4be Poul Sander 2025-02-03 21:28 3836
1193b4be Poul Sander 2025-02-03 21:28 3837
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 3838
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 3839
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 3840
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 3841
    PushMultiItemsWidths(components, CalcItemWidth());
1193b4be Poul Sander 2025-02-03 21:28 3842
    size_t type_size = GDataTypeInfo[data_type].Size;
1193b4be Poul Sander 2025-02-03 21:28 3843
    for (int i = 0; i < components; i++)
1193b4be Poul Sander 2025-02-03 21:28 3844
    {
1193b4be Poul Sander 2025-02-03 21:28 3845
        PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 3846
        if (i > 0)
1193b4be Poul Sander 2025-02-03 21:28 3847
            SameLine(0, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3848
        value_changed |= InputScalar("", data_type, p_data, p_step, p_step_fast, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3849
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 3850
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 3851
        p_data = (void*)((char*)p_data + type_size);
1193b4be Poul Sander 2025-02-03 21:28 3852
    }
1193b4be Poul Sander 2025-02-03 21:28 3853
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 3854
1193b4be Poul Sander 2025-02-03 21:28 3855
    const char* label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 3856
    if (label != label_end)
1193b4be Poul Sander 2025-02-03 21:28 3857
    {
1193b4be Poul Sander 2025-02-03 21:28 3858
        SameLine(0.0f, g.Style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 3859
        TextEx(label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 3860
    }
1193b4be Poul Sander 2025-02-03 21:28 3861
1193b4be Poul Sander 2025-02-03 21:28 3862
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 3863
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 3864
}
1193b4be Poul Sander 2025-02-03 21:28 3865
1193b4be Poul Sander 2025-02-03 21:28 3866
bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3867
{
1193b4be Poul Sander 2025-02-03 21:28 3868
    return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3869
}
1193b4be Poul Sander 2025-02-03 21:28 3870
1193b4be Poul Sander 2025-02-03 21:28 3871
bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3872
{
1193b4be Poul Sander 2025-02-03 21:28 3873
    return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3874
}
1193b4be Poul Sander 2025-02-03 21:28 3875
1193b4be Poul Sander 2025-02-03 21:28 3876
bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3877
{
1193b4be Poul Sander 2025-02-03 21:28 3878
    return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3879
}
1193b4be Poul Sander 2025-02-03 21:28 3880
1193b4be Poul Sander 2025-02-03 21:28 3881
bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3882
{
1193b4be Poul Sander 2025-02-03 21:28 3883
    return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3884
}
1193b4be Poul Sander 2025-02-03 21:28 3885
1193b4be Poul Sander 2025-02-03 21:28 3886
bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3887
{
1193b4be Poul Sander 2025-02-03 21:28 3888
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 3889
    const char* format = (flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
1193b4be Poul Sander 2025-02-03 21:28 3890
    return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step > 0 ? &step : NULL), (void*)(step_fast > 0 ? &step_fast : NULL), format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3891
}
1193b4be Poul Sander 2025-02-03 21:28 3892
1193b4be Poul Sander 2025-02-03 21:28 3893
bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3894
{
1193b4be Poul Sander 2025-02-03 21:28 3895
    return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3896
}
1193b4be Poul Sander 2025-02-03 21:28 3897
1193b4be Poul Sander 2025-02-03 21:28 3898
bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3899
{
1193b4be Poul Sander 2025-02-03 21:28 3900
    return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3901
}
1193b4be Poul Sander 2025-02-03 21:28 3902
1193b4be Poul Sander 2025-02-03 21:28 3903
bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3904
{
1193b4be Poul Sander 2025-02-03 21:28 3905
    return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", flags);
1193b4be Poul Sander 2025-02-03 21:28 3906
}
1193b4be Poul Sander 2025-02-03 21:28 3907
1193b4be Poul Sander 2025-02-03 21:28 3908
bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 3909
{
1193b4be Poul Sander 2025-02-03 21:28 3910
    return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step > 0.0 ? &step : NULL), (void*)(step_fast > 0.0 ? &step_fast : NULL), format, flags);
1193b4be Poul Sander 2025-02-03 21:28 3911
}
1193b4be Poul Sander 2025-02-03 21:28 3912
1193b4be Poul Sander 2025-02-03 21:28 3913
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3914
// [SECTION] Widgets: InputText, InputTextMultiline, InputTextWithHint
1193b4be Poul Sander 2025-02-03 21:28 3915
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3916
// - imstb_textedit.h include
1193b4be Poul Sander 2025-02-03 21:28 3917
// - InputText()
1193b4be Poul Sander 2025-02-03 21:28 3918
// - InputTextWithHint()
1193b4be Poul Sander 2025-02-03 21:28 3919
// - InputTextMultiline()
1193b4be Poul Sander 2025-02-03 21:28 3920
// - InputTextEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3921
// - DebugNodeInputTextState() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 3922
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 3923
1193b4be Poul Sander 2025-02-03 21:28 3924
namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 3925
{
1193b4be Poul Sander 2025-02-03 21:28 3926
#include "imstb_textedit.h"
1193b4be Poul Sander 2025-02-03 21:28 3927
}
1193b4be Poul Sander 2025-02-03 21:28 3928
1193b4be Poul Sander 2025-02-03 21:28 3929
bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
1193b4be Poul Sander 2025-02-03 21:28 3930
{
1193b4be Poul Sander 2025-02-03 21:28 3931
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
1193b4be Poul Sander 2025-02-03 21:28 3932
    return InputTextEx(label, NULL, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3933
}
1193b4be Poul Sander 2025-02-03 21:28 3934
1193b4be Poul Sander 2025-02-03 21:28 3935
bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
1193b4be Poul Sander 2025-02-03 21:28 3936
{
1193b4be Poul Sander 2025-02-03 21:28 3937
    return InputTextEx(label, NULL, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3938
}
1193b4be Poul Sander 2025-02-03 21:28 3939
1193b4be Poul Sander 2025-02-03 21:28 3940
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
1193b4be Poul Sander 2025-02-03 21:28 3941
{
1193b4be Poul Sander 2025-02-03 21:28 3942
    IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or  InputTextEx() manually if you need multi-line + hint.
1193b4be Poul Sander 2025-02-03 21:28 3943
    return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
1193b4be Poul Sander 2025-02-03 21:28 3944
}
1193b4be Poul Sander 2025-02-03 21:28 3945
4e9e2555 Poul Sander 2025-10-05 10:15 3946
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)
1193b4be Poul Sander 2025-02-03 21:28 3947
{
1193b4be Poul Sander 2025-02-03 21:28 3948
    ImGuiContext& g = *ctx;
4e9e2555 Poul Sander 2025-10-05 10:15 3949
    ImGuiInputTextState* obj = &g.InputTextState;
4e9e2555 Poul Sander 2025-10-05 10:15 3950
    return ImFontCalcTextSizeEx(g.Font, g.FontSize, FLT_MAX, obj->WrapWidth, text_begin, text_end_display, text_end, out_remaining, out_offset, flags);
1193b4be Poul Sander 2025-02-03 21:28 3951
}
1193b4be Poul Sander 2025-02-03 21:28 3952
1193b4be Poul Sander 2025-02-03 21:28 3953
// 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)
1193b4be Poul Sander 2025-02-03 21:28 3954
// With our UTF-8 use of stb_textedit:
1193b4be Poul Sander 2025-02-03 21:28 3955
// - 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.
1193b4be Poul Sander 2025-02-03 21:28 3956
// - 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).
1193b4be Poul Sander 2025-02-03 21:28 3957
// - ...but we don't use that feature.
1193b4be Poul Sander 2025-02-03 21:28 3958
namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 3959
{
4e9e2555 Poul Sander 2025-10-05 10:15 3960
static int     STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj)                             { return obj->TextLen; }
4e9e2555 Poul Sander 2025-10-05 10:15 3961
static char    STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx)                      { IM_ASSERT(idx >= 0 && idx <= obj->TextLen); return obj->TextSrc[idx]; }
4e9e2555 Poul Sander 2025-10-05 10:15 3962
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; }
1193b4be Poul Sander 2025-02-03 21:28 3963
static char    STB_TEXTEDIT_NEWLINE = '\n';
1193b4be Poul Sander 2025-02-03 21:28 3964
static void    STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
1193b4be Poul Sander 2025-02-03 21:28 3965
{
4e9e2555 Poul Sander 2025-10-05 10:15 3966
    const char* text = obj->TextSrc;
1193b4be Poul Sander 2025-02-03 21:28 3967
    const char* text_remaining = NULL;
4e9e2555 Poul Sander 2025-10-05 10:15 3968
    const ImVec2 size = InputTextCalcTextSize(obj->Ctx, text + line_start_idx, text + obj->TextLen, text + obj->TextLen, &text_remaining, NULL, ImDrawTextFlags_StopOnNewLine | ImDrawTextFlags_WrapKeepBlanks);
1193b4be Poul Sander 2025-02-03 21:28 3969
    r->x0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3970
    r->x1 = size.x;
1193b4be Poul Sander 2025-02-03 21:28 3971
    r->baseline_y_delta = size.y;
1193b4be Poul Sander 2025-02-03 21:28 3972
    r->ymin = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 3973
    r->ymax = size.y;
1193b4be Poul Sander 2025-02-03 21:28 3974
    r->num_chars = (int)(text_remaining - (text + line_start_idx));
1193b4be Poul Sander 2025-02-03 21:28 3975
}
1193b4be Poul Sander 2025-02-03 21:28 3976
1193b4be Poul Sander 2025-02-03 21:28 3977
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX  IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL
1193b4be Poul Sander 2025-02-03 21:28 3978
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX  IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL
1193b4be Poul Sander 2025-02-03 21:28 3979
1193b4be Poul Sander 2025-02-03 21:28 3980
static int IMSTB_TEXTEDIT_GETNEXTCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 3981
{
4e9e2555 Poul Sander 2025-10-05 10:15 3982
    if (idx >= obj->TextLen)
4e9e2555 Poul Sander 2025-10-05 10:15 3983
        return obj->TextLen + 1;
1193b4be Poul Sander 2025-02-03 21:28 3984
    unsigned int c;
4e9e2555 Poul Sander 2025-10-05 10:15 3985
    return idx + ImTextCharFromUtf8(&c, obj->TextSrc + idx, obj->TextSrc + obj->TextLen);
1193b4be Poul Sander 2025-02-03 21:28 3986
}
1193b4be Poul Sander 2025-02-03 21:28 3987
1193b4be Poul Sander 2025-02-03 21:28 3988
static int IMSTB_TEXTEDIT_GETPREVCHARINDEX_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 3989
{
1193b4be Poul Sander 2025-02-03 21:28 3990
    if (idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 3991
        return -1;
4e9e2555 Poul Sander 2025-10-05 10:15 3992
    const char* p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, obj->TextSrc + idx);
4e9e2555 Poul Sander 2025-10-05 10:15 3993
    return (int)(p - obj->TextSrc);
1193b4be Poul Sander 2025-02-03 21:28 3994
}
1193b4be Poul Sander 2025-02-03 21:28 3995
1193b4be Poul Sander 2025-02-03 21:28 3996
static bool ImCharIsSeparatorW(unsigned int c)
1193b4be Poul Sander 2025-02-03 21:28 3997
{
1193b4be Poul Sander 2025-02-03 21:28 3998
    static const unsigned int separator_list[] =
1193b4be Poul Sander 2025-02-03 21:28 3999
    {
1193b4be Poul Sander 2025-02-03 21:28 4000
        ',', 0x3001, '.', 0x3002, ';', 0xFF1B, '(', 0xFF08, ')', 0xFF09, '{', 0xFF5B, '}', 0xFF5D,
1193b4be Poul Sander 2025-02-03 21:28 4001
        '[', 0x300C, ']', 0x300D, '|', 0xFF5C, '!', 0xFF01, '\\', 0xFFE5, '/', 0x30FB, 0xFF0F,
1193b4be Poul Sander 2025-02-03 21:28 4002
        '\n', '\r',
1193b4be Poul Sander 2025-02-03 21:28 4003
    };
1193b4be Poul Sander 2025-02-03 21:28 4004
    for (unsigned int separator : separator_list)
1193b4be Poul Sander 2025-02-03 21:28 4005
        if (c == separator)
1193b4be Poul Sander 2025-02-03 21:28 4006
            return true;
1193b4be Poul Sander 2025-02-03 21:28 4007
    return false;
1193b4be Poul Sander 2025-02-03 21:28 4008
}
1193b4be Poul Sander 2025-02-03 21:28 4009
1193b4be Poul Sander 2025-02-03 21:28 4010
static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4011
{
1193b4be Poul Sander 2025-02-03 21:28 4012
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 4013
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 4014
        return 0;
1193b4be Poul Sander 2025-02-03 21:28 4015
4e9e2555 Poul Sander 2025-10-05 10:15 4016
    const char* curr_p = obj->TextSrc + idx;
4e9e2555 Poul Sander 2025-10-05 10:15 4017
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
4e9e2555 Poul Sander 2025-10-05 10:15 4018
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, curr_p, obj->TextSrc + obj->TextLen);
4e9e2555 Poul Sander 2025-10-05 10:15 4019
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, prev_p, obj->TextSrc + obj->TextLen);
1193b4be Poul Sander 2025-02-03 21:28 4020
1193b4be Poul Sander 2025-02-03 21:28 4021
    bool prev_white = ImCharIsBlankW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4022
    bool prev_separ = ImCharIsSeparatorW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4023
    bool curr_white = ImCharIsBlankW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4024
    bool curr_separ = ImCharIsSeparatorW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4025
    return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
1193b4be Poul Sander 2025-02-03 21:28 4026
}
1193b4be Poul Sander 2025-02-03 21:28 4027
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4028
{
1193b4be Poul Sander 2025-02-03 21:28 4029
    if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0)
1193b4be Poul Sander 2025-02-03 21:28 4030
        return 0;
1193b4be Poul Sander 2025-02-03 21:28 4031
4e9e2555 Poul Sander 2025-10-05 10:15 4032
    const char* curr_p = obj->TextSrc + idx;
4e9e2555 Poul Sander 2025-10-05 10:15 4033
    const char* prev_p = ImTextFindPreviousUtf8Codepoint(obj->TextSrc, curr_p);
4e9e2555 Poul Sander 2025-10-05 10:15 4034
    unsigned int prev_c; ImTextCharFromUtf8(&prev_c, curr_p, obj->TextSrc + obj->TextLen);
4e9e2555 Poul Sander 2025-10-05 10:15 4035
    unsigned int curr_c; ImTextCharFromUtf8(&curr_c, prev_p, obj->TextSrc + obj->TextLen);
1193b4be Poul Sander 2025-02-03 21:28 4036
1193b4be Poul Sander 2025-02-03 21:28 4037
    bool prev_white = ImCharIsBlankW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4038
    bool prev_separ = ImCharIsSeparatorW(prev_c);
1193b4be Poul Sander 2025-02-03 21:28 4039
    bool curr_white = ImCharIsBlankW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4040
    bool curr_separ = ImCharIsSeparatorW(curr_c);
1193b4be Poul Sander 2025-02-03 21:28 4041
    return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ);
1193b4be Poul Sander 2025-02-03 21:28 4042
}
1193b4be Poul Sander 2025-02-03 21:28 4043
static int  STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4044
{
1193b4be Poul Sander 2025-02-03 21:28 4045
    idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4046
    while (idx >= 0 && !is_word_boundary_from_right(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4047
        idx = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4048
    return idx < 0 ? 0 : idx;
1193b4be Poul Sander 2025-02-03 21:28 4049
}
1193b4be Poul Sander 2025-02-03 21:28 4050
static int  STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4051
{
4e9e2555 Poul Sander 2025-10-05 10:15 4052
    int len = obj->TextLen;
1193b4be Poul Sander 2025-02-03 21:28 4053
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4054
    while (idx < len && !is_word_boundary_from_left(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4055
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4056
    return idx > len ? len : idx;
1193b4be Poul Sander 2025-02-03 21:28 4057
}
1193b4be Poul Sander 2025-02-03 21:28 4058
static int  STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx)
1193b4be Poul Sander 2025-02-03 21:28 4059
{
1193b4be Poul Sander 2025-02-03 21:28 4060
    idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
4e9e2555 Poul Sander 2025-10-05 10:15 4061
    int len = obj->TextLen;
1193b4be Poul Sander 2025-02-03 21:28 4062
    while (idx < len && !is_word_boundary_from_right(obj, idx))
1193b4be Poul Sander 2025-02-03 21:28 4063
        idx = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx);
1193b4be Poul Sander 2025-02-03 21:28 4064
    return idx > len ? len : idx;
1193b4be Poul Sander 2025-02-03 21:28 4065
}
1193b4be Poul Sander 2025-02-03 21:28 4066
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); }
1193b4be Poul Sander 2025-02-03 21:28 4067
#define STB_TEXTEDIT_MOVEWORDLEFT       STB_TEXTEDIT_MOVEWORDLEFT_IMPL  // They need to be #define for stb_textedit.h
1193b4be Poul Sander 2025-02-03 21:28 4068
#define STB_TEXTEDIT_MOVEWORDRIGHT      STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
1193b4be Poul Sander 2025-02-03 21:28 4069
4e9e2555 Poul Sander 2025-10-05 10:15 4070
// Reimplementation of stb_textedit_move_line_start()/stb_textedit_move_line_end() which supports word-wrapping.
4e9e2555 Poul Sander 2025-10-05 10:15 4071
static int STB_TEXTEDIT_MOVELINESTART_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
4e9e2555 Poul Sander 2025-10-05 10:15 4072
{
4e9e2555 Poul Sander 2025-10-05 10:15 4073
    if (state->single_line)
4e9e2555 Poul Sander 2025-10-05 10:15 4074
        return 0;
4e9e2555 Poul Sander 2025-10-05 10:15 4075
4e9e2555 Poul Sander 2025-10-05 10:15 4076
    if (obj->WrapWidth > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 4077
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4078
        ImGuiContext& g = *obj->Ctx;
4e9e2555 Poul Sander 2025-10-05 10:15 4079
        const char* p_cursor = obj->TextSrc + cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4080
        const char* p_bol = ImStrbol(p_cursor, obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4081
        const char* p = p_bol;
4e9e2555 Poul Sander 2025-10-05 10:15 4082
        const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
4e9e2555 Poul Sander 2025-10-05 10:15 4083
        while (p >= p_bol)
4e9e2555 Poul Sander 2025-10-05 10:15 4084
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4085
            const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
4e9e2555 Poul Sander 2025-10-05 10:15 4086
            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)
4e9e2555 Poul Sander 2025-10-05 10:15 4087
                return (int)(p_bol - obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4088
            if (p_eol == p_cursor && obj->TextA[cursor] != '\n' && obj->LastMoveDirectionLR == ImGuiDir_Left)
4e9e2555 Poul Sander 2025-10-05 10:15 4089
                return (int)(p_bol - obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4090
            if (p_eol >= p_cursor)
4e9e2555 Poul Sander 2025-10-05 10:15 4091
                return (int)(p - obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4092
            p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
4e9e2555 Poul Sander 2025-10-05 10:15 4093
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4094
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4095
4e9e2555 Poul Sander 2025-10-05 10:15 4096
    // Regular handler, same as stb_textedit_move_line_start()
4e9e2555 Poul Sander 2025-10-05 10:15 4097
    while (cursor > 0)
4e9e2555 Poul Sander 2025-10-05 10:15 4098
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4099
        int prev_cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, cursor);
4e9e2555 Poul Sander 2025-10-05 10:15 4100
        if (STB_TEXTEDIT_GETCHAR(obj, prev_cursor) == STB_TEXTEDIT_NEWLINE)
4e9e2555 Poul Sander 2025-10-05 10:15 4101
            break;
4e9e2555 Poul Sander 2025-10-05 10:15 4102
        cursor = prev_cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4103
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4104
    return cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4105
}
4e9e2555 Poul Sander 2025-10-05 10:15 4106
4e9e2555 Poul Sander 2025-10-05 10:15 4107
static int STB_TEXTEDIT_MOVELINEEND_IMPL(ImGuiInputTextState* obj, ImStb::STB_TexteditState* state, int cursor)
4e9e2555 Poul Sander 2025-10-05 10:15 4108
{
4e9e2555 Poul Sander 2025-10-05 10:15 4109
    int n = STB_TEXTEDIT_STRINGLEN(obj);
4e9e2555 Poul Sander 2025-10-05 10:15 4110
    if (state->single_line)
4e9e2555 Poul Sander 2025-10-05 10:15 4111
        return n;
4e9e2555 Poul Sander 2025-10-05 10:15 4112
4e9e2555 Poul Sander 2025-10-05 10:15 4113
    if (obj->WrapWidth > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 4114
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4115
        ImGuiContext& g = *obj->Ctx;
4e9e2555 Poul Sander 2025-10-05 10:15 4116
        const char* p_cursor = obj->TextSrc + cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4117
        const char* p = ImStrbol(p_cursor, obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4118
        const char* text_end = obj->TextSrc + obj->TextLen; // End of line would be enough
4e9e2555 Poul Sander 2025-10-05 10:15 4119
        while (p < text_end)
4e9e2555 Poul Sander 2025-10-05 10:15 4120
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4121
            const char* p_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, obj->WrapWidth, ImDrawTextFlags_WrapKeepBlanks);
4e9e2555 Poul Sander 2025-10-05 10:15 4122
            cursor = (int)(p_eol - obj->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 4123
            if (p_eol == p_cursor && obj->LastMoveDirectionLR != ImGuiDir_Left) // If we are already on a visible end-of-line, switch to regular handle
4e9e2555 Poul Sander 2025-10-05 10:15 4124
                break;
4e9e2555 Poul Sander 2025-10-05 10:15 4125
            if (p_eol > p_cursor)
4e9e2555 Poul Sander 2025-10-05 10:15 4126
                return cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4127
            p = (*p_eol == '\n') ? p_eol + 1 : p_eol;
4e9e2555 Poul Sander 2025-10-05 10:15 4128
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4129
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4130
    // Regular handler, same as stb_textedit_move_line_end()
4e9e2555 Poul Sander 2025-10-05 10:15 4131
    while (cursor < n && STB_TEXTEDIT_GETCHAR(obj, cursor) != STB_TEXTEDIT_NEWLINE)
4e9e2555 Poul Sander 2025-10-05 10:15 4132
        cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, cursor);
4e9e2555 Poul Sander 2025-10-05 10:15 4133
    return cursor;
4e9e2555 Poul Sander 2025-10-05 10:15 4134
}
4e9e2555 Poul Sander 2025-10-05 10:15 4135
4e9e2555 Poul Sander 2025-10-05 10:15 4136
#define STB_TEXTEDIT_MOVELINESTART      STB_TEXTEDIT_MOVELINESTART_IMPL
4e9e2555 Poul Sander 2025-10-05 10:15 4137
#define STB_TEXTEDIT_MOVELINEEND        STB_TEXTEDIT_MOVELINEEND_IMPL
4e9e2555 Poul Sander 2025-10-05 10:15 4138
1193b4be Poul Sander 2025-02-03 21:28 4139
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
1193b4be Poul Sander 2025-02-03 21:28 4140
{
4e9e2555 Poul Sander 2025-10-05 10:15 4141
    // Offset remaining text (+ copy zero terminator)
4e9e2555 Poul Sander 2025-10-05 10:15 4142
    IM_ASSERT(obj->TextSrc == obj->TextA.Data);
1193b4be Poul Sander 2025-02-03 21:28 4143
    char* dst = obj->TextA.Data + pos;
4e9e2555 Poul Sander 2025-10-05 10:15 4144
    char* src = obj->TextA.Data + pos + n;
4e9e2555 Poul Sander 2025-10-05 10:15 4145
    memmove(dst, src, obj->TextLen - n - pos + 1);
1193b4be Poul Sander 2025-02-03 21:28 4146
    obj->Edited = true;
4e9e2555 Poul Sander 2025-10-05 10:15 4147
    obj->TextLen -= n;
1193b4be Poul Sander 2025-02-03 21:28 4148
}
1193b4be Poul Sander 2025-02-03 21:28 4149
1193b4be Poul Sander 2025-02-03 21:28 4150
static bool STB_TEXTEDIT_INSERTCHARS(ImGuiInputTextState* obj, int pos, const char* new_text, int new_text_len)
1193b4be Poul Sander 2025-02-03 21:28 4151
{
1193b4be Poul Sander 2025-02-03 21:28 4152
    const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4e9e2555 Poul Sander 2025-10-05 10:15 4153
    const int text_len = obj->TextLen;
1193b4be Poul Sander 2025-02-03 21:28 4154
    IM_ASSERT(pos <= text_len);
1193b4be Poul Sander 2025-02-03 21:28 4155
4e9e2555 Poul Sander 2025-10-05 10:15 4156
    if (!is_resizable && (new_text_len + obj->TextLen + 1 > obj->BufCapacity))
1193b4be Poul Sander 2025-02-03 21:28 4157
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4158
1193b4be Poul Sander 2025-02-03 21:28 4159
    // Grow internal buffer if needed
4e9e2555 Poul Sander 2025-10-05 10:15 4160
    IM_ASSERT(obj->TextSrc == obj->TextA.Data);
1193b4be Poul Sander 2025-02-03 21:28 4161
    if (new_text_len + text_len + 1 > obj->TextA.Size)
1193b4be Poul Sander 2025-02-03 21:28 4162
    {
1193b4be Poul Sander 2025-02-03 21:28 4163
        if (!is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4164
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4165
        obj->TextA.resize(text_len + ImClamp(new_text_len, 32, ImMax(256, new_text_len)) + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4166
        obj->TextSrc = obj->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4167
    }
1193b4be Poul Sander 2025-02-03 21:28 4168
1193b4be Poul Sander 2025-02-03 21:28 4169
    char* text = obj->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4170
    if (pos != text_len)
1193b4be Poul Sander 2025-02-03 21:28 4171
        memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos));
1193b4be Poul Sander 2025-02-03 21:28 4172
    memcpy(text + pos, new_text, (size_t)new_text_len);
1193b4be Poul Sander 2025-02-03 21:28 4173
1193b4be Poul Sander 2025-02-03 21:28 4174
    obj->Edited = true;
4e9e2555 Poul Sander 2025-10-05 10:15 4175
    obj->TextLen += new_text_len;
4e9e2555 Poul Sander 2025-10-05 10:15 4176
    obj->TextA[obj->TextLen] = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4177
1193b4be Poul Sander 2025-02-03 21:28 4178
    return true;
1193b4be Poul Sander 2025-02-03 21:28 4179
}
1193b4be Poul Sander 2025-02-03 21:28 4180
1193b4be Poul Sander 2025-02-03 21:28 4181
// 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)
1193b4be Poul Sander 2025-02-03 21:28 4182
#define STB_TEXTEDIT_K_LEFT         0x200000 // keyboard input to move cursor left
1193b4be Poul Sander 2025-02-03 21:28 4183
#define STB_TEXTEDIT_K_RIGHT        0x200001 // keyboard input to move cursor right
1193b4be Poul Sander 2025-02-03 21:28 4184
#define STB_TEXTEDIT_K_UP           0x200002 // keyboard input to move cursor up
1193b4be Poul Sander 2025-02-03 21:28 4185
#define STB_TEXTEDIT_K_DOWN         0x200003 // keyboard input to move cursor down
1193b4be Poul Sander 2025-02-03 21:28 4186
#define STB_TEXTEDIT_K_LINESTART    0x200004 // keyboard input to move cursor to start of line
1193b4be Poul Sander 2025-02-03 21:28 4187
#define STB_TEXTEDIT_K_LINEEND      0x200005 // keyboard input to move cursor to end of line
1193b4be Poul Sander 2025-02-03 21:28 4188
#define STB_TEXTEDIT_K_TEXTSTART    0x200006 // keyboard input to move cursor to start of text
1193b4be Poul Sander 2025-02-03 21:28 4189
#define STB_TEXTEDIT_K_TEXTEND      0x200007 // keyboard input to move cursor to end of text
1193b4be Poul Sander 2025-02-03 21:28 4190
#define STB_TEXTEDIT_K_DELETE       0x200008 // keyboard input to delete selection or character under cursor
1193b4be Poul Sander 2025-02-03 21:28 4191
#define STB_TEXTEDIT_K_BACKSPACE    0x200009 // keyboard input to delete selection or character left of cursor
1193b4be Poul Sander 2025-02-03 21:28 4192
#define STB_TEXTEDIT_K_UNDO         0x20000A // keyboard input to perform undo
1193b4be Poul Sander 2025-02-03 21:28 4193
#define STB_TEXTEDIT_K_REDO         0x20000B // keyboard input to perform redo
1193b4be Poul Sander 2025-02-03 21:28 4194
#define STB_TEXTEDIT_K_WORDLEFT     0x20000C // keyboard input to move cursor left one word
1193b4be Poul Sander 2025-02-03 21:28 4195
#define STB_TEXTEDIT_K_WORDRIGHT    0x20000D // keyboard input to move cursor right one word
1193b4be Poul Sander 2025-02-03 21:28 4196
#define STB_TEXTEDIT_K_PGUP         0x20000E // keyboard input to move cursor up a page
1193b4be Poul Sander 2025-02-03 21:28 4197
#define STB_TEXTEDIT_K_PGDOWN       0x20000F // keyboard input to move cursor down a page
1193b4be Poul Sander 2025-02-03 21:28 4198
#define STB_TEXTEDIT_K_SHIFT        0x400000
1193b4be Poul Sander 2025-02-03 21:28 4199
1193b4be Poul Sander 2025-02-03 21:28 4200
#define IMSTB_TEXTEDIT_IMPLEMENTATION
1193b4be Poul Sander 2025-02-03 21:28 4201
#define IMSTB_TEXTEDIT_memmove memmove
1193b4be Poul Sander 2025-02-03 21:28 4202
#include "imstb_textedit.h"
1193b4be Poul Sander 2025-02-03 21:28 4203
1193b4be Poul Sander 2025-02-03 21:28 4204
// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling
1193b4be Poul Sander 2025-02-03 21:28 4205
// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?)
1193b4be Poul Sander 2025-02-03 21:28 4206
static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
1193b4be Poul Sander 2025-02-03 21:28 4207
{
4e9e2555 Poul Sander 2025-10-05 10:15 4208
    stb_text_makeundo_replace(str, state, 0, str->TextLen, text_len);
4e9e2555 Poul Sander 2025-10-05 10:15 4209
    ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->TextLen);
1193b4be Poul Sander 2025-02-03 21:28 4210
    state->cursor = state->select_start = state->select_end = 0;
1193b4be Poul Sander 2025-02-03 21:28 4211
    if (text_len <= 0)
1193b4be Poul Sander 2025-02-03 21:28 4212
        return;
1193b4be Poul Sander 2025-02-03 21:28 4213
    if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
1193b4be Poul Sander 2025-02-03 21:28 4214
    {
1193b4be Poul Sander 2025-02-03 21:28 4215
        state->cursor = state->select_start = state->select_end = text_len;
1193b4be Poul Sander 2025-02-03 21:28 4216
        state->has_preferred_x = 0;
1193b4be Poul Sander 2025-02-03 21:28 4217
        return;
1193b4be Poul Sander 2025-02-03 21:28 4218
    }
1193b4be Poul Sander 2025-02-03 21:28 4219
    IM_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace()
1193b4be Poul Sander 2025-02-03 21:28 4220
}
1193b4be Poul Sander 2025-02-03 21:28 4221
1193b4be Poul Sander 2025-02-03 21:28 4222
} // namespace ImStb
1193b4be Poul Sander 2025-02-03 21:28 4223
1193b4be Poul Sander 2025-02-03 21:28 4224
// We added an extra indirection where 'Stb' is heap-allocated, in order facilitate the work of bindings generators.
1193b4be Poul Sander 2025-02-03 21:28 4225
ImGuiInputTextState::ImGuiInputTextState()
1193b4be Poul Sander 2025-02-03 21:28 4226
{
1193b4be Poul Sander 2025-02-03 21:28 4227
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 4228
    Stb = IM_NEW(ImStbTexteditState);
1193b4be Poul Sander 2025-02-03 21:28 4229
    memset(Stb, 0, sizeof(*Stb));
1193b4be Poul Sander 2025-02-03 21:28 4230
}
1193b4be Poul Sander 2025-02-03 21:28 4231
1193b4be Poul Sander 2025-02-03 21:28 4232
ImGuiInputTextState::~ImGuiInputTextState()
1193b4be Poul Sander 2025-02-03 21:28 4233
{
1193b4be Poul Sander 2025-02-03 21:28 4234
    IM_DELETE(Stb);
1193b4be Poul Sander 2025-02-03 21:28 4235
}
1193b4be Poul Sander 2025-02-03 21:28 4236
1193b4be Poul Sander 2025-02-03 21:28 4237
void ImGuiInputTextState::OnKeyPressed(int key)
1193b4be Poul Sander 2025-02-03 21:28 4238
{
1193b4be Poul Sander 2025-02-03 21:28 4239
    stb_textedit_key(this, Stb, key);
1193b4be Poul Sander 2025-02-03 21:28 4240
    CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4241
    CursorAnimReset();
4e9e2555 Poul Sander 2025-10-05 10:15 4242
    const int key_u = (key & ~STB_TEXTEDIT_K_SHIFT);
4e9e2555 Poul Sander 2025-10-05 10:15 4243
    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)
4e9e2555 Poul Sander 2025-10-05 10:15 4244
        LastMoveDirectionLR = ImGuiDir_Left;
4e9e2555 Poul Sander 2025-10-05 10:15 4245
    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)
4e9e2555 Poul Sander 2025-10-05 10:15 4246
        LastMoveDirectionLR = ImGuiDir_Right;
1193b4be Poul Sander 2025-02-03 21:28 4247
}
1193b4be Poul Sander 2025-02-03 21:28 4248
1193b4be Poul Sander 2025-02-03 21:28 4249
void ImGuiInputTextState::OnCharPressed(unsigned int c)
1193b4be Poul Sander 2025-02-03 21:28 4250
{
1193b4be Poul Sander 2025-02-03 21:28 4251
    // Convert the key to a UTF8 byte sequence.
1193b4be Poul Sander 2025-02-03 21:28 4252
    // The changes we had to make to stb_textedit_key made it very much UTF-8 specific which is not too great.
1193b4be Poul Sander 2025-02-03 21:28 4253
    char utf8[5];
1193b4be Poul Sander 2025-02-03 21:28 4254
    ImTextCharToUtf8(utf8, c);
4e9e2555 Poul Sander 2025-10-05 10:15 4255
    stb_textedit_text(this, Stb, utf8, (int)ImStrlen(utf8));
1193b4be Poul Sander 2025-02-03 21:28 4256
    CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4257
    CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4258
}
1193b4be Poul Sander 2025-02-03 21:28 4259
1193b4be Poul Sander 2025-02-03 21:28 4260
// Those functions are not inlined in imgui_internal.h, allowing us to hide ImStbTexteditState from that header.
1193b4be Poul Sander 2025-02-03 21:28 4261
void ImGuiInputTextState::CursorAnimReset()                 { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking
4e9e2555 Poul Sander 2025-10-05 10:15 4262
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); }
1193b4be Poul Sander 2025-02-03 21:28 4263
bool ImGuiInputTextState::HasSelection() const              { return Stb->select_start != Stb->select_end; }
1193b4be Poul Sander 2025-02-03 21:28 4264
void ImGuiInputTextState::ClearSelection()                  { Stb->select_start = Stb->select_end = Stb->cursor; }
1193b4be Poul Sander 2025-02-03 21:28 4265
int  ImGuiInputTextState::GetCursorPos() const              { return Stb->cursor; }
1193b4be Poul Sander 2025-02-03 21:28 4266
int  ImGuiInputTextState::GetSelectionStart() const         { return Stb->select_start; }
1193b4be Poul Sander 2025-02-03 21:28 4267
int  ImGuiInputTextState::GetSelectionEnd() const           { return Stb->select_end; }
4e9e2555 Poul Sander 2025-10-05 10:15 4268
float ImGuiInputTextState::GetPreferredOffsetX() const      { return Stb->has_preferred_x ? Stb->preferred_x : -1; }
4e9e2555 Poul Sander 2025-10-05 10:15 4269
void ImGuiInputTextState::SelectAll()                       { Stb->select_start = 0; Stb->cursor = Stb->select_end = TextLen; Stb->has_preferred_x = 0; }
4e9e2555 Poul Sander 2025-10-05 10:15 4270
void ImGuiInputTextState::ReloadUserBufAndSelectAll()       { WantReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; }
4e9e2555 Poul Sander 2025-10-05 10:15 4271
void ImGuiInputTextState::ReloadUserBufAndKeepSelection()   { WantReloadUserBuf = true; ReloadSelectionStart = Stb->select_start; ReloadSelectionEnd = Stb->select_end; }
4e9e2555 Poul Sander 2025-10-05 10:15 4272
void ImGuiInputTextState::ReloadUserBufAndMoveToEnd()       { WantReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; }
1193b4be Poul Sander 2025-02-03 21:28 4273
1193b4be Poul Sander 2025-02-03 21:28 4274
ImGuiInputTextCallbackData::ImGuiInputTextCallbackData()
1193b4be Poul Sander 2025-02-03 21:28 4275
{
1193b4be Poul Sander 2025-02-03 21:28 4276
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 4277
}
1193b4be Poul Sander 2025-02-03 21:28 4278
4e9e2555 Poul Sander 2025-10-05 10:15 4279
// Public API to manipulate UTF-8 text from within a callback.
1193b4be Poul Sander 2025-02-03 21:28 4280
// FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
4e9e2555 Poul Sander 2025-10-05 10:15 4281
// Historically they existed because STB_TEXTEDIT_INSERTCHARS() etc. worked on our ImWchar
4e9e2555 Poul Sander 2025-10-05 10:15 4282
// buffer, but nowadays they both work on UTF-8 data. Should aim to merge both.
1193b4be Poul Sander 2025-02-03 21:28 4283
void ImGuiInputTextCallbackData::DeleteChars(int pos, int bytes_count)
1193b4be Poul Sander 2025-02-03 21:28 4284
{
1193b4be Poul Sander 2025-02-03 21:28 4285
    IM_ASSERT(pos + bytes_count <= BufTextLen);
1193b4be Poul Sander 2025-02-03 21:28 4286
    char* dst = Buf + pos;
1193b4be Poul Sander 2025-02-03 21:28 4287
    const char* src = Buf + pos + bytes_count;
4e9e2555 Poul Sander 2025-10-05 10:15 4288
    memmove(dst, src, BufTextLen - bytes_count - pos + 1);
1193b4be Poul Sander 2025-02-03 21:28 4289
1193b4be Poul Sander 2025-02-03 21:28 4290
    if (CursorPos >= pos + bytes_count)
1193b4be Poul Sander 2025-02-03 21:28 4291
        CursorPos -= bytes_count;
1193b4be Poul Sander 2025-02-03 21:28 4292
    else if (CursorPos >= pos)
1193b4be Poul Sander 2025-02-03 21:28 4293
        CursorPos = pos;
1193b4be Poul Sander 2025-02-03 21:28 4294
    SelectionStart = SelectionEnd = CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4295
    BufDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 4296
    BufTextLen -= bytes_count;
1193b4be Poul Sander 2025-02-03 21:28 4297
}
1193b4be Poul Sander 2025-02-03 21:28 4298
1193b4be Poul Sander 2025-02-03 21:28 4299
void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
1193b4be Poul Sander 2025-02-03 21:28 4300
{
1193b4be Poul Sander 2025-02-03 21:28 4301
    // Accept null ranges
1193b4be Poul Sander 2025-02-03 21:28 4302
    if (new_text == new_text_end)
1193b4be Poul Sander 2025-02-03 21:28 4303
        return;
1193b4be Poul Sander 2025-02-03 21:28 4304
4e9e2555 Poul Sander 2025-10-05 10:15 4305
    ImGuiContext& g = *Ctx;
4e9e2555 Poul Sander 2025-10-05 10:15 4306
    ImGuiInputTextState* obj = &g.InputTextState;
4e9e2555 Poul Sander 2025-10-05 10:15 4307
    IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID);
4e9e2555 Poul Sander 2025-10-05 10:15 4308
4e9e2555 Poul Sander 2025-10-05 10:15 4309
    // Grow internal buffer if needed
1193b4be Poul Sander 2025-02-03 21:28 4310
    const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
4e9e2555 Poul Sander 2025-10-05 10:15 4311
    const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);
4e9e2555 Poul Sander 2025-10-05 10:15 4312
    if (new_text_len + BufTextLen + 1 > obj->TextA.Size && (Flags & ImGuiInputTextFlags_ReadOnly) == 0)
1193b4be Poul Sander 2025-02-03 21:28 4313
    {
1193b4be Poul Sander 2025-02-03 21:28 4314
        if (!is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4315
            return;
1193b4be Poul Sander 2025-02-03 21:28 4316
4e9e2555 Poul Sander 2025-10-05 10:15 4317
        IM_ASSERT(Buf == obj->TextA.Data);
1193b4be Poul Sander 2025-02-03 21:28 4318
        int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
4e9e2555 Poul Sander 2025-10-05 10:15 4319
        obj->TextA.resize(new_buf_size + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4320
        obj->TextSrc = obj->TextA.Data;
4e9e2555 Poul Sander 2025-10-05 10:15 4321
        Buf = obj->TextA.Data;
4e9e2555 Poul Sander 2025-10-05 10:15 4322
        BufSize = obj->BufCapacity = new_buf_size;
1193b4be Poul Sander 2025-02-03 21:28 4323
    }
1193b4be Poul Sander 2025-02-03 21:28 4324
1193b4be Poul Sander 2025-02-03 21:28 4325
    if (BufTextLen != pos)
1193b4be Poul Sander 2025-02-03 21:28 4326
        memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
1193b4be Poul Sander 2025-02-03 21:28 4327
    memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
1193b4be Poul Sander 2025-02-03 21:28 4328
    Buf[BufTextLen + new_text_len] = '\0';
1193b4be Poul Sander 2025-02-03 21:28 4329
1193b4be Poul Sander 2025-02-03 21:28 4330
    if (CursorPos >= pos)
1193b4be Poul Sander 2025-02-03 21:28 4331
        CursorPos += new_text_len;
1193b4be Poul Sander 2025-02-03 21:28 4332
    SelectionStart = SelectionEnd = CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4333
    BufDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 4334
    BufTextLen += new_text_len;
1193b4be Poul Sander 2025-02-03 21:28 4335
}
1193b4be Poul Sander 2025-02-03 21:28 4336
4e9e2555 Poul Sander 2025-10-05 10:15 4337
void ImGui::PushPasswordFont()
4e9e2555 Poul Sander 2025-10-05 10:15 4338
{
4e9e2555 Poul Sander 2025-10-05 10:15 4339
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 4340
    ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
4e9e2555 Poul Sander 2025-10-05 10:15 4341
    IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
4e9e2555 Poul Sander 2025-10-05 10:15 4342
    ImFontGlyph* glyph = g.FontBaked->FindGlyph('*');
4e9e2555 Poul Sander 2025-10-05 10:15 4343
    g.InputTextPasswordFontBackupFlags = g.Font->Flags;
4e9e2555 Poul Sander 2025-10-05 10:15 4344
    backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex;
4e9e2555 Poul Sander 2025-10-05 10:15 4345
    backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX;
4e9e2555 Poul Sander 2025-10-05 10:15 4346
    backup->IndexLookup.swap(g.FontBaked->IndexLookup);
4e9e2555 Poul Sander 2025-10-05 10:15 4347
    backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX);
4e9e2555 Poul Sander 2025-10-05 10:15 4348
    g.Font->Flags |= ImFontFlags_NoLoadGlyphs;
4e9e2555 Poul Sander 2025-10-05 10:15 4349
    g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph);
4e9e2555 Poul Sander 2025-10-05 10:15 4350
    g.FontBaked->FallbackAdvanceX = glyph->AdvanceX;
4e9e2555 Poul Sander 2025-10-05 10:15 4351
}
4e9e2555 Poul Sander 2025-10-05 10:15 4352
4e9e2555 Poul Sander 2025-10-05 10:15 4353
void ImGui::PopPasswordFont()
4e9e2555 Poul Sander 2025-10-05 10:15 4354
{
4e9e2555 Poul Sander 2025-10-05 10:15 4355
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 4356
    ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
4e9e2555 Poul Sander 2025-10-05 10:15 4357
    g.Font->Flags = g.InputTextPasswordFontBackupFlags;
4e9e2555 Poul Sander 2025-10-05 10:15 4358
    g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex;
4e9e2555 Poul Sander 2025-10-05 10:15 4359
    g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX;
4e9e2555 Poul Sander 2025-10-05 10:15 4360
    g.FontBaked->IndexLookup.swap(backup->IndexLookup);
4e9e2555 Poul Sander 2025-10-05 10:15 4361
    g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX);
4e9e2555 Poul Sander 2025-10-05 10:15 4362
    IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
4e9e2555 Poul Sander 2025-10-05 10:15 4363
}
4e9e2555 Poul Sander 2025-10-05 10:15 4364
1193b4be Poul Sander 2025-02-03 21:28 4365
// Return false to discard a character.
1193b4be Poul Sander 2025-02-03 21:28 4366
static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard)
1193b4be Poul Sander 2025-02-03 21:28 4367
{
1193b4be Poul Sander 2025-02-03 21:28 4368
    unsigned int c = *p_char;
1193b4be Poul Sander 2025-02-03 21:28 4369
1193b4be Poul Sander 2025-02-03 21:28 4370
    // Filter non-printable (NB: isprint is unreliable! see #2467)
1193b4be Poul Sander 2025-02-03 21:28 4371
    bool apply_named_filters = true;
1193b4be Poul Sander 2025-02-03 21:28 4372
    if (c < 0x20)
1193b4be Poul Sander 2025-02-03 21:28 4373
    {
1193b4be Poul Sander 2025-02-03 21:28 4374
        bool pass = false;
4e9e2555 Poul Sander 2025-10-05 10:15 4375
        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)
4e9e2555 Poul Sander 2025-10-05 10:15 4376
        if (c == '\n' && input_source_is_clipboard && (flags & ImGuiInputTextFlags_Multiline) == 0) // In single line mode, replace \n with a space
4e9e2555 Poul Sander 2025-10-05 10:15 4377
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4378
            c = *p_char = ' ';
4e9e2555 Poul Sander 2025-10-05 10:15 4379
            pass = true;
4e9e2555 Poul Sander 2025-10-05 10:15 4380
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4381
        pass |= (c == '\n') && (flags & ImGuiInputTextFlags_Multiline) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4382
        pass |= (c == '\t') && (flags & ImGuiInputTextFlags_AllowTabInput) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4383
        if (!pass)
1193b4be Poul Sander 2025-02-03 21:28 4384
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4385
        apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted.
1193b4be Poul Sander 2025-02-03 21:28 4386
    }
1193b4be Poul Sander 2025-02-03 21:28 4387
1193b4be Poul Sander 2025-02-03 21:28 4388
    if (input_source_is_clipboard == false)
1193b4be Poul Sander 2025-02-03 21:28 4389
    {
1193b4be Poul Sander 2025-02-03 21:28 4390
        // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817)
1193b4be Poul Sander 2025-02-03 21:28 4391
        if (c == 127)
1193b4be Poul Sander 2025-02-03 21:28 4392
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4393
1193b4be Poul Sander 2025-02-03 21:28 4394
        // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME)
1193b4be Poul Sander 2025-02-03 21:28 4395
        if (c >= 0xE000 && c <= 0xF8FF)
1193b4be Poul Sander 2025-02-03 21:28 4396
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4397
    }
1193b4be Poul Sander 2025-02-03 21:28 4398
1193b4be Poul Sander 2025-02-03 21:28 4399
    // Filter Unicode ranges we are not handling in this build
1193b4be Poul Sander 2025-02-03 21:28 4400
    if (c > IM_UNICODE_CODEPOINT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 4401
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4402
1193b4be Poul Sander 2025-02-03 21:28 4403
    // Generic named filters
1193b4be Poul Sander 2025-02-03 21:28 4404
    if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)))
1193b4be Poul Sander 2025-02-03 21:28 4405
    {
1193b4be Poul Sander 2025-02-03 21:28 4406
        // 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 '.'.
1193b4be Poul Sander 2025-02-03 21:28 4407
        // The standard mandate that programs starts in the "C" locale where the decimal point is '.'.
1193b4be Poul Sander 2025-02-03 21:28 4408
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 4409
        // Change the default decimal_point with:
1193b4be Poul Sander 2025-02-03 21:28 4410
        //   ImGui::GetPlatformIO()->Platform_LocaleDecimalPoint = *localeconv()->decimal_point;
1193b4be Poul Sander 2025-02-03 21:28 4411
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 4412
        ImGuiContext& g = *ctx;
1193b4be Poul Sander 2025-02-03 21:28 4413
        const unsigned c_decimal_point = (unsigned int)g.PlatformIO.Platform_LocaleDecimalPoint;
1193b4be Poul Sander 2025-02-03 21:28 4414
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))
1193b4be Poul Sander 2025-02-03 21:28 4415
            if (c == '.' || c == ',')
1193b4be Poul Sander 2025-02-03 21:28 4416
                c = c_decimal_point;
1193b4be Poul Sander 2025-02-03 21:28 4417
4e9e2555 Poul Sander 2025-10-05 10:15 4418
        // Full-width -> half-width conversion for numeric fields: https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
1193b4be Poul Sander 2025-02-03 21:28 4419
        // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
1193b4be Poul Sander 2025-02-03 21:28 4420
        // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
1193b4be Poul Sander 2025-02-03 21:28 4421
        if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
1193b4be Poul Sander 2025-02-03 21:28 4422
            if (c >= 0xFF01 && c <= 0xFF5E)
1193b4be Poul Sander 2025-02-03 21:28 4423
                c = c - 0xFF01 + 0x21;
1193b4be Poul Sander 2025-02-03 21:28 4424
1193b4be Poul Sander 2025-02-03 21:28 4425
        // Allow 0-9 . - + * /
1193b4be Poul Sander 2025-02-03 21:28 4426
        if (flags & ImGuiInputTextFlags_CharsDecimal)
1193b4be Poul Sander 2025-02-03 21:28 4427
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
1193b4be Poul Sander 2025-02-03 21:28 4428
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4429
1193b4be Poul Sander 2025-02-03 21:28 4430
        // Allow 0-9 . - + * / e E
1193b4be Poul Sander 2025-02-03 21:28 4431
        if (flags & ImGuiInputTextFlags_CharsScientific)
1193b4be Poul Sander 2025-02-03 21:28 4432
            if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
1193b4be Poul Sander 2025-02-03 21:28 4433
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4434
1193b4be Poul Sander 2025-02-03 21:28 4435
        // Allow 0-9 a-F A-F
1193b4be Poul Sander 2025-02-03 21:28 4436
        if (flags & ImGuiInputTextFlags_CharsHexadecimal)
1193b4be Poul Sander 2025-02-03 21:28 4437
            if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
1193b4be Poul Sander 2025-02-03 21:28 4438
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4439
1193b4be Poul Sander 2025-02-03 21:28 4440
        // Turn a-z into A-Z
1193b4be Poul Sander 2025-02-03 21:28 4441
        if (flags & ImGuiInputTextFlags_CharsUppercase)
1193b4be Poul Sander 2025-02-03 21:28 4442
            if (c >= 'a' && c <= 'z')
1193b4be Poul Sander 2025-02-03 21:28 4443
                c += (unsigned int)('A' - 'a');
1193b4be Poul Sander 2025-02-03 21:28 4444
1193b4be Poul Sander 2025-02-03 21:28 4445
        if (flags & ImGuiInputTextFlags_CharsNoBlank)
1193b4be Poul Sander 2025-02-03 21:28 4446
            if (ImCharIsBlankW(c))
1193b4be Poul Sander 2025-02-03 21:28 4447
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4448
1193b4be Poul Sander 2025-02-03 21:28 4449
        *p_char = c;
1193b4be Poul Sander 2025-02-03 21:28 4450
    }
1193b4be Poul Sander 2025-02-03 21:28 4451
1193b4be Poul Sander 2025-02-03 21:28 4452
    // Custom callback filter
1193b4be Poul Sander 2025-02-03 21:28 4453
    if (flags & ImGuiInputTextFlags_CallbackCharFilter)
1193b4be Poul Sander 2025-02-03 21:28 4454
    {
1193b4be Poul Sander 2025-02-03 21:28 4455
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4456
        ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 4457
        callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 4458
        callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
1193b4be Poul Sander 2025-02-03 21:28 4459
        callback_data.EventChar = (ImWchar)c;
1193b4be Poul Sander 2025-02-03 21:28 4460
        callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 4461
        callback_data.UserData = user_data;
1193b4be Poul Sander 2025-02-03 21:28 4462
        if (callback(&callback_data) != 0)
1193b4be Poul Sander 2025-02-03 21:28 4463
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4464
        *p_char = callback_data.EventChar;
1193b4be Poul Sander 2025-02-03 21:28 4465
        if (!callback_data.EventChar)
1193b4be Poul Sander 2025-02-03 21:28 4466
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4467
    }
1193b4be Poul Sander 2025-02-03 21:28 4468
1193b4be Poul Sander 2025-02-03 21:28 4469
    return true;
1193b4be Poul Sander 2025-02-03 21:28 4470
}
1193b4be Poul Sander 2025-02-03 21:28 4471
4e9e2555 Poul Sander 2025-10-05 10:15 4472
// Find the shortest single replacement we can make to get from old_buf to new_buf
4e9e2555 Poul Sander 2025-10-05 10:15 4473
// Note that this doesn't directly alter state->TextA, state->TextLen. They are expected to be made valid separately.
1193b4be Poul Sander 2025-02-03 21:28 4474
// 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.
4e9e2555 Poul Sander 2025-10-05 10:15 4475
static void InputTextReconcileUndoState(ImGuiInputTextState* state, const char* old_buf, int old_length, const char* new_buf, int new_length)
1193b4be Poul Sander 2025-02-03 21:28 4476
{
4e9e2555 Poul Sander 2025-10-05 10:15 4477
    const int shorter_length = ImMin(old_length, new_length);
1193b4be Poul Sander 2025-02-03 21:28 4478
    int first_diff;
1193b4be Poul Sander 2025-02-03 21:28 4479
    for (first_diff = 0; first_diff < shorter_length; first_diff++)
4e9e2555 Poul Sander 2025-10-05 10:15 4480
        if (old_buf[first_diff] != new_buf[first_diff])
1193b4be Poul Sander 2025-02-03 21:28 4481
            break;
4e9e2555 Poul Sander 2025-10-05 10:15 4482
    if (first_diff == old_length && first_diff == new_length)
1193b4be Poul Sander 2025-02-03 21:28 4483
        return;
1193b4be Poul Sander 2025-02-03 21:28 4484
1193b4be Poul Sander 2025-02-03 21:28 4485
    int old_last_diff = old_length   - 1;
4e9e2555 Poul Sander 2025-10-05 10:15 4486
    int new_last_diff = new_length - 1;
1193b4be Poul Sander 2025-02-03 21:28 4487
    for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--)
4e9e2555 Poul Sander 2025-10-05 10:15 4488
        if (old_buf[old_last_diff] != new_buf[new_last_diff])
1193b4be Poul Sander 2025-02-03 21:28 4489
            break;
1193b4be Poul Sander 2025-02-03 21:28 4490
1193b4be Poul Sander 2025-02-03 21:28 4491
    const int insert_len = new_last_diff - first_diff + 1;
1193b4be Poul Sander 2025-02-03 21:28 4492
    const int delete_len = old_last_diff - first_diff + 1;
1193b4be Poul Sander 2025-02-03 21:28 4493
    if (insert_len > 0 || delete_len > 0)
1193b4be Poul Sander 2025-02-03 21:28 4494
        if (IMSTB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb->undostate, first_diff, delete_len, insert_len))
1193b4be Poul Sander 2025-02-03 21:28 4495
            for (int i = 0; i < delete_len; i++)
1193b4be Poul Sander 2025-02-03 21:28 4496
                p[i] = old_buf[first_diff + i];
1193b4be Poul Sander 2025-02-03 21:28 4497
}
1193b4be Poul Sander 2025-02-03 21:28 4498
1193b4be Poul Sander 2025-02-03 21:28 4499
// As InputText() retain textual data and we currently provide a path for user to not retain it (via local variables)
1193b4be Poul Sander 2025-02-03 21:28 4500
// we need some form of hook to reapply data back to user buffer on deactivation frame. (#4714)
1193b4be Poul Sander 2025-02-03 21:28 4501
// It would be more desirable that we discourage users from taking advantage of the "user not retaining data" trick,
1193b4be Poul Sander 2025-02-03 21:28 4502
// but that more likely be attractive when we do have _NoLiveEdit flag available.
1193b4be Poul Sander 2025-02-03 21:28 4503
void ImGui::InputTextDeactivateHook(ImGuiID id)
1193b4be Poul Sander 2025-02-03 21:28 4504
{
1193b4be Poul Sander 2025-02-03 21:28 4505
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4506
    ImGuiInputTextState* state = &g.InputTextState;
1193b4be Poul Sander 2025-02-03 21:28 4507
    if (id == 0 || state->ID != id)
1193b4be Poul Sander 2025-02-03 21:28 4508
        return;
1193b4be Poul Sander 2025-02-03 21:28 4509
    g.InputTextDeactivatedState.ID = state->ID;
1193b4be Poul Sander 2025-02-03 21:28 4510
    if (state->Flags & ImGuiInputTextFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 4511
    {
1193b4be Poul Sander 2025-02-03 21:28 4512
        g.InputTextDeactivatedState.TextA.resize(0); // In theory this data won't be used, but clear to be neat.
1193b4be Poul Sander 2025-02-03 21:28 4513
    }
1193b4be Poul Sander 2025-02-03 21:28 4514
    else
1193b4be Poul Sander 2025-02-03 21:28 4515
    {
1193b4be Poul Sander 2025-02-03 21:28 4516
        IM_ASSERT(state->TextA.Data != 0);
4e9e2555 Poul Sander 2025-10-05 10:15 4517
        IM_ASSERT(state->TextA[state->TextLen] == 0);
4e9e2555 Poul Sander 2025-10-05 10:15 4518
        g.InputTextDeactivatedState.TextA.resize(state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4519
        memcpy(g.InputTextDeactivatedState.TextA.Data, state->TextA.Data, state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4520
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4521
}
4e9e2555 Poul Sander 2025-10-05 10:15 4522
4e9e2555 Poul Sander 2025-10-05 10:15 4523
static int* ImLowerBound(int* in_begin, int* in_end, int v)
4e9e2555 Poul Sander 2025-10-05 10:15 4524
{
4e9e2555 Poul Sander 2025-10-05 10:15 4525
    int* in_p = in_begin;
4e9e2555 Poul Sander 2025-10-05 10:15 4526
    for (size_t count = (size_t)(in_end - in_p); count > 0; )
4e9e2555 Poul Sander 2025-10-05 10:15 4527
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4528
        size_t count2 = count >> 1;
4e9e2555 Poul Sander 2025-10-05 10:15 4529
        int* mid = in_p + count2;
4e9e2555 Poul Sander 2025-10-05 10:15 4530
        if (*mid < v)
4e9e2555 Poul Sander 2025-10-05 10:15 4531
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4532
            in_p = ++mid;
4e9e2555 Poul Sander 2025-10-05 10:15 4533
            count -= count2 + 1;
4e9e2555 Poul Sander 2025-10-05 10:15 4534
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4535
        else
4e9e2555 Poul Sander 2025-10-05 10:15 4536
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4537
            count = count2;
4e9e2555 Poul Sander 2025-10-05 10:15 4538
        }
1193b4be Poul Sander 2025-02-03 21:28 4539
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4540
    return in_p;
4e9e2555 Poul Sander 2025-10-05 10:15 4541
}
4e9e2555 Poul Sander 2025-10-05 10:15 4542
4e9e2555 Poul Sander 2025-10-05 10:15 4543
// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool?
4e9e2555 Poul Sander 2025-10-05 10:15 4544
// '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)
4e9e2555 Poul Sander 2025-10-05 10:15 4545
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)
4e9e2555 Poul Sander 2025-10-05 10:15 4546
{
4e9e2555 Poul Sander 2025-10-05 10:15 4547
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 4548
    int size = 0;
4e9e2555 Poul Sander 2025-10-05 10:15 4549
    const char* s;
4e9e2555 Poul Sander 2025-10-05 10:15 4550
    if (flags & ImGuiInputTextFlags_WordWrap)
4e9e2555 Poul Sander 2025-10-05 10:15 4551
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4552
        for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s)
4e9e2555 Poul Sander 2025-10-05 10:15 4553
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4554
            if (size++ <= max_output_buffer_size)
4e9e2555 Poul Sander 2025-10-05 10:15 4555
                line_index->Offsets.push_back((int)(s - buf));
4e9e2555 Poul Sander 2025-10-05 10:15 4556
            s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
4e9e2555 Poul Sander 2025-10-05 10:15 4557
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4558
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4559
    else if (buf_end != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4560
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4561
        for (s = buf; s < buf_end; s = s ? s + 1 : buf_end)
4e9e2555 Poul Sander 2025-10-05 10:15 4562
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4563
            if (size++ <= max_output_buffer_size)
4e9e2555 Poul Sander 2025-10-05 10:15 4564
                line_index->Offsets.push_back((int)(s - buf));
4e9e2555 Poul Sander 2025-10-05 10:15 4565
            s = (const char*)ImMemchr(s, '\n', buf_end - s);
4e9e2555 Poul Sander 2025-10-05 10:15 4566
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4567
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4568
    else
4e9e2555 Poul Sander 2025-10-05 10:15 4569
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4570
        const char* s_eol;
4e9e2555 Poul Sander 2025-10-05 10:15 4571
        for (s = buf; ; s = s_eol + 1)
4e9e2555 Poul Sander 2025-10-05 10:15 4572
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4573
            if (size++ <= max_output_buffer_size)
4e9e2555 Poul Sander 2025-10-05 10:15 4574
                line_index->Offsets.push_back((int)(s - buf));
4e9e2555 Poul Sander 2025-10-05 10:15 4575
            if ((s_eol = strchr(s, '\n')) != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4576
                continue;
4e9e2555 Poul Sander 2025-10-05 10:15 4577
            s += strlen(s);
4e9e2555 Poul Sander 2025-10-05 10:15 4578
            break;
4e9e2555 Poul Sander 2025-10-05 10:15 4579
        }
4e9e2555 Poul Sander 2025-10-05 10:15 4580
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4581
    if (out_buf_end != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4582
        *out_buf_end = buf_end = s;
4e9e2555 Poul Sander 2025-10-05 10:15 4583
    if (size == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 4584
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4585
        line_index->Offsets.push_back(0);
4e9e2555 Poul Sander 2025-10-05 10:15 4586
        size++;
4e9e2555 Poul Sander 2025-10-05 10:15 4587
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4588
    if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size)
4e9e2555 Poul Sander 2025-10-05 10:15 4589
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4590
        line_index->Offsets.push_back((int)(buf_end - buf));
4e9e2555 Poul Sander 2025-10-05 10:15 4591
        size++;
4e9e2555 Poul Sander 2025-10-05 10:15 4592
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4593
    return size;
4e9e2555 Poul Sander 2025-10-05 10:15 4594
}
4e9e2555 Poul Sander 2025-10-05 10:15 4595
4e9e2555 Poul Sander 2025-10-05 10:15 4596
static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n)
4e9e2555 Poul Sander 2025-10-05 10:15 4597
{
4e9e2555 Poul Sander 2025-10-05 10:15 4598
    const char* cursor_ptr = buf + cursor_n;
4e9e2555 Poul Sander 2025-10-05 10:15 4599
    int* it_begin = line_index->Offsets.begin();
4e9e2555 Poul Sander 2025-10-05 10:15 4600
    int* it_end = line_index->Offsets.end();
4e9e2555 Poul Sander 2025-10-05 10:15 4601
    const int* it = ImLowerBound(it_begin, it_end, cursor_n);
4e9e2555 Poul Sander 2025-10-05 10:15 4602
    if (it > it_begin)
4e9e2555 Poul Sander 2025-10-05 10:15 4603
        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))
4e9e2555 Poul Sander 2025-10-05 10:15 4604
            it--;
4e9e2555 Poul Sander 2025-10-05 10:15 4605
4e9e2555 Poul Sander 2025-10-05 10:15 4606
    const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it);
4e9e2555 Poul Sander 2025-10-05 10:15 4607
    const char* line_start = line_index->get_line_begin(buf, line_no);
4e9e2555 Poul Sander 2025-10-05 10:15 4608
    ImVec2 offset;
4e9e2555 Poul Sander 2025-10-05 10:15 4609
    offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x;
4e9e2555 Poul Sander 2025-10-05 10:15 4610
    offset.y = (line_no + 1) * g.FontSize;
4e9e2555 Poul Sander 2025-10-05 10:15 4611
    return offset;
1193b4be Poul Sander 2025-02-03 21:28 4612
}
1193b4be Poul Sander 2025-02-03 21:28 4613
1193b4be Poul Sander 2025-02-03 21:28 4614
// Edit a string of text
1193b4be Poul Sander 2025-02-03 21:28 4615
// - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
1193b4be Poul Sander 2025-02-03 21:28 4616
//   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
1193b4be Poul Sander 2025-02-03 21:28 4617
//   Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator.
1193b4be Poul Sander 2025-02-03 21:28 4618
// - 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.
1193b4be Poul Sander 2025-02-03 21:28 4619
// - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h
1193b4be Poul Sander 2025-02-03 21:28 4620
// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are
1193b4be Poul Sander 2025-02-03 21:28 4621
//  doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188)
1193b4be Poul Sander 2025-02-03 21:28 4622
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)
1193b4be Poul Sander 2025-02-03 21:28 4623
{
1193b4be Poul Sander 2025-02-03 21:28 4624
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 4625
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 4626
        return false;
1193b4be Poul Sander 2025-02-03 21:28 4627
1193b4be Poul Sander 2025-02-03 21:28 4628
    IM_ASSERT(buf != NULL && buf_size >= 0);
1193b4be Poul Sander 2025-02-03 21:28 4629
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline)));        // Can't use both together (they both use up/down keys)
1193b4be Poul Sander 2025-02-03 21:28 4630
    IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
4e9e2555 Poul Sander 2025-10-05 10:15 4631
    IM_ASSERT(!((flags & ImGuiInputTextFlags_ElideLeft) && (flags & ImGuiInputTextFlags_Multiline)));              // Multiline does not not work with left-trimming
4e9e2555 Poul Sander 2025-10-05 10:15 4632
    IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Password) == 0);         // WordWrap does not work with Password mode.
4e9e2555 Poul Sander 2025-10-05 10:15 4633
    IM_ASSERT((flags & ImGuiInputTextFlags_WordWrap) == 0 || (flags & ImGuiInputTextFlags_Multiline) != 0);        // WordWrap does not work in single-line mode.
1193b4be Poul Sander 2025-02-03 21:28 4634
1193b4be Poul Sander 2025-02-03 21:28 4635
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 4636
    ImGuiIO& io = g.IO;
1193b4be Poul Sander 2025-02-03 21:28 4637
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 4638
1193b4be Poul Sander 2025-02-03 21:28 4639
    const bool RENDER_SELECTION_WHEN_INACTIVE = false;
1193b4be Poul Sander 2025-02-03 21:28 4640
    const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4641
1193b4be Poul Sander 2025-02-03 21:28 4642
    if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 4643
        BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 4644
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 4645
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 4646
    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
1193b4be Poul Sander 2025-02-03 21:28 4647
    const ImVec2 total_size = ImVec2(frame_size.x + (label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f), frame_size.y);
1193b4be Poul Sander 2025-02-03 21:28 4648
1193b4be Poul Sander 2025-02-03 21:28 4649
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 4650
    const ImRect total_bb(frame_bb.Min, frame_bb.Min + total_size);
1193b4be Poul Sander 2025-02-03 21:28 4651
1193b4be Poul Sander 2025-02-03 21:28 4652
    ImGuiWindow* draw_window = window;
1193b4be Poul Sander 2025-02-03 21:28 4653
    ImVec2 inner_size = frame_size;
1193b4be Poul Sander 2025-02-03 21:28 4654
    ImGuiLastItemData item_data_backup;
1193b4be Poul Sander 2025-02-03 21:28 4655
    if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4656
    {
1193b4be Poul Sander 2025-02-03 21:28 4657
        ImVec2 backup_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 4658
        ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 4659
        if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
1193b4be Poul Sander 2025-02-03 21:28 4660
        {
1193b4be Poul Sander 2025-02-03 21:28 4661
            EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 4662
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4663
        }
1193b4be Poul Sander 2025-02-03 21:28 4664
        item_data_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 4665
        window->DC.CursorPos = backup_pos;
1193b4be Poul Sander 2025-02-03 21:28 4666
4e9e2555 Poul Sander 2025-10-05 10:15 4667
        // Prevent NavActivation from explicit Tabbing when our widget accepts Tab inputs: this allows cycling through widgets without stopping.
4e9e2555 Poul Sander 2025-10-05 10:15 4668
        if (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_FromTabbing) && !(g.NavActivateFlags & ImGuiActivateFlags_FromFocusApi) && (flags & ImGuiInputTextFlags_AllowTabInput))
1193b4be Poul Sander 2025-02-03 21:28 4669
            g.NavActivateId = 0;
1193b4be Poul Sander 2025-02-03 21:28 4670
1193b4be Poul Sander 2025-02-03 21:28 4671
        // Prevent NavActivate reactivating in BeginChild() when we are already active.
1193b4be Poul Sander 2025-02-03 21:28 4672
        const ImGuiID backup_activate_id = g.NavActivateId;
1193b4be Poul Sander 2025-02-03 21:28 4673
        if (g.ActiveId == id) // Prevent reactivation
1193b4be Poul Sander 2025-02-03 21:28 4674
            g.NavActivateId = 0;
1193b4be Poul Sander 2025-02-03 21:28 4675
1193b4be Poul Sander 2025-02-03 21:28 4676
        // We reproduce the contents of BeginChildFrame() in order to provide 'label' so our window internal data are easier to read/debug.
1193b4be Poul Sander 2025-02-03 21:28 4677
        PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
1193b4be Poul Sander 2025-02-03 21:28 4678
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 4679
        PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 4680
        PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); // Ensure no clip rect so mouse hover can reach FramePadding edges
1193b4be Poul Sander 2025-02-03 21:28 4681
        bool child_visible = BeginChildEx(label, id, frame_bb.GetSize(), ImGuiChildFlags_Borders, ImGuiWindowFlags_NoMove);
1193b4be Poul Sander 2025-02-03 21:28 4682
        g.NavActivateId = backup_activate_id;
1193b4be Poul Sander 2025-02-03 21:28 4683
        PopStyleVar(3);
1193b4be Poul Sander 2025-02-03 21:28 4684
        PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 4685
        if (!child_visible)
1193b4be Poul Sander 2025-02-03 21:28 4686
        {
1193b4be Poul Sander 2025-02-03 21:28 4687
            EndChild();
1193b4be Poul Sander 2025-02-03 21:28 4688
            EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 4689
            return false;
1193b4be Poul Sander 2025-02-03 21:28 4690
        }
1193b4be Poul Sander 2025-02-03 21:28 4691
        draw_window = g.CurrentWindow; // Child window
1193b4be Poul Sander 2025-02-03 21:28 4692
        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.
1193b4be Poul Sander 2025-02-03 21:28 4693
        draw_window->DC.CursorPos += style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 4694
        inner_size.x -= draw_window->ScrollbarSizes.x;
1193b4be Poul Sander 2025-02-03 21:28 4695
    }
1193b4be Poul Sander 2025-02-03 21:28 4696
    else
1193b4be Poul Sander 2025-02-03 21:28 4697
    {
1193b4be Poul Sander 2025-02-03 21:28 4698
        // Support for internal ImGuiInputTextFlags_MergedItem flag, which could be redesigned as an ItemFlags if needed (with test performed in ItemAdd)
1193b4be Poul Sander 2025-02-03 21:28 4699
        ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 4700
        if (!(flags & ImGuiInputTextFlags_MergedItem))
1193b4be Poul Sander 2025-02-03 21:28 4701
            if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_Inputable))
1193b4be Poul Sander 2025-02-03 21:28 4702
                return false;
1193b4be Poul Sander 2025-02-03 21:28 4703
    }
1193b4be Poul Sander 2025-02-03 21:28 4704
1193b4be Poul Sander 2025-02-03 21:28 4705
    // Ensure mouse cursor is set even after switching to keyboard/gamepad mode. May generalize further? (#6417)
1193b4be Poul Sander 2025-02-03 21:28 4706
    bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags | ImGuiItemFlags_NoNavDisableMouseHover);
1193b4be Poul Sander 2025-02-03 21:28 4707
    if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 4708
        SetMouseCursor(ImGuiMouseCursor_TextInput);
1193b4be Poul Sander 2025-02-03 21:28 4709
    if (hovered && g.NavHighlightItemUnderNav)
1193b4be Poul Sander 2025-02-03 21:28 4710
        hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 4711
1193b4be Poul Sander 2025-02-03 21:28 4712
    // We are only allowed to access the state if we are already the active widget.
1193b4be Poul Sander 2025-02-03 21:28 4713
    ImGuiInputTextState* state = GetInputTextState(id);
1193b4be Poul Sander 2025-02-03 21:28 4714
1193b4be Poul Sander 2025-02-03 21:28 4715
    if (g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly)
1193b4be Poul Sander 2025-02-03 21:28 4716
        flags |= ImGuiInputTextFlags_ReadOnly;
1193b4be Poul Sander 2025-02-03 21:28 4717
    const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4718
    const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4719
    const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
1193b4be Poul Sander 2025-02-03 21:28 4720
    const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0;
1193b4be Poul Sander 2025-02-03 21:28 4721
    if (is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 4722
        IM_ASSERT(callback != NULL); // Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!
1193b4be Poul Sander 2025-02-03 21:28 4723
4e9e2555 Poul Sander 2025-10-05 10:15 4724
    // Word-wrapping: enforcing a fixed width not altered by vertical scrollbar makes things easier, notably to track cursor reliably and avoid one-frame glitches.
4e9e2555 Poul Sander 2025-10-05 10:15 4725
    // Instead of using ImGuiWindowFlags_AlwaysVerticalScrollbar we account for that space if the scrollbar is not visible.
4e9e2555 Poul Sander 2025-10-05 10:15 4726
    const bool is_wordwrap = (flags & ImGuiInputTextFlags_WordWrap) != 0;
4e9e2555 Poul Sander 2025-10-05 10:15 4727
    float wrap_width = 0.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 4728
    if (is_wordwrap)
4e9e2555 Poul Sander 2025-10-05 10:15 4729
        wrap_width = ImMax(1.0f, GetContentRegionAvail().x + (draw_window->ScrollbarY ? 0.0f : -g.Style.ScrollbarSize));
4e9e2555 Poul Sander 2025-10-05 10:15 4730
1193b4be Poul Sander 2025-02-03 21:28 4731
    const bool input_requested_by_nav = (g.ActiveId != id) && ((g.NavActivateId == id) && ((g.NavActivateFlags & ImGuiActivateFlags_PreferInput) || (g.NavInputSource == ImGuiInputSource_Keyboard)));
1193b4be Poul Sander 2025-02-03 21:28 4732
1193b4be Poul Sander 2025-02-03 21:28 4733
    const bool user_clicked = hovered && io.MouseClicked[0];
1193b4be Poul Sander 2025-02-03 21:28 4734
    const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
1193b4be Poul Sander 2025-02-03 21:28 4735
    const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetWindowScrollbarID(draw_window, ImGuiAxis_Y);
1193b4be Poul Sander 2025-02-03 21:28 4736
    bool clear_active_id = false;
1193b4be Poul Sander 2025-02-03 21:28 4737
    bool select_all = false;
1193b4be Poul Sander 2025-02-03 21:28 4738
1193b4be Poul Sander 2025-02-03 21:28 4739
    float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 4740
4e9e2555 Poul Sander 2025-10-05 10:15 4741
    const bool init_reload_from_user_buf = (state != NULL && state->WantReloadUserBuf);
1193b4be Poul Sander 2025-02-03 21:28 4742
    const bool init_changed_specs = (state != NULL && state->Stb->single_line != !is_multiline); // state != NULL means its our state.
1193b4be Poul Sander 2025-02-03 21:28 4743
    const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav);
1193b4be Poul Sander 2025-02-03 21:28 4744
    const bool init_state = (init_make_active || user_scroll_active);
4e9e2555 Poul Sander 2025-10-05 10:15 4745
    if (init_reload_from_user_buf)
4e9e2555 Poul Sander 2025-10-05 10:15 4746
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4747
        int new_len = (int)ImStrlen(buf);
4e9e2555 Poul Sander 2025-10-05 10:15 4748
        IM_ASSERT(new_len + 1 <= buf_size && "Is your input buffer properly zero-terminated?");
4e9e2555 Poul Sander 2025-10-05 10:15 4749
        state->WantReloadUserBuf = false;
4e9e2555 Poul Sander 2025-10-05 10:15 4750
        InputTextReconcileUndoState(state, state->TextA.Data, state->TextLen, buf, new_len);
4e9e2555 Poul Sander 2025-10-05 10:15 4751
        state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
4e9e2555 Poul Sander 2025-10-05 10:15 4752
        state->TextLen = new_len;
4e9e2555 Poul Sander 2025-10-05 10:15 4753
        memcpy(state->TextA.Data, buf, state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4754
        state->Stb->select_start = state->ReloadSelectionStart;
4e9e2555 Poul Sander 2025-10-05 10:15 4755
        state->Stb->cursor = state->Stb->select_end = state->ReloadSelectionEnd;
4e9e2555 Poul Sander 2025-10-05 10:15 4756
        state->CursorClamp();
4e9e2555 Poul Sander 2025-10-05 10:15 4757
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4758
    else if ((init_state && g.ActiveId != id) || init_changed_specs)
1193b4be Poul Sander 2025-02-03 21:28 4759
    {
1193b4be Poul Sander 2025-02-03 21:28 4760
        // Access state even if we don't own it yet.
1193b4be Poul Sander 2025-02-03 21:28 4761
        state = &g.InputTextState;
1193b4be Poul Sander 2025-02-03 21:28 4762
        state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4763
1193b4be Poul Sander 2025-02-03 21:28 4764
        // 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)
1193b4be Poul Sander 2025-02-03 21:28 4765
        InputTextDeactivateHook(state->ID);
1193b4be Poul Sander 2025-02-03 21:28 4766
4e9e2555 Poul Sander 2025-10-05 10:15 4767
        // Take a copy of the initial buffer value.
1193b4be Poul Sander 2025-02-03 21:28 4768
        // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode)
4e9e2555 Poul Sander 2025-10-05 10:15 4769
        const int buf_len = (int)ImStrlen(buf);
4e9e2555 Poul Sander 2025-10-05 10:15 4770
        IM_ASSERT(((buf_len + 1 <= buf_size) || (buf_len == 0 && buf_size == 0)) && "Is your input buffer properly zero-terminated?");
4e9e2555 Poul Sander 2025-10-05 10:15 4771
        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.
4e9e2555 Poul Sander 2025-10-05 10:15 4772
        memcpy(state->TextToRevertTo.Data, buf, buf_len + 1);
1193b4be Poul Sander 2025-02-03 21:28 4773
1193b4be Poul Sander 2025-02-03 21:28 4774
        // Preserve cursor position and undo/redo stack if we come back to same widget
1193b4be Poul Sander 2025-02-03 21:28 4775
        // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate?
4e9e2555 Poul Sander 2025-10-05 10:15 4776
        bool recycle_state = (state->ID == id && !init_changed_specs);
4e9e2555 Poul Sander 2025-10-05 10:15 4777
        if (recycle_state && (state->TextLen != buf_len || (state->TextA.Data == NULL || strncmp(state->TextA.Data, buf, buf_len) != 0)))
1193b4be Poul Sander 2025-02-03 21:28 4778
            recycle_state = false;
1193b4be Poul Sander 2025-02-03 21:28 4779
1193b4be Poul Sander 2025-02-03 21:28 4780
        // Start edition
1193b4be Poul Sander 2025-02-03 21:28 4781
        state->ID = id;
4e9e2555 Poul Sander 2025-10-05 10:15 4782
        state->TextLen = buf_len;
4e9e2555 Poul Sander 2025-10-05 10:15 4783
        if (!is_readonly)
4e9e2555 Poul Sander 2025-10-05 10:15 4784
        {
4e9e2555 Poul Sander 2025-10-05 10:15 4785
            state->TextA.resize(buf_size + 1); // we use +1 to make sure that .Data is always pointing to at least an empty string.
4e9e2555 Poul Sander 2025-10-05 10:15 4786
            memcpy(state->TextA.Data, buf, state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 4787
        }
1193b4be Poul Sander 2025-02-03 21:28 4788
4e9e2555 Poul Sander 2025-10-05 10:15 4789
        // Find initial scroll position for right alignment
4e9e2555 Poul Sander 2025-10-05 10:15 4790
        state->Scroll = ImVec2(0.0f, 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 4791
        if (flags & ImGuiInputTextFlags_ElideLeft)
4e9e2555 Poul Sander 2025-10-05 10:15 4792
            state->Scroll.x += ImMax(0.0f, CalcTextSize(buf).x - frame_size.x + style.FramePadding.x * 2.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 4793
4e9e2555 Poul Sander 2025-10-05 10:15 4794
        // Recycle existing cursor/selection/undo stack but clamp position
4e9e2555 Poul Sander 2025-10-05 10:15 4795
        // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
1193b4be Poul Sander 2025-02-03 21:28 4796
        if (recycle_state)
1193b4be Poul Sander 2025-02-03 21:28 4797
            state->CursorClamp();
1193b4be Poul Sander 2025-02-03 21:28 4798
        else
1193b4be Poul Sander 2025-02-03 21:28 4799
            stb_textedit_initialize_state(state->Stb, !is_multiline);
1193b4be Poul Sander 2025-02-03 21:28 4800
4e9e2555 Poul Sander 2025-10-05 10:15 4801
        if (!is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4802
        {
1193b4be Poul Sander 2025-02-03 21:28 4803
            if (flags & ImGuiInputTextFlags_AutoSelectAll)
1193b4be Poul Sander 2025-02-03 21:28 4804
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4805
            if (input_requested_by_nav && (!recycle_state || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState)))
1193b4be Poul Sander 2025-02-03 21:28 4806
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4807
            if (user_clicked && io.KeyCtrl)
1193b4be Poul Sander 2025-02-03 21:28 4808
                select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 4809
        }
1193b4be Poul Sander 2025-02-03 21:28 4810
1193b4be Poul Sander 2025-02-03 21:28 4811
        if (flags & ImGuiInputTextFlags_AlwaysOverwrite)
1193b4be Poul Sander 2025-02-03 21:28 4812
            state->Stb->insert_mode = 1; // stb field name is indeed incorrect (see #2863)
1193b4be Poul Sander 2025-02-03 21:28 4813
    }
1193b4be Poul Sander 2025-02-03 21:28 4814
1193b4be Poul Sander 2025-02-03 21:28 4815
    const bool is_osx = io.ConfigMacOSXBehaviors;
1193b4be Poul Sander 2025-02-03 21:28 4816
    if (g.ActiveId != id && init_make_active)
1193b4be Poul Sander 2025-02-03 21:28 4817
    {
1193b4be Poul Sander 2025-02-03 21:28 4818
        IM_ASSERT(state && state->ID == id);
1193b4be Poul Sander 2025-02-03 21:28 4819
        SetActiveID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 4820
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 4821
        FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 4822
    }
1193b4be Poul Sander 2025-02-03 21:28 4823
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 4824
    {
1193b4be Poul Sander 2025-02-03 21:28 4825
        // Declare some inputs, the other are registered and polled via Shortcut() routing system.
4e9e2555 Poul Sander 2025-10-05 10:15 4826
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 4827
        const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };
1193b4be Poul Sander 2025-02-03 21:28 4828
        for (ImGuiKey key : always_owned_keys)
1193b4be Poul Sander 2025-02-03 21:28 4829
            SetKeyOwner(key, id);
1193b4be Poul Sander 2025-02-03 21:28 4830
        if (user_clicked)
1193b4be Poul Sander 2025-02-03 21:28 4831
            SetKeyOwner(ImGuiKey_MouseLeft, id);
1193b4be Poul Sander 2025-02-03 21:28 4832
        g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 4833
        if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
1193b4be Poul Sander 2025-02-03 21:28 4834
        {
1193b4be Poul Sander 2025-02-03 21:28 4835
            g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
1193b4be Poul Sander 2025-02-03 21:28 4836
            SetKeyOwner(ImGuiKey_UpArrow, id);
1193b4be Poul Sander 2025-02-03 21:28 4837
            SetKeyOwner(ImGuiKey_DownArrow, id);
1193b4be Poul Sander 2025-02-03 21:28 4838
        }
1193b4be Poul Sander 2025-02-03 21:28 4839
        if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4840
        {
1193b4be Poul Sander 2025-02-03 21:28 4841
            SetKeyOwner(ImGuiKey_PageUp, id);
1193b4be Poul Sander 2025-02-03 21:28 4842
            SetKeyOwner(ImGuiKey_PageDown, id);
1193b4be Poul Sander 2025-02-03 21:28 4843
        }
1193b4be Poul Sander 2025-02-03 21:28 4844
        // FIXME: May be a problem to always steal Alt on OSX, would ideally still allow an uninterrupted Alt down-up to toggle menu
1193b4be Poul Sander 2025-02-03 21:28 4845
        if (is_osx)
1193b4be Poul Sander 2025-02-03 21:28 4846
            SetKeyOwner(ImGuiMod_Alt, id);
1193b4be Poul Sander 2025-02-03 21:28 4847
1193b4be Poul Sander 2025-02-03 21:28 4848
        // Expose scroll in a manner that is agnostic to us using a child window
1193b4be Poul Sander 2025-02-03 21:28 4849
        if (is_multiline && state != NULL)
1193b4be Poul Sander 2025-02-03 21:28 4850
            state->Scroll.y = draw_window->Scroll.y;
4e9e2555 Poul Sander 2025-10-05 10:15 4851
4e9e2555 Poul Sander 2025-10-05 10:15 4852
        // Read-only mode always ever read from source buffer. Refresh TextLen when active.
4e9e2555 Poul Sander 2025-10-05 10:15 4853
        if (is_readonly && state != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4854
            state->TextLen = (int)ImStrlen(buf);
4e9e2555 Poul Sander 2025-10-05 10:15 4855
        //if (is_readonly && state != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4856
        //    state->TextA.clear(); // Uncomment to facilitate debugging, but we otherwise prefer to keep/amortize th allocation.
1193b4be Poul Sander 2025-02-03 21:28 4857
    }
4e9e2555 Poul Sander 2025-10-05 10:15 4858
    if (state != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 4859
        state->TextSrc = is_readonly ? buf : state->TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 4860
1193b4be Poul Sander 2025-02-03 21:28 4861
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 4862
    if (g.ActiveId == id && state == NULL)
1193b4be Poul Sander 2025-02-03 21:28 4863
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 4864
1193b4be Poul Sander 2025-02-03 21:28 4865
    // Release focus when we click outside
1193b4be Poul Sander 2025-02-03 21:28 4866
    if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active) //-V560
1193b4be Poul Sander 2025-02-03 21:28 4867
        clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 4868
1193b4be Poul Sander 2025-02-03 21:28 4869
    // Lock the decision of whether we are going to take the path displaying the cursor or selection
1193b4be Poul Sander 2025-02-03 21:28 4870
    bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
1193b4be Poul Sander 2025-02-03 21:28 4871
    bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
1193b4be Poul Sander 2025-02-03 21:28 4872
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 4873
    bool validated = false;
1193b4be Poul Sander 2025-02-03 21:28 4874
1193b4be Poul Sander 2025-02-03 21:28 4875
    // Select the buffer to render.
1193b4be Poul Sander 2025-02-03 21:28 4876
    const bool buf_display_from_state = (render_cursor || render_selection || g.ActiveId == id) && !is_readonly && state;
4e9e2555 Poul Sander 2025-10-05 10:15 4877
    bool is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
1193b4be Poul Sander 2025-02-03 21:28 4878
1193b4be Poul Sander 2025-02-03 21:28 4879
    // Password pushes a temporary font with only a fallback glyph
1193b4be Poul Sander 2025-02-03 21:28 4880
    if (is_password && !is_displaying_hint)
4e9e2555 Poul Sander 2025-10-05 10:15 4881
        PushPasswordFont();
4e9e2555 Poul Sander 2025-10-05 10:15 4882
4e9e2555 Poul Sander 2025-10-05 10:15 4883
    // Word-wrapping: attempt to keep cursor in view while resizing frame/parent
4e9e2555 Poul Sander 2025-10-05 10:15 4884
    // FIXME-WORDWRAP: It would be better to preserve same relative offset.
4e9e2555 Poul Sander 2025-10-05 10:15 4885
    if (is_wordwrap && state != NULL && state->ID == id && state->WrapWidth != wrap_width)
1193b4be Poul Sander 2025-02-03 21:28 4886
    {
4e9e2555 Poul Sander 2025-10-05 10:15 4887
        state->CursorCenterY = true;
4e9e2555 Poul Sander 2025-10-05 10:15 4888
        state->WrapWidth = wrap_width;
4e9e2555 Poul Sander 2025-10-05 10:15 4889
        render_cursor = true;
1193b4be Poul Sander 2025-02-03 21:28 4890
    }
1193b4be Poul Sander 2025-02-03 21:28 4891
1193b4be Poul Sander 2025-02-03 21:28 4892
    // Process mouse inputs and character inputs
1193b4be Poul Sander 2025-02-03 21:28 4893
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 4894
    {
1193b4be Poul Sander 2025-02-03 21:28 4895
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 4896
        state->Edited = false;
4e9e2555 Poul Sander 2025-10-05 10:15 4897
        state->BufCapacity = buf_size;
1193b4be Poul Sander 2025-02-03 21:28 4898
        state->Flags = flags;
4e9e2555 Poul Sander 2025-10-05 10:15 4899
        state->WrapWidth = wrap_width;
1193b4be Poul Sander 2025-02-03 21:28 4900
1193b4be Poul Sander 2025-02-03 21:28 4901
        // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
1193b4be Poul Sander 2025-02-03 21:28 4902
        // Down the line we should have a cleaner library-wide concept of Selected vs Active.
1193b4be Poul Sander 2025-02-03 21:28 4903
        g.ActiveIdAllowOverlap = !io.MouseDown[0];
1193b4be Poul Sander 2025-02-03 21:28 4904
1193b4be Poul Sander 2025-02-03 21:28 4905
        // Edit in progress
1193b4be Poul Sander 2025-02-03 21:28 4906
        const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->Scroll.x;
1193b4be Poul Sander 2025-02-03 21:28 4907
        const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y) : (g.FontSize * 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 4908
1193b4be Poul Sander 2025-02-03 21:28 4909
        if (select_all)
1193b4be Poul Sander 2025-02-03 21:28 4910
        {
1193b4be Poul Sander 2025-02-03 21:28 4911
            state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 4912
            state->SelectedAllMouseLock = true;
1193b4be Poul Sander 2025-02-03 21:28 4913
        }
1193b4be Poul Sander 2025-02-03 21:28 4914
        else if (hovered && io.MouseClickedCount[0] >= 2 && !io.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 4915
        {
1193b4be Poul Sander 2025-02-03 21:28 4916
            stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4917
            const int multiclick_count = (io.MouseClickedCount[0] - 2);
1193b4be Poul Sander 2025-02-03 21:28 4918
            if ((multiclick_count % 2) == 0)
1193b4be Poul Sander 2025-02-03 21:28 4919
            {
1193b4be Poul Sander 2025-02-03 21:28 4920
                // Double-click: Select word
1193b4be Poul Sander 2025-02-03 21:28 4921
                // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant:
1193b4be Poul Sander 2025-02-03 21:28 4922
                // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS)
1193b4be Poul Sander 2025-02-03 21:28 4923
                const bool is_bol = (state->Stb->cursor == 0) || ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor - 1) == '\n';
1193b4be Poul Sander 2025-02-03 21:28 4924
                if (STB_TEXT_HAS_SELECTION(state->Stb) || !is_bol)
1193b4be Poul Sander 2025-02-03 21:28 4925
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
1193b4be Poul Sander 2025-02-03 21:28 4926
                //state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4927
                if (!STB_TEXT_HAS_SELECTION(state->Stb))
1193b4be Poul Sander 2025-02-03 21:28 4928
                    ImStb::stb_textedit_prep_selection_at_cursor(state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 4929
                state->Stb->cursor = ImStb::STB_TEXTEDIT_MOVEWORDRIGHT_MAC(state, state->Stb->cursor);
1193b4be Poul Sander 2025-02-03 21:28 4930
                state->Stb->select_end = state->Stb->cursor;
1193b4be Poul Sander 2025-02-03 21:28 4931
                ImStb::stb_textedit_clamp(state, state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 4932
            }
1193b4be Poul Sander 2025-02-03 21:28 4933
            else
1193b4be Poul Sander 2025-02-03 21:28 4934
            {
1193b4be Poul Sander 2025-02-03 21:28 4935
                // Triple-click: Select line
1193b4be Poul Sander 2025-02-03 21:28 4936
                const bool is_eol = ImStb::STB_TEXTEDIT_GETCHAR(state, state->Stb->cursor) == '\n';
4e9e2555 Poul Sander 2025-10-05 10:15 4937
                state->WrapWidth = 0.0f; // Temporarily disable wrapping so we use real line start.
1193b4be Poul Sander 2025-02-03 21:28 4938
                state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART);
1193b4be Poul Sander 2025-02-03 21:28 4939
                state->OnKeyPressed(STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 4940
                state->OnKeyPressed(STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT);
4e9e2555 Poul Sander 2025-10-05 10:15 4941
                state->WrapWidth = wrap_width;
1193b4be Poul Sander 2025-02-03 21:28 4942
                if (!is_eol && is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 4943
                {
1193b4be Poul Sander 2025-02-03 21:28 4944
                    ImSwap(state->Stb->select_start, state->Stb->select_end);
1193b4be Poul Sander 2025-02-03 21:28 4945
                    state->Stb->cursor = state->Stb->select_end;
1193b4be Poul Sander 2025-02-03 21:28 4946
                }
1193b4be Poul Sander 2025-02-03 21:28 4947
                state->CursorFollow = false;
1193b4be Poul Sander 2025-02-03 21:28 4948
            }
1193b4be Poul Sander 2025-02-03 21:28 4949
            state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4950
        }
1193b4be Poul Sander 2025-02-03 21:28 4951
        else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
1193b4be Poul Sander 2025-02-03 21:28 4952
        {
1193b4be Poul Sander 2025-02-03 21:28 4953
            if (hovered)
1193b4be Poul Sander 2025-02-03 21:28 4954
            {
1193b4be Poul Sander 2025-02-03 21:28 4955
                if (io.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 4956
                    stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4957
                else
1193b4be Poul Sander 2025-02-03 21:28 4958
                    stb_textedit_click(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4959
                state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4960
            }
1193b4be Poul Sander 2025-02-03 21:28 4961
        }
1193b4be Poul Sander 2025-02-03 21:28 4962
        else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 4963
        {
1193b4be Poul Sander 2025-02-03 21:28 4964
            stb_textedit_drag(state, state->Stb, mouse_x, mouse_y);
1193b4be Poul Sander 2025-02-03 21:28 4965
            state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 4966
            state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 4967
        }
1193b4be Poul Sander 2025-02-03 21:28 4968
        if (state->SelectedAllMouseLock && !io.MouseDown[0])
1193b4be Poul Sander 2025-02-03 21:28 4969
            state->SelectedAllMouseLock = false;
1193b4be Poul Sander 2025-02-03 21:28 4970
1193b4be Poul Sander 2025-02-03 21:28 4971
        // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
1193b4be Poul Sander 2025-02-03 21:28 4972
        // (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)
1193b4be Poul Sander 2025-02-03 21:28 4973
        if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 4974
        {
1193b4be Poul Sander 2025-02-03 21:28 4975
            if (Shortcut(ImGuiKey_Tab, ImGuiInputFlags_Repeat, id))
1193b4be Poul Sander 2025-02-03 21:28 4976
            {
1193b4be Poul Sander 2025-02-03 21:28 4977
                unsigned int c = '\t'; // Insert TAB
1193b4be Poul Sander 2025-02-03 21:28 4978
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 4979
                    state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 4980
            }
1193b4be Poul Sander 2025-02-03 21:28 4981
            // FIXME: Implement Shift+Tab
1193b4be Poul Sander 2025-02-03 21:28 4982
            /*
1193b4be Poul Sander 2025-02-03 21:28 4983
            if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, ImGuiInputFlags_Repeat, id))
1193b4be Poul Sander 2025-02-03 21:28 4984
            {
1193b4be Poul Sander 2025-02-03 21:28 4985
            }
1193b4be Poul Sander 2025-02-03 21:28 4986
            */
1193b4be Poul Sander 2025-02-03 21:28 4987
        }
1193b4be Poul Sander 2025-02-03 21:28 4988
1193b4be Poul Sander 2025-02-03 21:28 4989
        // Process regular text input (before we check for Return because using some IME will effectively send a Return?)
1193b4be Poul Sander 2025-02-03 21:28 4990
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 4991
        const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeyCtrl);
1193b4be Poul Sander 2025-02-03 21:28 4992
        if (io.InputQueueCharacters.Size > 0)
1193b4be Poul Sander 2025-02-03 21:28 4993
        {
1193b4be Poul Sander 2025-02-03 21:28 4994
            if (!ignore_char_inputs && !is_readonly && !input_requested_by_nav)
1193b4be Poul Sander 2025-02-03 21:28 4995
                for (int n = 0; n < io.InputQueueCharacters.Size; n++)
1193b4be Poul Sander 2025-02-03 21:28 4996
                {
1193b4be Poul Sander 2025-02-03 21:28 4997
                    // Insert character if they pass filtering
1193b4be Poul Sander 2025-02-03 21:28 4998
                    unsigned int c = (unsigned int)io.InputQueueCharacters[n];
1193b4be Poul Sander 2025-02-03 21:28 4999
                    if (c == '\t') // Skip Tab, see above.
1193b4be Poul Sander 2025-02-03 21:28 5000
                        continue;
1193b4be Poul Sander 2025-02-03 21:28 5001
                    if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 5002
                        state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 5003
                }
1193b4be Poul Sander 2025-02-03 21:28 5004
1193b4be Poul Sander 2025-02-03 21:28 5005
            // Consume characters
1193b4be Poul Sander 2025-02-03 21:28 5006
            io.InputQueueCharacters.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 5007
        }
1193b4be Poul Sander 2025-02-03 21:28 5008
    }
1193b4be Poul Sander 2025-02-03 21:28 5009
1193b4be Poul Sander 2025-02-03 21:28 5010
    // Process other shortcuts/key-presses
1193b4be Poul Sander 2025-02-03 21:28 5011
    bool revert_edit = false;
1193b4be Poul Sander 2025-02-03 21:28 5012
    if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
1193b4be Poul Sander 2025-02-03 21:28 5013
    {
1193b4be Poul Sander 2025-02-03 21:28 5014
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 5015
1193b4be Poul Sander 2025-02-03 21:28 5016
        const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
1193b4be Poul Sander 2025-02-03 21:28 5017
        state->Stb->row_count_per_page = row_count_per_page;
1193b4be Poul Sander 2025-02-03 21:28 5018
1193b4be Poul Sander 2025-02-03 21:28 5019
        const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
1193b4be Poul Sander 2025-02-03 21:28 5020
        const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl;                     // OS X style: Text editing cursor movement using Alt instead of Ctrl
1193b4be Poul Sander 2025-02-03 21:28 5021
        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
1193b4be Poul Sander 2025-02-03 21:28 5022
4e9e2555 Poul Sander 2025-10-05 10:15 5023
        // 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)
1193b4be Poul Sander 2025-02-03 21:28 5024
        // Otherwise we could simply assume that we own the keys as we are active.
1193b4be Poul Sander 2025-02-03 21:28 5025
        const ImGuiInputFlags f_repeat = ImGuiInputFlags_Repeat;
1193b4be Poul Sander 2025-02-03 21:28 5026
        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());
1193b4be Poul Sander 2025-02-03 21:28 5027
        const bool is_copy  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0,        id) || Shortcut(ImGuiMod_Ctrl  | ImGuiKey_Insert, 0,        id)) && !is_password && (!is_multiline || state->HasSelection());
1193b4be Poul Sander 2025-02-03 21:28 5028
        const bool is_paste = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_V, f_repeat, id) || Shortcut(ImGuiMod_Shift | ImGuiKey_Insert, f_repeat, id)) && !is_readonly;
1193b4be Poul Sander 2025-02-03 21:28 5029
        const bool is_undo  = (Shortcut(ImGuiMod_Ctrl | ImGuiKey_Z, f_repeat, id)) && !is_readonly && is_undoable;
4e9e2555 Poul Sander 2025-10-05 10:15 5030
        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;
1193b4be Poul Sander 2025-02-03 21:28 5031
        const bool is_select_all = Shortcut(ImGuiMod_Ctrl | ImGuiKey_A, 0, id);
1193b4be Poul Sander 2025-02-03 21:28 5032
1193b4be Poul Sander 2025-02-03 21:28 5033
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 5034
        const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
1193b4be Poul Sander 2025-02-03 21:28 5035
        const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
1193b4be Poul Sander 2025-02-03 21:28 5036
        const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
1193b4be Poul Sander 2025-02-03 21:28 5037
        const bool is_cancel = Shortcut(ImGuiKey_Escape, f_repeat, id) || (nav_gamepad_active && Shortcut(ImGuiKey_NavGamepadCancel, f_repeat, id));
1193b4be Poul Sander 2025-02-03 21:28 5038
1193b4be Poul Sander 2025-02-03 21:28 5039
        // FIXME: Should use more Shortcut() and reduce IsKeyPressed()+SetKeyOwner(), but requires modifiers combination to be taken account of.
1193b4be Poul Sander 2025-02-03 21:28 5040
        // FIXME-OSX: Missing support for Alt(option)+Right/Left = go to end of line, or next line if already in end of line.
1193b4be Poul Sander 2025-02-03 21:28 5041
        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); }
1193b4be Poul Sander 2025-02-03 21:28 5042
        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); }
1193b4be Poul Sander 2025-02-03 21:28 5043
        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); }
1193b4be Poul Sander 2025-02-03 21:28 5044
        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); }
1193b4be Poul Sander 2025-02-03 21:28 5045
        else if (IsKeyPressed(ImGuiKey_PageUp) && is_multiline)      { state->OnKeyPressed(STB_TEXTEDIT_K_PGUP | k_mask); scroll_y -= row_count_per_page * g.FontSize; }
1193b4be Poul Sander 2025-02-03 21:28 5046
        else if (IsKeyPressed(ImGuiKey_PageDown) && is_multiline)    { state->OnKeyPressed(STB_TEXTEDIT_K_PGDOWN | k_mask); scroll_y += row_count_per_page * g.FontSize; }
1193b4be Poul Sander 2025-02-03 21:28 5047
        else if (IsKeyPressed(ImGuiKey_Home))                        { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
1193b4be Poul Sander 2025-02-03 21:28 5048
        else if (IsKeyPressed(ImGuiKey_End))                         { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
1193b4be Poul Sander 2025-02-03 21:28 5049
        else if (IsKeyPressed(ImGuiKey_Delete) && !is_readonly && !is_cut)
1193b4be Poul Sander 2025-02-03 21:28 5050
        {
1193b4be Poul Sander 2025-02-03 21:28 5051
            if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 5052
            {
1193b4be Poul Sander 2025-02-03 21:28 5053
                // 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)
1193b4be Poul Sander 2025-02-03 21:28 5054
                if (is_wordmove_key_down)
1193b4be Poul Sander 2025-02-03 21:28 5055
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 5056
            }
1193b4be Poul Sander 2025-02-03 21:28 5057
            state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
1193b4be Poul Sander 2025-02-03 21:28 5058
        }
1193b4be Poul Sander 2025-02-03 21:28 5059
        else if (IsKeyPressed(ImGuiKey_Backspace) && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5060
        {
1193b4be Poul Sander 2025-02-03 21:28 5061
            if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 5062
            {
1193b4be Poul Sander 2025-02-03 21:28 5063
                if (is_wordmove_key_down)
1193b4be Poul Sander 2025-02-03 21:28 5064
                    state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 5065
                else if (is_osx && io.KeyCtrl && !io.KeyAlt && !io.KeySuper)
1193b4be Poul Sander 2025-02-03 21:28 5066
                    state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
1193b4be Poul Sander 2025-02-03 21:28 5067
            }
1193b4be Poul Sander 2025-02-03 21:28 5068
            state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
1193b4be Poul Sander 2025-02-03 21:28 5069
        }
1193b4be Poul Sander 2025-02-03 21:28 5070
        else if (is_enter_pressed || is_gamepad_validate)
1193b4be Poul Sander 2025-02-03 21:28 5071
        {
1193b4be Poul Sander 2025-02-03 21:28 5072
            // Determine if we turn Enter into a \n character
1193b4be Poul Sander 2025-02-03 21:28 5073
            bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
4e9e2555 Poul Sander 2025-10-05 10:15 5074
            if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line != io.KeyCtrl))
1193b4be Poul Sander 2025-02-03 21:28 5075
            {
1193b4be Poul Sander 2025-02-03 21:28 5076
                validated = true;
1193b4be Poul Sander 2025-02-03 21:28 5077
                if (io.ConfigInputTextEnterKeepActive && !is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5078
                    state->SelectAll(); // No need to scroll
1193b4be Poul Sander 2025-02-03 21:28 5079
                else
1193b4be Poul Sander 2025-02-03 21:28 5080
                    clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 5081
            }
1193b4be Poul Sander 2025-02-03 21:28 5082
            else if (!is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5083
            {
1193b4be Poul Sander 2025-02-03 21:28 5084
                unsigned int c = '\n'; // Insert new line
1193b4be Poul Sander 2025-02-03 21:28 5085
                if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data))
1193b4be Poul Sander 2025-02-03 21:28 5086
                    state->OnCharPressed(c);
1193b4be Poul Sander 2025-02-03 21:28 5087
            }
1193b4be Poul Sander 2025-02-03 21:28 5088
        }
1193b4be Poul Sander 2025-02-03 21:28 5089
        else if (is_cancel)
1193b4be Poul Sander 2025-02-03 21:28 5090
        {
1193b4be Poul Sander 2025-02-03 21:28 5091
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
1193b4be Poul Sander 2025-02-03 21:28 5092
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5093
                if (state->TextA.Data[0] != 0)
1193b4be Poul Sander 2025-02-03 21:28 5094
                {
1193b4be Poul Sander 2025-02-03 21:28 5095
                    revert_edit = true;
1193b4be Poul Sander 2025-02-03 21:28 5096
                }
1193b4be Poul Sander 2025-02-03 21:28 5097
                else
1193b4be Poul Sander 2025-02-03 21:28 5098
                {
1193b4be Poul Sander 2025-02-03 21:28 5099
                    render_cursor = render_selection = false;
1193b4be Poul Sander 2025-02-03 21:28 5100
                    clear_active_id = true;
1193b4be Poul Sander 2025-02-03 21:28 5101
                }
1193b4be Poul Sander 2025-02-03 21:28 5102
            }
1193b4be Poul Sander 2025-02-03 21:28 5103
            else
1193b4be Poul Sander 2025-02-03 21:28 5104
            {
1193b4be Poul Sander 2025-02-03 21:28 5105
                clear_active_id = revert_edit = true;
1193b4be Poul Sander 2025-02-03 21:28 5106
                render_cursor = render_selection = false;
1193b4be Poul Sander 2025-02-03 21:28 5107
            }
1193b4be Poul Sander 2025-02-03 21:28 5108
        }
1193b4be Poul Sander 2025-02-03 21:28 5109
        else if (is_undo || is_redo)
1193b4be Poul Sander 2025-02-03 21:28 5110
        {
1193b4be Poul Sander 2025-02-03 21:28 5111
            state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
1193b4be Poul Sander 2025-02-03 21:28 5112
            state->ClearSelection();
1193b4be Poul Sander 2025-02-03 21:28 5113
        }
1193b4be Poul Sander 2025-02-03 21:28 5114
        else if (is_select_all)
1193b4be Poul Sander 2025-02-03 21:28 5115
        {
1193b4be Poul Sander 2025-02-03 21:28 5116
            state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 5117
            state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 5118
        }
1193b4be Poul Sander 2025-02-03 21:28 5119
        else if (is_cut || is_copy)
1193b4be Poul Sander 2025-02-03 21:28 5120
        {
1193b4be Poul Sander 2025-02-03 21:28 5121
            // Cut, Copy
1193b4be Poul Sander 2025-02-03 21:28 5122
            if (g.PlatformIO.Platform_SetClipboardTextFn != NULL)
1193b4be Poul Sander 2025-02-03 21:28 5123
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5124
                // SetClipboardText() only takes null terminated strings + state->TextSrc may point to read-only user buffer, so we need to make a copy.
1193b4be Poul Sander 2025-02-03 21:28 5125
                const int ib = state->HasSelection() ? ImMin(state->Stb->select_start, state->Stb->select_end) : 0;
4e9e2555 Poul Sander 2025-10-05 10:15 5126
                const int ie = state->HasSelection() ? ImMax(state->Stb->select_start, state->Stb->select_end) : state->TextLen;
4e9e2555 Poul Sander 2025-10-05 10:15 5127
                g.TempBuffer.reserve(ie - ib + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 5128
                memcpy(g.TempBuffer.Data, state->TextSrc + ib, ie - ib);
4e9e2555 Poul Sander 2025-10-05 10:15 5129
                g.TempBuffer.Data[ie - ib] = 0;
4e9e2555 Poul Sander 2025-10-05 10:15 5130
                SetClipboardText(g.TempBuffer.Data);
1193b4be Poul Sander 2025-02-03 21:28 5131
            }
1193b4be Poul Sander 2025-02-03 21:28 5132
            if (is_cut)
1193b4be Poul Sander 2025-02-03 21:28 5133
            {
1193b4be Poul Sander 2025-02-03 21:28 5134
                if (!state->HasSelection())
1193b4be Poul Sander 2025-02-03 21:28 5135
                    state->SelectAll();
1193b4be Poul Sander 2025-02-03 21:28 5136
                state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 5137
                stb_textedit_cut(state, state->Stb);
1193b4be Poul Sander 2025-02-03 21:28 5138
            }
1193b4be Poul Sander 2025-02-03 21:28 5139
        }
1193b4be Poul Sander 2025-02-03 21:28 5140
        else if (is_paste)
1193b4be Poul Sander 2025-02-03 21:28 5141
        {
1193b4be Poul Sander 2025-02-03 21:28 5142
            if (const char* clipboard = GetClipboardText())
1193b4be Poul Sander 2025-02-03 21:28 5143
            {
1193b4be Poul Sander 2025-02-03 21:28 5144
                // Filter pasted buffer
4e9e2555 Poul Sander 2025-10-05 10:15 5145
                const int clipboard_len = (int)ImStrlen(clipboard);
4e9e2555 Poul Sander 2025-10-05 10:15 5146
                ImVector<char> clipboard_filtered;
4e9e2555 Poul Sander 2025-10-05 10:15 5147
                clipboard_filtered.reserve(clipboard_len + 1);
1193b4be Poul Sander 2025-02-03 21:28 5148
                for (const char* s = clipboard; *s != 0; )
1193b4be Poul Sander 2025-02-03 21:28 5149
                {
1193b4be Poul Sander 2025-02-03 21:28 5150
                    unsigned int c;
4e9e2555 Poul Sander 2025-10-05 10:15 5151
                    int in_len = ImTextCharFromUtf8(&c, s, NULL);
4e9e2555 Poul Sander 2025-10-05 10:15 5152
                    s += in_len;
1193b4be Poul Sander 2025-02-03 21:28 5153
                    if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true))
1193b4be Poul Sander 2025-02-03 21:28 5154
                        continue;
4e9e2555 Poul Sander 2025-10-05 10:15 5155
                    char c_utf8[5];
4e9e2555 Poul Sander 2025-10-05 10:15 5156
                    ImTextCharToUtf8(c_utf8, c);
4e9e2555 Poul Sander 2025-10-05 10:15 5157
                    int out_len = (int)ImStrlen(c_utf8);
4e9e2555 Poul Sander 2025-10-05 10:15 5158
                    clipboard_filtered.resize(clipboard_filtered.Size + out_len);
4e9e2555 Poul Sander 2025-10-05 10:15 5159
                    memcpy(clipboard_filtered.Data + clipboard_filtered.Size - out_len, c_utf8, out_len);
1193b4be Poul Sander 2025-02-03 21:28 5160
                }
4e9e2555 Poul Sander 2025-10-05 10:15 5161
                if (clipboard_filtered.Size > 0) // If everything was filtered, ignore the pasting operation
1193b4be Poul Sander 2025-02-03 21:28 5162
                {
4e9e2555 Poul Sander 2025-10-05 10:15 5163
                    clipboard_filtered.push_back(0);
4e9e2555 Poul Sander 2025-10-05 10:15 5164
                    stb_textedit_paste(state, state->Stb, clipboard_filtered.Data, clipboard_filtered.Size - 1);
1193b4be Poul Sander 2025-02-03 21:28 5165
                    state->CursorFollow = true;
1193b4be Poul Sander 2025-02-03 21:28 5166
                }
1193b4be Poul Sander 2025-02-03 21:28 5167
            }
1193b4be Poul Sander 2025-02-03 21:28 5168
        }
1193b4be Poul Sander 2025-02-03 21:28 5169
1193b4be Poul Sander 2025-02-03 21:28 5170
        // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame.
1193b4be Poul Sander 2025-02-03 21:28 5171
        render_selection |= state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
1193b4be Poul Sander 2025-02-03 21:28 5172
    }
1193b4be Poul Sander 2025-02-03 21:28 5173
1193b4be Poul Sander 2025-02-03 21:28 5174
    // Process callbacks and apply result back to user's buffer.
1193b4be Poul Sander 2025-02-03 21:28 5175
    const char* apply_new_text = NULL;
1193b4be Poul Sander 2025-02-03 21:28 5176
    int apply_new_text_length = 0;
1193b4be Poul Sander 2025-02-03 21:28 5177
    if (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5178
    {
1193b4be Poul Sander 2025-02-03 21:28 5179
        IM_ASSERT(state != NULL);
1193b4be Poul Sander 2025-02-03 21:28 5180
        if (revert_edit && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 5181
        {
1193b4be Poul Sander 2025-02-03 21:28 5182
            if (flags & ImGuiInputTextFlags_EscapeClearsAll)
1193b4be Poul Sander 2025-02-03 21:28 5183
            {
1193b4be Poul Sander 2025-02-03 21:28 5184
                // Clear input
4e9e2555 Poul Sander 2025-10-05 10:15 5185
                IM_ASSERT(state->TextA.Data[0] != 0);
1193b4be Poul Sander 2025-02-03 21:28 5186
                apply_new_text = "";
1193b4be Poul Sander 2025-02-03 21:28 5187
                apply_new_text_length = 0;
1193b4be Poul Sander 2025-02-03 21:28 5188
                value_changed = true;
4e9e2555 Poul Sander 2025-10-05 10:15 5189
                char empty_string = 0;
1193b4be Poul Sander 2025-02-03 21:28 5190
                stb_textedit_replace(state, state->Stb, &empty_string, 0);
1193b4be Poul Sander 2025-02-03 21:28 5191
            }
4e9e2555 Poul Sander 2025-10-05 10:15 5192
            else if (strcmp(state->TextA.Data, state->TextToRevertTo.Data) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5193
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5194
                apply_new_text = state->TextToRevertTo.Data;
4e9e2555 Poul Sander 2025-10-05 10:15 5195
                apply_new_text_length = state->TextToRevertTo.Size - 1;
1193b4be Poul Sander 2025-02-03 21:28 5196
1193b4be Poul Sander 2025-02-03 21:28 5197
                // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
1193b4be Poul Sander 2025-02-03 21:28 5198
                // Push records into the undo stack so we can CTRL+Z the revert operation itself
1193b4be Poul Sander 2025-02-03 21:28 5199
                value_changed = true;
4e9e2555 Poul Sander 2025-10-05 10:15 5200
                stb_textedit_replace(state, state->Stb, state->TextToRevertTo.Data, state->TextToRevertTo.Size - 1);
1193b4be Poul Sander 2025-02-03 21:28 5201
            }
1193b4be Poul Sander 2025-02-03 21:28 5202
        }
1193b4be Poul Sander 2025-02-03 21:28 5203
4e9e2555 Poul Sander 2025-10-05 10:15 5204
        // FIXME-OPT: We always reapply the live buffer back to the input buffer before clearing ActiveId,
4e9e2555 Poul Sander 2025-10-05 10:15 5205
        // even though strictly speaking it wasn't modified on this frame. Should mark dirty state from the stb_textedit callbacks.
4e9e2555 Poul Sander 2025-10-05 10:15 5206
        // If we do that, need to ensure that as special case, 'validated == true' also writes back.
4e9e2555 Poul Sander 2025-10-05 10:15 5207
        // This also allows the user to use InputText() without maintaining any user-side storage.
1193b4be Poul Sander 2025-02-03 21:28 5208
        // (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object
1193b4be Poul Sander 2025-02-03 21:28 5209
        // unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
4e9e2555 Poul Sander 2025-10-05 10:15 5210
        const bool apply_edit_back_to_user_buffer = true;// !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
1193b4be Poul Sander 2025-02-03 21:28 5211
        if (apply_edit_back_to_user_buffer)
1193b4be Poul Sander 2025-02-03 21:28 5212
        {
4e9e2555 Poul Sander 2025-10-05 10:15 5213
            // Apply current edited text immediately.
1193b4be Poul Sander 2025-02-03 21:28 5214
            // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
1193b4be Poul Sander 2025-02-03 21:28 5215
1193b4be Poul Sander 2025-02-03 21:28 5216
            // User callback
1193b4be Poul Sander 2025-02-03 21:28 5217
            if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5218
            {
1193b4be Poul Sander 2025-02-03 21:28 5219
                IM_ASSERT(callback != NULL);
1193b4be Poul Sander 2025-02-03 21:28 5220
1193b4be Poul Sander 2025-02-03 21:28 5221
                // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
1193b4be Poul Sander 2025-02-03 21:28 5222
                ImGuiInputTextFlags event_flag = 0;
1193b4be Poul Sander 2025-02-03 21:28 5223
                ImGuiKey event_key = ImGuiKey_None;
1193b4be Poul Sander 2025-02-03 21:28 5224
                if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, 0, id))
1193b4be Poul Sander 2025-02-03 21:28 5225
                {
1193b4be Poul Sander 2025-02-03 21:28 5226
                    event_flag = ImGuiInputTextFlags_CallbackCompletion;
1193b4be Poul Sander 2025-02-03 21:28 5227
                    event_key = ImGuiKey_Tab;
1193b4be Poul Sander 2025-02-03 21:28 5228
                }
1193b4be Poul Sander 2025-02-03 21:28 5229
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow))
1193b4be Poul Sander 2025-02-03 21:28 5230
                {
1193b4be Poul Sander 2025-02-03 21:28 5231
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
1193b4be Poul Sander 2025-02-03 21:28 5232
                    event_key = ImGuiKey_UpArrow;
1193b4be Poul Sander 2025-02-03 21:28 5233
                }
1193b4be Poul Sander 2025-02-03 21:28 5234
                else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow))
1193b4be Poul Sander 2025-02-03 21:28 5235
                {
1193b4be Poul Sander 2025-02-03 21:28 5236
                    event_flag = ImGuiInputTextFlags_CallbackHistory;
1193b4be Poul Sander 2025-02-03 21:28 5237
                    event_key = ImGuiKey_DownArrow;
1193b4be Poul Sander 2025-02-03 21:28 5238
                }
1193b4be Poul Sander 2025-02-03 21:28 5239
                else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited)
1193b4be Poul Sander 2025-02-03 21:28 5240
                {
1193b4be Poul Sander 2025-02-03 21:28 5241
                    event_flag = ImGuiInputTextFlags_CallbackEdit;
1193b4be Poul Sander 2025-02-03 21:28 5242
                }
1193b4be Poul Sander 2025-02-03 21:28 5243
                else if (flags & ImGuiInputTextFlags_CallbackAlways)
1193b4be Poul Sander 2025-02-03 21:28 5244
                {
1193b4be Poul Sander 2025-02-03 21:28 5245
                    event_flag = ImGuiInputTextFlags_CallbackAlways;
1193b4be Poul Sander 2025-02-03 21:28 5246
                }
1193b4be Poul Sander 2025-02-03 21:28 5247
1193b4be Poul Sander 2025-02-03 21:28 5248
                if (event_flag)
1193b4be Poul Sander 2025-02-03 21:28 5249
                {
1193b4be Poul Sander 2025-02-03 21:28 5250
                    ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 5251
                    callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 5252
                    callback_data.EventFlag = event_flag;
1193b4be Poul Sander 2025-02-03 21:28 5253
                    callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 5254
                    callback_data.UserData = callback_user_data;
1193b4be Poul Sander 2025-02-03 21:28 5255
1193b4be Poul Sander 2025-02-03 21:28 5256
                    // FIXME-OPT: Undo stack reconcile needs a backup of the data until we rework API, see #7925
1193b4be Poul Sander 2025-02-03 21:28 5257
                    char* callback_buf = is_readonly ? buf : state->TextA.Data;
4e9e2555 Poul Sander 2025-10-05 10:15 5258
                    IM_ASSERT(callback_buf == state->TextSrc);
4e9e2555 Poul Sander 2025-10-05 10:15 5259
                    state->CallbackTextBackup.resize(state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 5260
                    memcpy(state->CallbackTextBackup.Data, callback_buf, state->TextLen + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 5261
1193b4be Poul Sander 2025-02-03 21:28 5262
                    callback_data.EventKey = event_key;
1193b4be Poul Sander 2025-02-03 21:28 5263
                    callback_data.Buf = callback_buf;
4e9e2555 Poul Sander 2025-10-05 10:15 5264
                    callback_data.BufTextLen = state->TextLen;
4e9e2555 Poul Sander 2025-10-05 10:15 5265
                    callback_data.BufSize = state->BufCapacity;
1193b4be Poul Sander 2025-02-03 21:28 5266
                    callback_data.BufDirty = false;
1193b4be Poul Sander 2025-02-03 21:28 5267
1193b4be Poul Sander 2025-02-03 21:28 5268
                    const int utf8_cursor_pos = callback_data.CursorPos = state->Stb->cursor;
1193b4be Poul Sander 2025-02-03 21:28 5269
                    const int utf8_selection_start = callback_data.SelectionStart = state->Stb->select_start;
1193b4be Poul Sander 2025-02-03 21:28 5270
                    const int utf8_selection_end = callback_data.SelectionEnd = state->Stb->select_end;
1193b4be Poul Sander 2025-02-03 21:28 5271
1193b4be Poul Sander 2025-02-03 21:28 5272
                    // Call user code
1193b4be Poul Sander 2025-02-03 21:28 5273
                    callback(&callback_data);
1193b4be Poul Sander 2025-02-03 21:28 5274
1193b4be Poul Sander 2025-02-03 21:28 5275
                    // Read back what user may have modified
1193b4be Poul Sander 2025-02-03 21:28 5276
                    callback_buf = is_readonly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback
1193b4be Poul Sander 2025-02-03 21:28 5277
                    IM_ASSERT(callback_data.Buf == callback_buf);         // Invalid to modify those fields
4e9e2555 Poul Sander 2025-10-05 10:15 5278
                    IM_ASSERT(callback_data.BufSize == state->BufCapacity);
1193b4be Poul Sander 2025-02-03 21:28 5279
                    IM_ASSERT(callback_data.Flags == flags);
1193b4be Poul Sander 2025-02-03 21:28 5280
                    const bool buf_dirty = callback_data.BufDirty;
1193b4be Poul Sander 2025-02-03 21:28 5281
                    if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty)            { state->Stb->cursor = callback_data.CursorPos; state->CursorFollow = true; }
1193b4be Poul Sander 2025-02-03 21:28 5282
                    if (callback_data.SelectionStart != utf8_selection_start || buf_dirty)  { state->Stb->select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? state->Stb->cursor : callback_data.SelectionStart; }
1193b4be Poul Sander 2025-02-03 21:28 5283
                    if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty)      { state->Stb->select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb->select_start : callback_data.SelectionEnd; }
1193b4be Poul Sander 2025-02-03 21:28 5284
                    if (buf_dirty)
1193b4be Poul Sander 2025-02-03 21:28 5285
                    {
1193b4be Poul Sander 2025-02-03 21:28 5286
                        // Callback may update buffer and thus set buf_dirty even in read-only mode.
4e9e2555 Poul Sander 2025-10-05 10:15 5287
                        IM_ASSERT(callback_data.BufTextLen == (int)ImStrlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
4e9e2555 Poul Sander 2025-10-05 10:15 5288
                        InputTextReconcileUndoState(state, state->CallbackTextBackup.Data, state->CallbackTextBackup.Size - 1, callback_data.Buf, callback_data.BufTextLen);
4e9e2555 Poul Sander 2025-10-05 10:15 5289
                        state->TextLen = callback_data.BufTextLen;  // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
1193b4be Poul Sander 2025-02-03 21:28 5290
                        state->CursorAnimReset();
1193b4be Poul Sander 2025-02-03 21:28 5291
                    }
1193b4be Poul Sander 2025-02-03 21:28 5292
                }
1193b4be Poul Sander 2025-02-03 21:28 5293
            }
1193b4be Poul Sander 2025-02-03 21:28 5294
1193b4be Poul Sander 2025-02-03 21:28 5295
            // Will copy result string if modified
4e9e2555 Poul Sander 2025-10-05 10:15 5296
            if (!is_readonly && strcmp(state->TextSrc, buf) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5297
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5298
                apply_new_text = state->TextSrc;
4e9e2555 Poul Sander 2025-10-05 10:15 5299
                apply_new_text_length = state->TextLen;
1193b4be Poul Sander 2025-02-03 21:28 5300
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5301
            }
1193b4be Poul Sander 2025-02-03 21:28 5302
        }
1193b4be Poul Sander 2025-02-03 21:28 5303
    }
1193b4be Poul Sander 2025-02-03 21:28 5304
1193b4be Poul Sander 2025-02-03 21:28 5305
    // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details)
1193b4be Poul Sander 2025-02-03 21:28 5306
    if (g.InputTextDeactivatedState.ID == id)
1193b4be Poul Sander 2025-02-03 21:28 5307
    {
1193b4be Poul Sander 2025-02-03 21:28 5308
        if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly && strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5309
        {
1193b4be Poul Sander 2025-02-03 21:28 5310
            apply_new_text = g.InputTextDeactivatedState.TextA.Data;
1193b4be Poul Sander 2025-02-03 21:28 5311
            apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1;
1193b4be Poul Sander 2025-02-03 21:28 5312
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5313
            //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text);
1193b4be Poul Sander 2025-02-03 21:28 5314
        }
1193b4be Poul Sander 2025-02-03 21:28 5315
        g.InputTextDeactivatedState.ID = 0;
1193b4be Poul Sander 2025-02-03 21:28 5316
    }
1193b4be Poul Sander 2025-02-03 21:28 5317
1193b4be Poul Sander 2025-02-03 21:28 5318
    // Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5319
    if (apply_new_text != NULL)
1193b4be Poul Sander 2025-02-03 21:28 5320
    {
1193b4be Poul Sander 2025-02-03 21:28 5321
        //// We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size
1193b4be Poul Sander 2025-02-03 21:28 5322
        //// of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used
1193b4be Poul Sander 2025-02-03 21:28 5323
        //// without any storage on user's side.
1193b4be Poul Sander 2025-02-03 21:28 5324
        IM_ASSERT(apply_new_text_length >= 0);
1193b4be Poul Sander 2025-02-03 21:28 5325
        if (is_resizable)
1193b4be Poul Sander 2025-02-03 21:28 5326
        {
1193b4be Poul Sander 2025-02-03 21:28 5327
            ImGuiInputTextCallbackData callback_data;
1193b4be Poul Sander 2025-02-03 21:28 5328
            callback_data.Ctx = &g;
1193b4be Poul Sander 2025-02-03 21:28 5329
            callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize;
1193b4be Poul Sander 2025-02-03 21:28 5330
            callback_data.Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 5331
            callback_data.Buf = buf;
1193b4be Poul Sander 2025-02-03 21:28 5332
            callback_data.BufTextLen = apply_new_text_length;
1193b4be Poul Sander 2025-02-03 21:28 5333
            callback_data.BufSize = ImMax(buf_size, apply_new_text_length + 1);
1193b4be Poul Sander 2025-02-03 21:28 5334
            callback_data.UserData = callback_user_data;
1193b4be Poul Sander 2025-02-03 21:28 5335
            callback(&callback_data);
1193b4be Poul Sander 2025-02-03 21:28 5336
            buf = callback_data.Buf;
1193b4be Poul Sander 2025-02-03 21:28 5337
            buf_size = callback_data.BufSize;
1193b4be Poul Sander 2025-02-03 21:28 5338
            apply_new_text_length = ImMin(callback_data.BufTextLen, buf_size - 1);
1193b4be Poul Sander 2025-02-03 21:28 5339
            IM_ASSERT(apply_new_text_length <= buf_size);
1193b4be Poul Sander 2025-02-03 21:28 5340
        }
1193b4be Poul Sander 2025-02-03 21:28 5341
        //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length);
1193b4be Poul Sander 2025-02-03 21:28 5342
1193b4be Poul Sander 2025-02-03 21:28 5343
        // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size.
1193b4be Poul Sander 2025-02-03 21:28 5344
        ImStrncpy(buf, apply_new_text, ImMin(apply_new_text_length + 1, buf_size));
1193b4be Poul Sander 2025-02-03 21:28 5345
    }
1193b4be Poul Sander 2025-02-03 21:28 5346
1193b4be Poul Sander 2025-02-03 21:28 5347
    // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
1193b4be Poul Sander 2025-02-03 21:28 5348
    // Otherwise request text input ahead for next frame.
1193b4be Poul Sander 2025-02-03 21:28 5349
    if (g.ActiveId == id && clear_active_id)
1193b4be Poul Sander 2025-02-03 21:28 5350
        ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 5351
1193b4be Poul Sander 2025-02-03 21:28 5352
    // Render frame
1193b4be Poul Sander 2025-02-03 21:28 5353
    if (!is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5354
    {
1193b4be Poul Sander 2025-02-03 21:28 5355
        RenderNavCursor(frame_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 5356
        RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 5357
    }
1193b4be Poul Sander 2025-02-03 21:28 5358
1193b4be Poul Sander 2025-02-03 21:28 5359
    ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 5360
    ImVec2 text_size(0.0f, 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 5361
    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
4e9e2555 Poul Sander 2025-10-05 10:15 5362
    if (is_multiline)
4e9e2555 Poul Sander 2025-10-05 10:15 5363
        clip_rect.ClipWith(draw_window->ClipRect);
1193b4be Poul Sander 2025-02-03 21:28 5364
1193b4be Poul Sander 2025-02-03 21:28 5365
    // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line
1193b4be Poul Sander 2025-02-03 21:28 5366
    // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
1193b4be Poul Sander 2025-02-03 21:28 5367
    // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash.
1193b4be Poul Sander 2025-02-03 21:28 5368
    const int buf_display_max_length = 2 * 1024 * 1024;
1193b4be Poul Sander 2025-02-03 21:28 5369
    const char* buf_display = buf_display_from_state ? state->TextA.Data : buf; //-V595
1193b4be Poul Sander 2025-02-03 21:28 5370
    const char* buf_display_end = NULL; // We have specialized paths below for setting the length
4e9e2555 Poul Sander 2025-10-05 10:15 5371
4e9e2555 Poul Sander 2025-10-05 10:15 5372
    // Display hint when contents is empty
4e9e2555 Poul Sander 2025-10-05 10:15 5373
    // At this point we need to handle the possibility that a callback could have modified the underlying buffer (#8368)
4e9e2555 Poul Sander 2025-10-05 10:15 5374
    const bool new_is_displaying_hint = (hint != NULL && (buf_display_from_state ? state->TextA.Data : buf)[0] == 0);
4e9e2555 Poul Sander 2025-10-05 10:15 5375
    if (new_is_displaying_hint != is_displaying_hint)
4e9e2555 Poul Sander 2025-10-05 10:15 5376
    {
4e9e2555 Poul Sander 2025-10-05 10:15 5377
        if (is_password && !is_displaying_hint)
4e9e2555 Poul Sander 2025-10-05 10:15 5378
            PopPasswordFont();
4e9e2555 Poul Sander 2025-10-05 10:15 5379
        is_displaying_hint = new_is_displaying_hint;
4e9e2555 Poul Sander 2025-10-05 10:15 5380
        if (is_password && !is_displaying_hint)
4e9e2555 Poul Sander 2025-10-05 10:15 5381
            PushPasswordFont();
4e9e2555 Poul Sander 2025-10-05 10:15 5382
    }
1193b4be Poul Sander 2025-02-03 21:28 5383
    if (is_displaying_hint)
1193b4be Poul Sander 2025-02-03 21:28 5384
    {
1193b4be Poul Sander 2025-02-03 21:28 5385
        buf_display = hint;
4e9e2555 Poul Sander 2025-10-05 10:15 5386
        buf_display_end = hint + ImStrlen(hint);
4e9e2555 Poul Sander 2025-10-05 10:15 5387
    }
4e9e2555 Poul Sander 2025-10-05 10:15 5388
    else
4e9e2555 Poul Sander 2025-10-05 10:15 5389
    {
4e9e2555 Poul Sander 2025-10-05 10:15 5390
        if (render_cursor || render_selection || g.ActiveId == id)
4e9e2555 Poul Sander 2025-10-05 10:15 5391
            buf_display_end = buf_display + state->TextLen; //-V595
4e9e2555 Poul Sander 2025-10-05 10:15 5392
        else if (is_multiline && !is_wordwrap)
4e9e2555 Poul Sander 2025-10-05 10:15 5393
            buf_display_end = NULL; // Inactive multi-line: end of buffer will be output by InputTextLineIndexBuild() special strchr() path.
4e9e2555 Poul Sander 2025-10-05 10:15 5394
        else
4e9e2555 Poul Sander 2025-10-05 10:15 5395
            buf_display_end = buf_display + ImStrlen(buf_display);
1193b4be Poul Sander 2025-02-03 21:28 5396
    }
1193b4be Poul Sander 2025-02-03 21:28 5397
4e9e2555 Poul Sander 2025-10-05 10:15 5398
    // Calculate visibility
4e9e2555 Poul Sander 2025-10-05 10:15 5399
    int line_visible_n0 = 0, line_visible_n1 = 1;
4e9e2555 Poul Sander 2025-10-05 10:15 5400
    if (is_multiline)
4e9e2555 Poul Sander 2025-10-05 10:15 5401
        CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
4e9e2555 Poul Sander 2025-10-05 10:15 5402
4e9e2555 Poul Sander 2025-10-05 10:15 5403
    // Build line index for easy data access (makes code below simpler and faster)
4e9e2555 Poul Sander 2025-10-05 10:15 5404
    ImGuiTextIndex* line_index = &g.InputTextLineIndex;
4e9e2555 Poul Sander 2025-10-05 10:15 5405
    line_index->Offsets.resize(0);
4e9e2555 Poul Sander 2025-10-05 10:15 5406
    int line_count = 1;
4e9e2555 Poul Sander 2025-10-05 10:15 5407
    if (is_multiline)
4e9e2555 Poul Sander 2025-10-05 10:15 5408
        line_count = InputTextLineIndexBuild(flags, line_index, buf_display, buf_display_end, wrap_width, (render_cursor && state && state->CursorFollow) ? INT_MAX : line_visible_n1 + 1, buf_display_end ? NULL : &buf_display_end);
4e9e2555 Poul Sander 2025-10-05 10:15 5409
    line_index->EndOffset = (int)(buf_display_end - buf_display);
4e9e2555 Poul Sander 2025-10-05 10:15 5410
    line_visible_n1 = ImMin(line_visible_n1, line_count);
4e9e2555 Poul Sander 2025-10-05 10:15 5411
4e9e2555 Poul Sander 2025-10-05 10:15 5412
    // Store text height (we don't need width)
4e9e2555 Poul Sander 2025-10-05 10:15 5413
    text_size = ImVec2(inner_size.x, line_count * g.FontSize);
4e9e2555 Poul Sander 2025-10-05 10:15 5414
    //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));
4e9e2555 Poul Sander 2025-10-05 10:15 5415
4e9e2555 Poul Sander 2025-10-05 10:15 5416
    // Calculate blinking cursor position
4e9e2555 Poul Sander 2025-10-05 10:15 5417
    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);
4e9e2555 Poul Sander 2025-10-05 10:15 5418
    ImVec2 draw_scroll;
4e9e2555 Poul Sander 2025-10-05 10:15 5419
1193b4be Poul Sander 2025-02-03 21:28 5420
    // Render text. We currently only render selection when the widget is active or while scrolling.
4e9e2555 Poul Sander 2025-10-05 10:15 5421
    const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 5422
    if (render_cursor || render_selection)
1193b4be Poul Sander 2025-02-03 21:28 5423
    {
1193b4be Poul Sander 2025-02-03 21:28 5424
        // Render text (with cursor and selection)
1193b4be Poul Sander 2025-02-03 21:28 5425
        // This is going to be messy. We need to:
1193b4be Poul Sander 2025-02-03 21:28 5426
        // - Display the text (this alone can be more easily clipped)
1193b4be Poul Sander 2025-02-03 21:28 5427
        // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
1193b4be Poul Sander 2025-02-03 21:28 5428
        // - Measure text height (for scrollbar)
1193b4be Poul Sander 2025-02-03 21:28 5429
        // 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)
1193b4be Poul Sander 2025-02-03 21:28 5430
        // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
4e9e2555 Poul Sander 2025-10-05 10:15 5431
        IM_ASSERT(state != NULL);
4e9e2555 Poul Sander 2025-10-05 10:15 5432
        state->LineCount = line_count;
1193b4be Poul Sander 2025-02-03 21:28 5433
1193b4be Poul Sander 2025-02-03 21:28 5434
        // Scroll
4e9e2555 Poul Sander 2025-10-05 10:15 5435
        float new_scroll_y = scroll_y;
1193b4be Poul Sander 2025-02-03 21:28 5436
        if (render_cursor && state->CursorFollow)
1193b4be Poul Sander 2025-02-03 21:28 5437
        {
1193b4be Poul Sander 2025-02-03 21:28 5438
            // Horizontal scroll in chunks of quarter width
1193b4be Poul Sander 2025-02-03 21:28 5439
            if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
1193b4be Poul Sander 2025-02-03 21:28 5440
            {
1193b4be Poul Sander 2025-02-03 21:28 5441
                const float scroll_increment_x = inner_size.x * 0.25f;
1193b4be Poul Sander 2025-02-03 21:28 5442
                const float visible_width = inner_size.x - style.FramePadding.x;
1193b4be Poul Sander 2025-02-03 21:28 5443
                if (cursor_offset.x < state->Scroll.x)
1193b4be Poul Sander 2025-02-03 21:28 5444
                    state->Scroll.x = IM_TRUNC(ImMax(0.0f, cursor_offset.x - scroll_increment_x));
1193b4be Poul Sander 2025-02-03 21:28 5445
                else if (cursor_offset.x - visible_width >= state->Scroll.x)
1193b4be Poul Sander 2025-02-03 21:28 5446
                    state->Scroll.x = IM_TRUNC(cursor_offset.x - visible_width + scroll_increment_x);
1193b4be Poul Sander 2025-02-03 21:28 5447
            }
1193b4be Poul Sander 2025-02-03 21:28 5448
            else
1193b4be Poul Sander 2025-02-03 21:28 5449
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5450
                state->Scroll.x = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5451
            }
1193b4be Poul Sander 2025-02-03 21:28 5452
1193b4be Poul Sander 2025-02-03 21:28 5453
            // Vertical scroll
1193b4be Poul Sander 2025-02-03 21:28 5454
            if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5455
            {
1193b4be Poul Sander 2025-02-03 21:28 5456
                // Test if cursor is vertically visible
1193b4be Poul Sander 2025-02-03 21:28 5457
                if (cursor_offset.y - g.FontSize < scroll_y)
4e9e2555 Poul Sander 2025-10-05 10:15 5458
                    new_scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 5459
                else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
4e9e2555 Poul Sander 2025-10-05 10:15 5460
                    new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 5461
            }
1193b4be Poul Sander 2025-02-03 21:28 5462
            state->CursorFollow = false;
1193b4be Poul Sander 2025-02-03 21:28 5463
        }
4e9e2555 Poul Sander 2025-10-05 10:15 5464
        if (state->CursorCenterY)
1193b4be Poul Sander 2025-02-03 21:28 5465
        {
4e9e2555 Poul Sander 2025-10-05 10:15 5466
            if (is_multiline)
4e9e2555 Poul Sander 2025-10-05 10:15 5467
                new_scroll_y = cursor_offset.y - g.FontSize - (inner_size.y * 0.5f - style.FramePadding.y);
4e9e2555 Poul Sander 2025-10-05 10:15 5468
            state->CursorCenterY = false;
4e9e2555 Poul Sander 2025-10-05 10:15 5469
            render_cursor = false;
1193b4be Poul Sander 2025-02-03 21:28 5470
        }
4e9e2555 Poul Sander 2025-10-05 10:15 5471
        if (new_scroll_y != scroll_y)
1193b4be Poul Sander 2025-02-03 21:28 5472
        {
4e9e2555 Poul Sander 2025-10-05 10:15 5473
            const float scroll_max_y = ImMax((text_size.y + style.FramePadding.y * 2.0f) - inner_size.y, 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 5474
            scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y);
4e9e2555 Poul Sander 2025-10-05 10:15 5475
            draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
4e9e2555 Poul Sander 2025-10-05 10:15 5476
            draw_window->Scroll.y = scroll_y;
4e9e2555 Poul Sander 2025-10-05 10:15 5477
            CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
4e9e2555 Poul Sander 2025-10-05 10:15 5478
            line_visible_n1 = ImMin(line_visible_n1, line_count);
1193b4be Poul Sander 2025-02-03 21:28 5479
        }
1193b4be Poul Sander 2025-02-03 21:28 5480
4e9e2555 Poul Sander 2025-10-05 10:15 5481
        // Draw selection
4e9e2555 Poul Sander 2025-10-05 10:15 5482
        draw_scroll.x = state->Scroll.x;
4e9e2555 Poul Sander 2025-10-05 10:15 5483
        if (render_selection)
1193b4be Poul Sander 2025-02-03 21:28 5484
        {
4e9e2555 Poul Sander 2025-10-05 10:15 5485
            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.
4e9e2555 Poul Sander 2025-10-05 10:15 5486
            const float bg_offy_up = is_multiline ? 0.0f : -1.0f;    // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
4e9e2555 Poul Sander 2025-10-05 10:15 5487
            const float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 5488
            const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
1193b4be Poul Sander 2025-02-03 21:28 5489
4e9e2555 Poul Sander 2025-10-05 10:15 5490
            const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end);
4e9e2555 Poul Sander 2025-10-05 10:15 5491
            const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end);
4e9e2555 Poul Sander 2025-10-05 10:15 5492
            for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++)
1193b4be Poul Sander 2025-02-03 21:28 5493
            {
4e9e2555 Poul Sander 2025-10-05 10:15 5494
                const char* p = line_index->get_line_begin(buf_display, line_n);
4e9e2555 Poul Sander 2025-10-05 10:15 5495
                const char* p_eol = line_index->get_line_end(buf_display, line_n);
4e9e2555 Poul Sander 2025-10-05 10:15 5496
                const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n');
4e9e2555 Poul Sander 2025-10-05 10:15 5497
                if (p_eol_is_wrap)
4e9e2555 Poul Sander 2025-10-05 10:15 5498
                    p_eol++;
4e9e2555 Poul Sander 2025-10-05 10:15 5499
                const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
4e9e2555 Poul Sander 2025-10-05 10:15 5500
                const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol;
4e9e2555 Poul Sander 2025-10-05 10:15 5501
4e9e2555 Poul Sander 2025-10-05 10:15 5502
                float rect_width = 0.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 5503
                if (line_selected_begin < line_selected_end)
4e9e2555 Poul Sander 2025-10-05 10:15 5504
                    rect_width += CalcTextSize(line_selected_begin, line_selected_end).x;
4e9e2555 Poul Sander 2025-10-05 10:15 5505
                if (text_selected_begin <= p_eol && text_selected_end > p_eol && !p_eol_is_wrap)
4e9e2555 Poul Sander 2025-10-05 10:15 5506
                    rect_width += bg_eol_width; // So we can see selected empty lines
4e9e2555 Poul Sander 2025-10-05 10:15 5507
                if (rect_width == 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 5508
                    continue;
4e9e2555 Poul Sander 2025-10-05 10:15 5509
4e9e2555 Poul Sander 2025-10-05 10:15 5510
                ImRect rect;
4e9e2555 Poul Sander 2025-10-05 10:15 5511
                rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x;
4e9e2555 Poul Sander 2025-10-05 10:15 5512
                rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize;
4e9e2555 Poul Sander 2025-10-05 10:15 5513
                rect.Max.x = rect.Min.x + rect_width;
4e9e2555 Poul Sander 2025-10-05 10:15 5514
                rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize;
4e9e2555 Poul Sander 2025-10-05 10:15 5515
                rect.Min.y -= bg_offy_up;
4e9e2555 Poul Sander 2025-10-05 10:15 5516
                rect.ClipWith(clip_rect);
4e9e2555 Poul Sander 2025-10-05 10:15 5517
                draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
1193b4be Poul Sander 2025-02-03 21:28 5518
            }
1193b4be Poul Sander 2025-02-03 21:28 5519
        }
1193b4be Poul Sander 2025-02-03 21:28 5520
    }
4e9e2555 Poul Sander 2025-10-05 10:15 5521
4e9e2555 Poul Sander 2025-10-05 10:15 5522
    // Find render position for right alignment (single-line only)
4e9e2555 Poul Sander 2025-10-05 10:15 5523
    if (g.ActiveId != id && flags & ImGuiInputTextFlags_ElideLeft)
4e9e2555 Poul Sander 2025-10-05 10:15 5524
        draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x);
4e9e2555 Poul Sander 2025-10-05 10:15 5525
    //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive?
4e9e2555 Poul Sander 2025-10-05 10:15 5526
4e9e2555 Poul Sander 2025-10-05 10:15 5527
    // Render text
4e9e2555 Poul Sander 2025-10-05 10:15 5528
    if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1))
4e9e2555 Poul Sander 2025-10-05 10:15 5529
        g.Font->RenderText(draw_window->DrawList, g.FontSize,
4e9e2555 Poul Sander 2025-10-05 10:15 5530
            draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize),
4e9e2555 Poul Sander 2025-10-05 10:15 5531
            text_col, clip_rect.AsVec4(),
4e9e2555 Poul Sander 2025-10-05 10:15 5532
            line_index->get_line_begin(buf_display, line_visible_n0),
4e9e2555 Poul Sander 2025-10-05 10:15 5533
            line_index->get_line_end(buf_display, line_visible_n1 - 1),
4e9e2555 Poul Sander 2025-10-05 10:15 5534
            wrap_width, ImDrawTextFlags_WrapKeepBlanks);
4e9e2555 Poul Sander 2025-10-05 10:15 5535
4e9e2555 Poul Sander 2025-10-05 10:15 5536
    // Render blinking cursor
4e9e2555 Poul Sander 2025-10-05 10:15 5537
    if (render_cursor)
1193b4be Poul Sander 2025-02-03 21:28 5538
    {
4e9e2555 Poul Sander 2025-10-05 10:15 5539
        state->CursorAnim += io.DeltaTime;
4e9e2555 Poul Sander 2025-10-05 10:15 5540
        bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
4e9e2555 Poul Sander 2025-10-05 10:15 5541
        ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
4e9e2555 Poul Sander 2025-10-05 10:15 5542
        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);
4e9e2555 Poul Sander 2025-10-05 10:15 5543
        if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
4e9e2555 Poul Sander 2025-10-05 10:15 5544
            draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
1193b4be Poul Sander 2025-02-03 21:28 5545
4e9e2555 Poul Sander 2025-10-05 10:15 5546
        // 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.)
4e9e2555 Poul Sander 2025-10-05 10:15 5547
        // This is required for some backends (SDL3) to start emitting character/text inputs.
4e9e2555 Poul Sander 2025-10-05 10:15 5548
        // As per #6341, make sure we don't set that on the deactivating frame.
4e9e2555 Poul Sander 2025-10-05 10:15 5549
        if (!is_readonly && g.ActiveId == id)
1193b4be Poul Sander 2025-02-03 21:28 5550
        {
4e9e2555 Poul Sander 2025-10-05 10:15 5551
            ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
4e9e2555 Poul Sander 2025-10-05 10:15 5552
            ime_data->WantVisible = true;
4e9e2555 Poul Sander 2025-10-05 10:15 5553
            ime_data->WantTextInput = true;
4e9e2555 Poul Sander 2025-10-05 10:15 5554
            ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
4e9e2555 Poul Sander 2025-10-05 10:15 5555
            ime_data->InputLineHeight = g.FontSize;
4e9e2555 Poul Sander 2025-10-05 10:15 5556
            ime_data->ViewportId = window->Viewport->ID;
1193b4be Poul Sander 2025-02-03 21:28 5557
        }
1193b4be Poul Sander 2025-02-03 21:28 5558
    }
1193b4be Poul Sander 2025-02-03 21:28 5559
1193b4be Poul Sander 2025-02-03 21:28 5560
    if (is_password && !is_displaying_hint)
4e9e2555 Poul Sander 2025-10-05 10:15 5561
        PopPasswordFont();
1193b4be Poul Sander 2025-02-03 21:28 5562
1193b4be Poul Sander 2025-02-03 21:28 5563
    if (is_multiline)
1193b4be Poul Sander 2025-02-03 21:28 5564
    {
1193b4be Poul Sander 2025-02-03 21:28 5565
        // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (see #4761, #7870)...
1193b4be Poul Sander 2025-02-03 21:28 5566
        Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y));
1193b4be Poul Sander 2025-02-03 21:28 5567
        g.NextItemData.ItemFlags |= (ImGuiItemFlags)ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop;
1193b4be Poul Sander 2025-02-03 21:28 5568
        EndChild();
1193b4be Poul Sander 2025-02-03 21:28 5569
        item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow);
1193b4be Poul Sander 2025-02-03 21:28 5570
1193b4be Poul Sander 2025-02-03 21:28 5571
        // ...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...
1193b4be Poul Sander 2025-02-03 21:28 5572
        // FIXME: This quite messy/tricky, should attempt to get rid of the child window.
1193b4be Poul Sander 2025-02-03 21:28 5573
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 5574
        if (g.LastItemData.ID == 0 || g.LastItemData.ID != GetWindowScrollbarID(draw_window, ImGuiAxis_Y))
1193b4be Poul Sander 2025-02-03 21:28 5575
        {
1193b4be Poul Sander 2025-02-03 21:28 5576
            g.LastItemData.ID = id;
1193b4be Poul Sander 2025-02-03 21:28 5577
            g.LastItemData.ItemFlags = item_data_backup.ItemFlags;
1193b4be Poul Sander 2025-02-03 21:28 5578
            g.LastItemData.StatusFlags = item_data_backup.StatusFlags;
1193b4be Poul Sander 2025-02-03 21:28 5579
        }
1193b4be Poul Sander 2025-02-03 21:28 5580
    }
4e9e2555 Poul Sander 2025-10-05 10:15 5581
    if (state)
4e9e2555 Poul Sander 2025-10-05 10:15 5582
        state->TextSrc = NULL;
1193b4be Poul Sander 2025-02-03 21:28 5583
1193b4be Poul Sander 2025-02-03 21:28 5584
    // Log as text
1193b4be Poul Sander 2025-02-03 21:28 5585
    if (g.LogEnabled && (!is_password || is_displaying_hint))
1193b4be Poul Sander 2025-02-03 21:28 5586
    {
1193b4be Poul Sander 2025-02-03 21:28 5587
        LogSetNextTextDecoration("{", "}");
1193b4be Poul Sander 2025-02-03 21:28 5588
        LogRenderedText(&draw_pos, buf_display, buf_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5589
    }
1193b4be Poul Sander 2025-02-03 21:28 5590
1193b4be Poul Sander 2025-02-03 21:28 5591
    if (label_size.x > 0)
1193b4be Poul Sander 2025-02-03 21:28 5592
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
1193b4be Poul Sander 2025-02-03 21:28 5593
1193b4be Poul Sander 2025-02-03 21:28 5594
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 5595
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 5596
1193b4be Poul Sander 2025-02-03 21:28 5597
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Inputable);
1193b4be Poul Sander 2025-02-03 21:28 5598
    if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
1193b4be Poul Sander 2025-02-03 21:28 5599
        return validated;
1193b4be Poul Sander 2025-02-03 21:28 5600
    else
1193b4be Poul Sander 2025-02-03 21:28 5601
        return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5602
}
1193b4be Poul Sander 2025-02-03 21:28 5603
1193b4be Poul Sander 2025-02-03 21:28 5604
void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
1193b4be Poul Sander 2025-02-03 21:28 5605
{
1193b4be Poul Sander 2025-02-03 21:28 5606
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 5607
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5608
    ImStb::STB_TexteditState* stb_state = state->Stb;
1193b4be Poul Sander 2025-02-03 21:28 5609
    ImStb::StbUndoState* undo_state = &stb_state->undostate;
1193b4be Poul Sander 2025-02-03 21:28 5610
    Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
1193b4be Poul Sander 2025-02-03 21:28 5611
    DebugLocateItemOnHover(state->ID);
4e9e2555 Poul Sander 2025-10-05 10:15 5612
    Text("TextLen: %d, Cursor: %d%s, Selection: %d..%d", state->TextLen, stb_state->cursor,
4e9e2555 Poul Sander 2025-10-05 10:15 5613
        (state->Flags & ImGuiInputTextFlags_WordWrap) ? (state->LastMoveDirectionLR == ImGuiDir_Left ? " (L)" : " (R)") : "",
4e9e2555 Poul Sander 2025-10-05 10:15 5614
        stb_state->select_start, stb_state->select_end);
4e9e2555 Poul Sander 2025-10-05 10:15 5615
    Text("BufCapacity: %d, LineCount: %d", state->BufCapacity, state->LineCount);
4e9e2555 Poul Sander 2025-10-05 10:15 5616
    Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity);
1193b4be Poul Sander 2025-02-03 21:28 5617
    Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
1193b4be Poul Sander 2025-02-03 21:28 5618
    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);
1193b4be Poul Sander 2025-02-03 21:28 5619
    if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 10), ImGuiChildFlags_Borders | ImGuiChildFlags_ResizeY)) // Visualize undo state
1193b4be Poul Sander 2025-02-03 21:28 5620
    {
1193b4be Poul Sander 2025-02-03 21:28 5621
        PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
1193b4be Poul Sander 2025-02-03 21:28 5622
        for (int n = 0; n < IMSTB_TEXTEDIT_UNDOSTATECOUNT; n++)
1193b4be Poul Sander 2025-02-03 21:28 5623
        {
1193b4be Poul Sander 2025-02-03 21:28 5624
            ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n];
1193b4be Poul Sander 2025-02-03 21:28 5625
            const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' ';
1193b4be Poul Sander 2025-02-03 21:28 5626
            if (undo_rec_type == ' ')
1193b4be Poul Sander 2025-02-03 21:28 5627
                BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 5628
            const int buf_preview_len = (undo_rec_type != ' ' && undo_rec->char_storage != -1) ? undo_rec->insert_length : 0;
1193b4be Poul Sander 2025-02-03 21:28 5629
            const char* buf_preview_str = undo_state->undo_char + undo_rec->char_storage;
1193b4be Poul Sander 2025-02-03 21:28 5630
            Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%.*s\"",
1193b4be Poul Sander 2025-02-03 21:28 5631
                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);
1193b4be Poul Sander 2025-02-03 21:28 5632
            if (undo_rec_type == ' ')
1193b4be Poul Sander 2025-02-03 21:28 5633
                EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 5634
        }
1193b4be Poul Sander 2025-02-03 21:28 5635
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 5636
    }
1193b4be Poul Sander 2025-02-03 21:28 5637
    EndChild();
1193b4be Poul Sander 2025-02-03 21:28 5638
#else
1193b4be Poul Sander 2025-02-03 21:28 5639
    IM_UNUSED(state);
1193b4be Poul Sander 2025-02-03 21:28 5640
#endif
1193b4be Poul Sander 2025-02-03 21:28 5641
}
1193b4be Poul Sander 2025-02-03 21:28 5642
1193b4be Poul Sander 2025-02-03 21:28 5643
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5644
// [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc.
1193b4be Poul Sander 2025-02-03 21:28 5645
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5646
// - ColorEdit3()
1193b4be Poul Sander 2025-02-03 21:28 5647
// - ColorEdit4()
1193b4be Poul Sander 2025-02-03 21:28 5648
// - ColorPicker3()
1193b4be Poul Sander 2025-02-03 21:28 5649
// - RenderColorRectWithAlphaCheckerboard() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5650
// - ColorPicker4()
1193b4be Poul Sander 2025-02-03 21:28 5651
// - ColorButton()
1193b4be Poul Sander 2025-02-03 21:28 5652
// - SetColorEditOptions()
1193b4be Poul Sander 2025-02-03 21:28 5653
// - ColorTooltip() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5654
// - ColorEditOptionsPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5655
// - ColorPickerOptionsPopup() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 5656
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 5657
1193b4be Poul Sander 2025-02-03 21:28 5658
bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5659
{
1193b4be Poul Sander 2025-02-03 21:28 5660
    return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 5661
}
1193b4be Poul Sander 2025-02-03 21:28 5662
1193b4be Poul Sander 2025-02-03 21:28 5663
static void ColorEditRestoreH(const float* col, float* H)
1193b4be Poul Sander 2025-02-03 21:28 5664
{
1193b4be Poul Sander 2025-02-03 21:28 5665
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5666
    IM_ASSERT(g.ColorEditCurrentID != 0);
1193b4be Poul Sander 2025-02-03 21:28 5667
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
1193b4be Poul Sander 2025-02-03 21:28 5668
        return;
1193b4be Poul Sander 2025-02-03 21:28 5669
    *H = g.ColorEditSavedHue;
1193b4be Poul Sander 2025-02-03 21:28 5670
}
1193b4be Poul Sander 2025-02-03 21:28 5671
1193b4be Poul Sander 2025-02-03 21:28 5672
// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
1193b4be Poul Sander 2025-02-03 21:28 5673
// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting.
1193b4be Poul Sander 2025-02-03 21:28 5674
static void ColorEditRestoreHS(const float* col, float* H, float* S, float* V)
1193b4be Poul Sander 2025-02-03 21:28 5675
{
1193b4be Poul Sander 2025-02-03 21:28 5676
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5677
    IM_ASSERT(g.ColorEditCurrentID != 0);
1193b4be Poul Sander 2025-02-03 21:28 5678
    if (g.ColorEditSavedID != g.ColorEditCurrentID || g.ColorEditSavedColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
1193b4be Poul Sander 2025-02-03 21:28 5679
        return;
1193b4be Poul Sander 2025-02-03 21:28 5680
1193b4be Poul Sander 2025-02-03 21:28 5681
    // When S == 0, H is undefined.
1193b4be Poul Sander 2025-02-03 21:28 5682
    // When H == 1 it wraps around to 0.
1193b4be Poul Sander 2025-02-03 21:28 5683
    if (*S == 0.0f || (*H == 0.0f && g.ColorEditSavedHue == 1))
1193b4be Poul Sander 2025-02-03 21:28 5684
        *H = g.ColorEditSavedHue;
1193b4be Poul Sander 2025-02-03 21:28 5685
1193b4be Poul Sander 2025-02-03 21:28 5686
    // When V == 0, S is undefined.
1193b4be Poul Sander 2025-02-03 21:28 5687
    if (*V == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 5688
        *S = g.ColorEditSavedSat;
1193b4be Poul Sander 2025-02-03 21:28 5689
}
1193b4be Poul Sander 2025-02-03 21:28 5690
1193b4be Poul Sander 2025-02-03 21:28 5691
// Edit colors components (each component in 0.0f..1.0f range).
1193b4be Poul Sander 2025-02-03 21:28 5692
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
4e9e2555 Poul Sander 2025-10-05 10:15 5693
// 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.
1193b4be Poul Sander 2025-02-03 21:28 5694
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5695
{
1193b4be Poul Sander 2025-02-03 21:28 5696
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 5697
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 5698
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5699
1193b4be Poul Sander 2025-02-03 21:28 5700
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5701
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 5702
    const float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 5703
    const char* label_display_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 5704
    float w_full = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 5705
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 5706
1193b4be Poul Sander 2025-02-03 21:28 5707
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 5708
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 5709
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
1193b4be Poul Sander 2025-02-03 21:28 5710
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5711
        g.ColorEditCurrentID = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 5712
1193b4be Poul Sander 2025-02-03 21:28 5713
    // If we're not showing any slider there's no point in doing any HSV conversions
1193b4be Poul Sander 2025-02-03 21:28 5714
    const ImGuiColorEditFlags flags_untouched = flags;
1193b4be Poul Sander 2025-02-03 21:28 5715
    if (flags & ImGuiColorEditFlags_NoInputs)
1193b4be Poul Sander 2025-02-03 21:28 5716
        flags = (flags & (~ImGuiColorEditFlags_DisplayMask_)) | ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_NoOptions;
1193b4be Poul Sander 2025-02-03 21:28 5717
1193b4be Poul Sander 2025-02-03 21:28 5718
    // Context menu: display and modify options (before defaults are applied)
1193b4be Poul Sander 2025-02-03 21:28 5719
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5720
        ColorEditOptionsPopup(col, flags);
1193b4be Poul Sander 2025-02-03 21:28 5721
1193b4be Poul Sander 2025-02-03 21:28 5722
    // Read stored options
1193b4be Poul Sander 2025-02-03 21:28 5723
    if (!(flags & ImGuiColorEditFlags_DisplayMask_))
1193b4be Poul Sander 2025-02-03 21:28 5724
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DisplayMask_);
1193b4be Poul Sander 2025-02-03 21:28 5725
    if (!(flags & ImGuiColorEditFlags_DataTypeMask_))
1193b4be Poul Sander 2025-02-03 21:28 5726
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_DataTypeMask_);
1193b4be Poul Sander 2025-02-03 21:28 5727
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
1193b4be Poul Sander 2025-02-03 21:28 5728
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 5729
    if (!(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 5730
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_InputMask_);
1193b4be Poul Sander 2025-02-03 21:28 5731
    flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_));
1193b4be Poul Sander 2025-02-03 21:28 5732
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_)); // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5733
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));   // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5734
1193b4be Poul Sander 2025-02-03 21:28 5735
    const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
1193b4be Poul Sander 2025-02-03 21:28 5736
    const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
1193b4be Poul Sander 2025-02-03 21:28 5737
    const int components = alpha ? 4 : 3;
1193b4be Poul Sander 2025-02-03 21:28 5738
    const float w_button = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5739
    const float w_inputs = ImMax(w_full - w_button, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5740
    w_full = w_inputs + w_button;
1193b4be Poul Sander 2025-02-03 21:28 5741
1193b4be Poul Sander 2025-02-03 21:28 5742
    // Convert to the formats we need
1193b4be Poul Sander 2025-02-03 21:28 5743
    float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
1193b4be Poul Sander 2025-02-03 21:28 5744
    if ((flags & ImGuiColorEditFlags_InputHSV) && (flags & ImGuiColorEditFlags_DisplayRGB))
1193b4be Poul Sander 2025-02-03 21:28 5745
        ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5746
    else if ((flags & ImGuiColorEditFlags_InputRGB) && (flags & ImGuiColorEditFlags_DisplayHSV))
1193b4be Poul Sander 2025-02-03 21:28 5747
    {
1193b4be Poul Sander 2025-02-03 21:28 5748
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
1193b4be Poul Sander 2025-02-03 21:28 5749
        ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5750
        ColorEditRestoreHS(col, &f[0], &f[1], &f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5751
    }
1193b4be Poul Sander 2025-02-03 21:28 5752
    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]) };
1193b4be Poul Sander 2025-02-03 21:28 5753
1193b4be Poul Sander 2025-02-03 21:28 5754
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 5755
    bool value_changed_as_float = false;
1193b4be Poul Sander 2025-02-03 21:28 5756
1193b4be Poul Sander 2025-02-03 21:28 5757
    const ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 5758
    const float inputs_offset_x = (style.ColorButtonPosition == ImGuiDir_Left) ? w_button : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5759
    window->DC.CursorPos.x = pos.x + inputs_offset_x;
1193b4be Poul Sander 2025-02-03 21:28 5760
1193b4be Poul Sander 2025-02-03 21:28 5761
    if ((flags & (ImGuiColorEditFlags_DisplayRGB | ImGuiColorEditFlags_DisplayHSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5762
    {
1193b4be Poul Sander 2025-02-03 21:28 5763
        // RGB/HSV 0..255 Sliders
1193b4be Poul Sander 2025-02-03 21:28 5764
        const float w_items = w_inputs - style.ItemInnerSpacing.x * (components - 1);
1193b4be Poul Sander 2025-02-03 21:28 5765
1193b4be Poul Sander 2025-02-03 21:28 5766
        const bool hide_prefix = (IM_TRUNC(w_items / components) <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
1193b4be Poul Sander 2025-02-03 21:28 5767
        static const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
1193b4be Poul Sander 2025-02-03 21:28 5768
        static const char* fmt_table_int[3][4] =
1193b4be Poul Sander 2025-02-03 21:28 5769
        {
1193b4be Poul Sander 2025-02-03 21:28 5770
            {   "%3d",   "%3d",   "%3d",   "%3d" }, // Short display
1193b4be Poul Sander 2025-02-03 21:28 5771
            { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
1193b4be Poul Sander 2025-02-03 21:28 5772
            { "H:%3d", "S:%3d", "V:%3d", "A:%3d" }  // Long display for HSVA
1193b4be Poul Sander 2025-02-03 21:28 5773
        };
1193b4be Poul Sander 2025-02-03 21:28 5774
        static const char* fmt_table_float[3][4] =
1193b4be Poul Sander 2025-02-03 21:28 5775
        {
1193b4be Poul Sander 2025-02-03 21:28 5776
            {   "%0.3f",   "%0.3f",   "%0.3f",   "%0.3f" }, // Short display
1193b4be Poul Sander 2025-02-03 21:28 5777
            { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
1193b4be Poul Sander 2025-02-03 21:28 5778
            { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" }  // Long display for HSVA
1193b4be Poul Sander 2025-02-03 21:28 5779
        };
1193b4be Poul Sander 2025-02-03 21:28 5780
        const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_DisplayHSV) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 5781
1193b4be Poul Sander 2025-02-03 21:28 5782
        float prev_split = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 5783
        for (int n = 0; n < components; n++)
1193b4be Poul Sander 2025-02-03 21:28 5784
        {
1193b4be Poul Sander 2025-02-03 21:28 5785
            if (n > 0)
1193b4be Poul Sander 2025-02-03 21:28 5786
                SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5787
            float next_split = IM_TRUNC(w_items * (n + 1) / components);
1193b4be Poul Sander 2025-02-03 21:28 5788
            SetNextItemWidth(ImMax(next_split - prev_split, 1.0f));
1193b4be Poul Sander 2025-02-03 21:28 5789
            prev_split = next_split;
1193b4be Poul Sander 2025-02-03 21:28 5790
1193b4be Poul Sander 2025-02-03 21:28 5791
            // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0.
1193b4be Poul Sander 2025-02-03 21:28 5792
            if (flags & ImGuiColorEditFlags_Float)
1193b4be Poul Sander 2025-02-03 21:28 5793
            {
1193b4be Poul Sander 2025-02-03 21:28 5794
                value_changed |= DragFloat(ids[n], &f[n], 1.0f / 255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
1193b4be Poul Sander 2025-02-03 21:28 5795
                value_changed_as_float |= value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5796
            }
1193b4be Poul Sander 2025-02-03 21:28 5797
            else
1193b4be Poul Sander 2025-02-03 21:28 5798
            {
1193b4be Poul Sander 2025-02-03 21:28 5799
                value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
1193b4be Poul Sander 2025-02-03 21:28 5800
            }
1193b4be Poul Sander 2025-02-03 21:28 5801
            if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5802
                OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5803
        }
1193b4be Poul Sander 2025-02-03 21:28 5804
    }
1193b4be Poul Sander 2025-02-03 21:28 5805
    else if ((flags & ImGuiColorEditFlags_DisplayHex) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 5806
    {
1193b4be Poul Sander 2025-02-03 21:28 5807
        // RGB Hexadecimal Input
1193b4be Poul Sander 2025-02-03 21:28 5808
        char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 5809
        if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5810
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255), ImClamp(i[3], 0, 255));
1193b4be Poul Sander 2025-02-03 21:28 5811
        else
1193b4be Poul Sander 2025-02-03 21:28 5812
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
1193b4be Poul Sander 2025-02-03 21:28 5813
        SetNextItemWidth(w_inputs);
1193b4be Poul Sander 2025-02-03 21:28 5814
        if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsUppercase))
1193b4be Poul Sander 2025-02-03 21:28 5815
        {
1193b4be Poul Sander 2025-02-03 21:28 5816
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 5817
            char* p = buf;
1193b4be Poul Sander 2025-02-03 21:28 5818
            while (*p == '#' || ImCharIsBlankA(*p))
1193b4be Poul Sander 2025-02-03 21:28 5819
                p++;
1193b4be Poul Sander 2025-02-03 21:28 5820
            i[0] = i[1] = i[2] = 0;
1193b4be Poul Sander 2025-02-03 21:28 5821
            i[3] = 0xFF; // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha)
1193b4be Poul Sander 2025-02-03 21:28 5822
            int r;
1193b4be Poul Sander 2025-02-03 21:28 5823
            if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5824
                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)
1193b4be Poul Sander 2025-02-03 21:28 5825
            else
1193b4be Poul Sander 2025-02-03 21:28 5826
                r = sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
1193b4be Poul Sander 2025-02-03 21:28 5827
            IM_UNUSED(r); // Fixes C6031: Return value ignored: 'sscanf'.
1193b4be Poul Sander 2025-02-03 21:28 5828
        }
1193b4be Poul Sander 2025-02-03 21:28 5829
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5830
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5831
    }
1193b4be Poul Sander 2025-02-03 21:28 5832
1193b4be Poul Sander 2025-02-03 21:28 5833
    ImGuiWindow* picker_active_window = NULL;
1193b4be Poul Sander 2025-02-03 21:28 5834
    if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
1193b4be Poul Sander 2025-02-03 21:28 5835
    {
1193b4be Poul Sander 2025-02-03 21:28 5836
        const float button_offset_x = ((flags & ImGuiColorEditFlags_NoInputs) || (style.ColorButtonPosition == ImGuiDir_Left)) ? 0.0f : w_inputs + style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 5837
        window->DC.CursorPos = ImVec2(pos.x + button_offset_x, pos.y);
1193b4be Poul Sander 2025-02-03 21:28 5838
1193b4be Poul Sander 2025-02-03 21:28 5839
        const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 5840
        if (ColorButton("##ColorButton", col_v4, flags))
1193b4be Poul Sander 2025-02-03 21:28 5841
        {
1193b4be Poul Sander 2025-02-03 21:28 5842
            if (!(flags & ImGuiColorEditFlags_NoPicker))
1193b4be Poul Sander 2025-02-03 21:28 5843
            {
1193b4be Poul Sander 2025-02-03 21:28 5844
                // Store current color and open a picker
1193b4be Poul Sander 2025-02-03 21:28 5845
                g.ColorPickerRef = col_v4;
1193b4be Poul Sander 2025-02-03 21:28 5846
                OpenPopup("picker");
1193b4be Poul Sander 2025-02-03 21:28 5847
                SetNextWindowPos(g.LastItemData.Rect.GetBL() + ImVec2(0.0f, style.ItemSpacing.y));
1193b4be Poul Sander 2025-02-03 21:28 5848
            }
1193b4be Poul Sander 2025-02-03 21:28 5849
        }
1193b4be Poul Sander 2025-02-03 21:28 5850
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5851
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 5852
1193b4be Poul Sander 2025-02-03 21:28 5853
        if (BeginPopup("picker"))
1193b4be Poul Sander 2025-02-03 21:28 5854
        {
1193b4be Poul Sander 2025-02-03 21:28 5855
            if (g.CurrentWindow->BeginCount == 1)
1193b4be Poul Sander 2025-02-03 21:28 5856
            {
1193b4be Poul Sander 2025-02-03 21:28 5857
                picker_active_window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 5858
                if (label != label_display_end)
1193b4be Poul Sander 2025-02-03 21:28 5859
                {
1193b4be Poul Sander 2025-02-03 21:28 5860
                    TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5861
                    Spacing();
1193b4be Poul Sander 2025-02-03 21:28 5862
                }
1193b4be Poul Sander 2025-02-03 21:28 5863
                ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
1193b4be Poul Sander 2025-02-03 21:28 5864
                ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
1193b4be Poul Sander 2025-02-03 21:28 5865
                SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
1193b4be Poul Sander 2025-02-03 21:28 5866
                value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
1193b4be Poul Sander 2025-02-03 21:28 5867
            }
1193b4be Poul Sander 2025-02-03 21:28 5868
            EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 5869
        }
1193b4be Poul Sander 2025-02-03 21:28 5870
    }
1193b4be Poul Sander 2025-02-03 21:28 5871
1193b4be Poul Sander 2025-02-03 21:28 5872
    if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 5873
    {
1193b4be Poul Sander 2025-02-03 21:28 5874
        // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left),
1193b4be Poul Sander 2025-02-03 21:28 5875
        // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this.
1193b4be Poul Sander 2025-02-03 21:28 5876
        SameLine(0.0f, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5877
        window->DC.CursorPos.x = pos.x + ((flags & ImGuiColorEditFlags_NoInputs) ? w_button : w_full + style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 5878
        TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 5879
    }
1193b4be Poul Sander 2025-02-03 21:28 5880
1193b4be Poul Sander 2025-02-03 21:28 5881
    // Convert back
1193b4be Poul Sander 2025-02-03 21:28 5882
    if (value_changed && picker_active_window == NULL)
1193b4be Poul Sander 2025-02-03 21:28 5883
    {
1193b4be Poul Sander 2025-02-03 21:28 5884
        if (!value_changed_as_float)
1193b4be Poul Sander 2025-02-03 21:28 5885
            for (int n = 0; n < 4; n++)
1193b4be Poul Sander 2025-02-03 21:28 5886
                f[n] = i[n] / 255.0f;
1193b4be Poul Sander 2025-02-03 21:28 5887
        if ((flags & ImGuiColorEditFlags_DisplayHSV) && (flags & ImGuiColorEditFlags_InputRGB))
1193b4be Poul Sander 2025-02-03 21:28 5888
        {
1193b4be Poul Sander 2025-02-03 21:28 5889
            g.ColorEditSavedHue = f[0];
1193b4be Poul Sander 2025-02-03 21:28 5890
            g.ColorEditSavedSat = f[1];
1193b4be Poul Sander 2025-02-03 21:28 5891
            ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5892
            g.ColorEditSavedID = g.ColorEditCurrentID;
1193b4be Poul Sander 2025-02-03 21:28 5893
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
1193b4be Poul Sander 2025-02-03 21:28 5894
        }
1193b4be Poul Sander 2025-02-03 21:28 5895
        if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
1193b4be Poul Sander 2025-02-03 21:28 5896
            ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
1193b4be Poul Sander 2025-02-03 21:28 5897
1193b4be Poul Sander 2025-02-03 21:28 5898
        col[0] = f[0];
1193b4be Poul Sander 2025-02-03 21:28 5899
        col[1] = f[1];
1193b4be Poul Sander 2025-02-03 21:28 5900
        col[2] = f[2];
1193b4be Poul Sander 2025-02-03 21:28 5901
        if (alpha)
1193b4be Poul Sander 2025-02-03 21:28 5902
            col[3] = f[3];
1193b4be Poul Sander 2025-02-03 21:28 5903
    }
1193b4be Poul Sander 2025-02-03 21:28 5904
1193b4be Poul Sander 2025-02-03 21:28 5905
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5906
        g.ColorEditCurrentID = 0;
1193b4be Poul Sander 2025-02-03 21:28 5907
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 5908
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 5909
1193b4be Poul Sander 2025-02-03 21:28 5910
    // Drag and Drop Target
1193b4be Poul Sander 2025-02-03 21:28 5911
    // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
1193b4be Poul Sander 2025-02-03 21:28 5912
    if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
1193b4be Poul Sander 2025-02-03 21:28 5913
    {
1193b4be Poul Sander 2025-02-03 21:28 5914
        bool accepted_drag_drop = false;
1193b4be Poul Sander 2025-02-03 21:28 5915
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
1193b4be Poul Sander 2025-02-03 21:28 5916
        {
1193b4be Poul Sander 2025-02-03 21:28 5917
            memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
1193b4be Poul Sander 2025-02-03 21:28 5918
            value_changed = accepted_drag_drop = true;
1193b4be Poul Sander 2025-02-03 21:28 5919
        }
1193b4be Poul Sander 2025-02-03 21:28 5920
        if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
1193b4be Poul Sander 2025-02-03 21:28 5921
        {
1193b4be Poul Sander 2025-02-03 21:28 5922
            memcpy((float*)col, payload->Data, sizeof(float) * components);
1193b4be Poul Sander 2025-02-03 21:28 5923
            value_changed = accepted_drag_drop = true;
1193b4be Poul Sander 2025-02-03 21:28 5924
        }
1193b4be Poul Sander 2025-02-03 21:28 5925
1193b4be Poul Sander 2025-02-03 21:28 5926
        // Drag-drop payloads are always RGB
1193b4be Poul Sander 2025-02-03 21:28 5927
        if (accepted_drag_drop && (flags & ImGuiColorEditFlags_InputHSV))
1193b4be Poul Sander 2025-02-03 21:28 5928
            ColorConvertRGBtoHSV(col[0], col[1], col[2], col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 5929
        EndDragDropTarget();
1193b4be Poul Sander 2025-02-03 21:28 5930
    }
1193b4be Poul Sander 2025-02-03 21:28 5931
1193b4be Poul Sander 2025-02-03 21:28 5932
    // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
1193b4be Poul Sander 2025-02-03 21:28 5933
    if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
1193b4be Poul Sander 2025-02-03 21:28 5934
        g.LastItemData.ID = g.ActiveId;
1193b4be Poul Sander 2025-02-03 21:28 5935
1193b4be Poul Sander 2025-02-03 21:28 5936
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
1193b4be Poul Sander 2025-02-03 21:28 5937
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 5938
1193b4be Poul Sander 2025-02-03 21:28 5939
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 5940
}
1193b4be Poul Sander 2025-02-03 21:28 5941
1193b4be Poul Sander 2025-02-03 21:28 5942
bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 5943
{
1193b4be Poul Sander 2025-02-03 21:28 5944
    float col4[4] = { col[0], col[1], col[2], 1.0f };
1193b4be Poul Sander 2025-02-03 21:28 5945
    if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
1193b4be Poul Sander 2025-02-03 21:28 5946
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5947
    col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
1193b4be Poul Sander 2025-02-03 21:28 5948
    return true;
1193b4be Poul Sander 2025-02-03 21:28 5949
}
1193b4be Poul Sander 2025-02-03 21:28 5950
1193b4be Poul Sander 2025-02-03 21:28 5951
// Helper for ColorPicker4()
1193b4be Poul Sander 2025-02-03 21:28 5952
static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w, float alpha)
1193b4be Poul Sander 2025-02-03 21:28 5953
{
1193b4be Poul Sander 2025-02-03 21:28 5954
    ImU32 alpha8 = IM_F32_TO_INT8_SAT(alpha);
1193b4be Poul Sander 2025-02-03 21:28 5955
    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));
1193b4be Poul Sander 2025-02-03 21:28 5956
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x,             pos.y), half_sz,                              ImGuiDir_Right, IM_COL32(255,255,255,alpha8));
1193b4be Poul Sander 2025-02-03 21:28 5957
    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));
1193b4be Poul Sander 2025-02-03 21:28 5958
    ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x,     pos.y), half_sz,                              ImGuiDir_Left,  IM_COL32(255,255,255,alpha8));
1193b4be Poul Sander 2025-02-03 21:28 5959
}
1193b4be Poul Sander 2025-02-03 21:28 5960
1193b4be Poul Sander 2025-02-03 21:28 5961
// Note: ColorPicker4() only accesses 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
1193b4be Poul Sander 2025-02-03 21:28 5962
// (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.)
1193b4be Poul Sander 2025-02-03 21:28 5963
// 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..)
1193b4be Poul Sander 2025-02-03 21:28 5964
// 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)
1193b4be Poul Sander 2025-02-03 21:28 5965
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
1193b4be Poul Sander 2025-02-03 21:28 5966
{
1193b4be Poul Sander 2025-02-03 21:28 5967
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 5968
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 5969
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 5970
        return false;
1193b4be Poul Sander 2025-02-03 21:28 5971
1193b4be Poul Sander 2025-02-03 21:28 5972
    ImDrawList* draw_list = window->DrawList;
1193b4be Poul Sander 2025-02-03 21:28 5973
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 5974
    ImGuiIO& io = g.IO;
1193b4be Poul Sander 2025-02-03 21:28 5975
1193b4be Poul Sander 2025-02-03 21:28 5976
    const float width = CalcItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 5977
    const bool is_readonly = ((g.NextItemData.ItemFlags | g.CurrentItemFlags) & ImGuiItemFlags_ReadOnly) != 0;
1193b4be Poul Sander 2025-02-03 21:28 5978
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 5979
1193b4be Poul Sander 2025-02-03 21:28 5980
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 5981
    const bool set_current_color_edit_id = (g.ColorEditCurrentID == 0);
1193b4be Poul Sander 2025-02-03 21:28 5982
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 5983
        g.ColorEditCurrentID = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 5984
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 5985
1193b4be Poul Sander 2025-02-03 21:28 5986
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 5987
        flags |= ImGuiColorEditFlags_NoSmallPreview;
1193b4be Poul Sander 2025-02-03 21:28 5988
1193b4be Poul Sander 2025-02-03 21:28 5989
    // Context menu: display and store options.
1193b4be Poul Sander 2025-02-03 21:28 5990
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 5991
        ColorPickerOptionsPopup(col, flags);
1193b4be Poul Sander 2025-02-03 21:28 5992
1193b4be Poul Sander 2025-02-03 21:28 5993
    // Read stored options
1193b4be Poul Sander 2025-02-03 21:28 5994
    if (!(flags & ImGuiColorEditFlags_PickerMask_))
1193b4be Poul Sander 2025-02-03 21:28 5995
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_PickerMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_PickerMask_;
1193b4be Poul Sander 2025-02-03 21:28 5996
    if (!(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 5997
        flags |= ((g.ColorEditOptions & ImGuiColorEditFlags_InputMask_) ? g.ColorEditOptions : ImGuiColorEditFlags_DefaultOptions_) & ImGuiColorEditFlags_InputMask_;
1193b4be Poul Sander 2025-02-03 21:28 5998
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_)); // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 5999
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));  // Check that only 1 is selected
1193b4be Poul Sander 2025-02-03 21:28 6000
    if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 6001
        flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 6002
1193b4be Poul Sander 2025-02-03 21:28 6003
    // Setup
1193b4be Poul Sander 2025-02-03 21:28 6004
    int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
1193b4be Poul Sander 2025-02-03 21:28 6005
    bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 6006
    ImVec2 picker_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 6007
    float square_sz = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 6008
    float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
1193b4be Poul Sander 2025-02-03 21:28 6009
    float sv_picker_size = ImMax(bars_width * 1, width - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
1193b4be Poul Sander 2025-02-03 21:28 6010
    float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 6011
    float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 6012
    float bars_triangles_half_sz = IM_TRUNC(bars_width * 0.20f);
1193b4be Poul Sander 2025-02-03 21:28 6013
1193b4be Poul Sander 2025-02-03 21:28 6014
    float backup_initial_col[4];
1193b4be Poul Sander 2025-02-03 21:28 6015
    memcpy(backup_initial_col, col, components * sizeof(float));
1193b4be Poul Sander 2025-02-03 21:28 6016
1193b4be Poul Sander 2025-02-03 21:28 6017
    float wheel_thickness = sv_picker_size * 0.08f;
1193b4be Poul Sander 2025-02-03 21:28 6018
    float wheel_r_outer = sv_picker_size * 0.50f;
1193b4be Poul Sander 2025-02-03 21:28 6019
    float wheel_r_inner = wheel_r_outer - wheel_thickness;
1193b4be Poul Sander 2025-02-03 21:28 6020
    ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 6021
1193b4be Poul Sander 2025-02-03 21:28 6022
    // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
1193b4be Poul Sander 2025-02-03 21:28 6023
    float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
1193b4be Poul Sander 2025-02-03 21:28 6024
    ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
1193b4be Poul Sander 2025-02-03 21:28 6025
    ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
1193b4be Poul Sander 2025-02-03 21:28 6026
    ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
1193b4be Poul Sander 2025-02-03 21:28 6027
1193b4be Poul Sander 2025-02-03 21:28 6028
    float H = col[0], S = col[1], V = col[2];
1193b4be Poul Sander 2025-02-03 21:28 6029
    float R = col[0], G = col[1], B = col[2];
1193b4be Poul Sander 2025-02-03 21:28 6030
    if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 6031
    {
1193b4be Poul Sander 2025-02-03 21:28 6032
        // Hue is lost when converting from grayscale rgb (saturation=0). Restore it.
1193b4be Poul Sander 2025-02-03 21:28 6033
        ColorConvertRGBtoHSV(R, G, B, H, S, V);
1193b4be Poul Sander 2025-02-03 21:28 6034
        ColorEditRestoreHS(col, &H, &S, &V);
1193b4be Poul Sander 2025-02-03 21:28 6035
    }
1193b4be Poul Sander 2025-02-03 21:28 6036
    else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6037
    {
1193b4be Poul Sander 2025-02-03 21:28 6038
        ColorConvertHSVtoRGB(H, S, V, R, G, B);
1193b4be Poul Sander 2025-02-03 21:28 6039
    }
1193b4be Poul Sander 2025-02-03 21:28 6040
1193b4be Poul Sander 2025-02-03 21:28 6041
    bool value_changed = false, value_changed_h = false, value_changed_sv = false;
1193b4be Poul Sander 2025-02-03 21:28 6042
1193b4be Poul Sander 2025-02-03 21:28 6043
    PushItemFlag(ImGuiItemFlags_NoNav, true);
1193b4be Poul Sander 2025-02-03 21:28 6044
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
1193b4be Poul Sander 2025-02-03 21:28 6045
    {
1193b4be Poul Sander 2025-02-03 21:28 6046
        // Hue wheel + SV triangle logic
1193b4be Poul Sander 2025-02-03 21:28 6047
        InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 6048
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 6049
        {
1193b4be Poul Sander 2025-02-03 21:28 6050
            ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
1193b4be Poul Sander 2025-02-03 21:28 6051
            ImVec2 current_off = g.IO.MousePos - wheel_center;
1193b4be Poul Sander 2025-02-03 21:28 6052
            float initial_dist2 = ImLengthSqr(initial_off);
1193b4be Poul Sander 2025-02-03 21:28 6053
            if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
1193b4be Poul Sander 2025-02-03 21:28 6054
            {
1193b4be Poul Sander 2025-02-03 21:28 6055
                // Interactive with Hue wheel
1193b4be Poul Sander 2025-02-03 21:28 6056
                H = ImAtan2(current_off.y, current_off.x) / IM_PI * 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 6057
                if (H < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 6058
                    H += 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 6059
                value_changed = value_changed_h = true;
1193b4be Poul Sander 2025-02-03 21:28 6060
            }
1193b4be Poul Sander 2025-02-03 21:28 6061
            float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 6062
            float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 6063
            if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
1193b4be Poul Sander 2025-02-03 21:28 6064
            {
1193b4be Poul Sander 2025-02-03 21:28 6065
                // Interacting with SV triangle
1193b4be Poul Sander 2025-02-03 21:28 6066
                ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6067
                if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
1193b4be Poul Sander 2025-02-03 21:28 6068
                    current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
1193b4be Poul Sander 2025-02-03 21:28 6069
                float uu, vv, ww;
1193b4be Poul Sander 2025-02-03 21:28 6070
                ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
1193b4be Poul Sander 2025-02-03 21:28 6071
                V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 6072
                S = ImClamp(uu / V, 0.0001f, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 6073
                value_changed = value_changed_sv = true;
1193b4be Poul Sander 2025-02-03 21:28 6074
            }
1193b4be Poul Sander 2025-02-03 21:28 6075
        }
1193b4be Poul Sander 2025-02-03 21:28 6076
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 6077
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 6078
    }
1193b4be Poul Sander 2025-02-03 21:28 6079
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
1193b4be Poul Sander 2025-02-03 21:28 6080
    {
1193b4be Poul Sander 2025-02-03 21:28 6081
        // SV rectangle logic
1193b4be Poul Sander 2025-02-03 21:28 6082
        InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 6083
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 6084
        {
1193b4be Poul Sander 2025-02-03 21:28 6085
            S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 6086
            V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 6087
            ColorEditRestoreH(col, &H); // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
1193b4be Poul Sander 2025-02-03 21:28 6088
            value_changed = value_changed_sv = true;
1193b4be Poul Sander 2025-02-03 21:28 6089
        }
1193b4be Poul Sander 2025-02-03 21:28 6090
        if (!(flags & ImGuiColorEditFlags_NoOptions))
1193b4be Poul Sander 2025-02-03 21:28 6091
            OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
1193b4be Poul Sander 2025-02-03 21:28 6092
1193b4be Poul Sander 2025-02-03 21:28 6093
        // Hue bar logic
1193b4be Poul Sander 2025-02-03 21:28 6094
        SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
1193b4be Poul Sander 2025-02-03 21:28 6095
        InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 6096
        if (IsItemActive() && !is_readonly)
1193b4be Poul Sander 2025-02-03 21:28 6097
        {
1193b4be Poul Sander 2025-02-03 21:28 6098
            H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 6099
            value_changed = value_changed_h = true;
1193b4be Poul Sander 2025-02-03 21:28 6100
        }
1193b4be Poul Sander 2025-02-03 21:28 6101
    }
1193b4be Poul Sander 2025-02-03 21:28 6102
1193b4be Poul Sander 2025-02-03 21:28 6103
    // Alpha bar logic
1193b4be Poul Sander 2025-02-03 21:28 6104
    if (alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 6105
    {
1193b4be Poul Sander 2025-02-03 21:28 6106
        SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
1193b4be Poul Sander 2025-02-03 21:28 6107
        InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
1193b4be Poul Sander 2025-02-03 21:28 6108
        if (IsItemActive())
1193b4be Poul Sander 2025-02-03 21:28 6109
        {
1193b4be Poul Sander 2025-02-03 21:28 6110
            col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
1193b4be Poul Sander 2025-02-03 21:28 6111
            value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 6112
        }
1193b4be Poul Sander 2025-02-03 21:28 6113
    }
1193b4be Poul Sander 2025-02-03 21:28 6114
    PopItemFlag(); // ImGuiItemFlags_NoNav
1193b4be Poul Sander 2025-02-03 21:28 6115
1193b4be Poul Sander 2025-02-03 21:28 6116
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 6117
    {
1193b4be Poul Sander 2025-02-03 21:28 6118
        SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 6119
        BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 6120
    }
1193b4be Poul Sander 2025-02-03 21:28 6121
1193b4be Poul Sander 2025-02-03 21:28 6122
    if (!(flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 6123
    {
1193b4be Poul Sander 2025-02-03 21:28 6124
        const char* label_display_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 6125
        if (label != label_display_end)
1193b4be Poul Sander 2025-02-03 21:28 6126
        {
1193b4be Poul Sander 2025-02-03 21:28 6127
            if ((flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 6128
                SameLine(0, style.ItemInnerSpacing.x);
1193b4be Poul Sander 2025-02-03 21:28 6129
            TextEx(label, label_display_end);
1193b4be Poul Sander 2025-02-03 21:28 6130
        }
1193b4be Poul Sander 2025-02-03 21:28 6131
    }
1193b4be Poul Sander 2025-02-03 21:28 6132
1193b4be Poul Sander 2025-02-03 21:28 6133
    if (!(flags & ImGuiColorEditFlags_NoSidePreview))
1193b4be Poul Sander 2025-02-03 21:28 6134
    {
1193b4be Poul Sander 2025-02-03 21:28 6135
        PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
1193b4be Poul Sander 2025-02-03 21:28 6136
        ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6137
        if ((flags & ImGuiColorEditFlags_NoLabel))
1193b4be Poul Sander 2025-02-03 21:28 6138
            Text("Current");
1193b4be Poul Sander 2025-02-03 21:28 6139
4e9e2555 Poul Sander 2025-10-05 10:15 6140
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoTooltip;
1193b4be Poul Sander 2025-02-03 21:28 6141
        ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
1193b4be Poul Sander 2025-02-03 21:28 6142
        if (ref_col != NULL)
1193b4be Poul Sander 2025-02-03 21:28 6143
        {
1193b4be Poul Sander 2025-02-03 21:28 6144
            Text("Original");
1193b4be Poul Sander 2025-02-03 21:28 6145
            ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6146
            if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
1193b4be Poul Sander 2025-02-03 21:28 6147
            {
1193b4be Poul Sander 2025-02-03 21:28 6148
                memcpy(col, ref_col, components * sizeof(float));
1193b4be Poul Sander 2025-02-03 21:28 6149
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 6150
            }
1193b4be Poul Sander 2025-02-03 21:28 6151
        }
1193b4be Poul Sander 2025-02-03 21:28 6152
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 6153
        EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 6154
    }
1193b4be Poul Sander 2025-02-03 21:28 6155
1193b4be Poul Sander 2025-02-03 21:28 6156
    // Convert back color to RGB
1193b4be Poul Sander 2025-02-03 21:28 6157
    if (value_changed_h || value_changed_sv)
1193b4be Poul Sander 2025-02-03 21:28 6158
    {
1193b4be Poul Sander 2025-02-03 21:28 6159
        if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 6160
        {
1193b4be Poul Sander 2025-02-03 21:28 6161
            ColorConvertHSVtoRGB(H, S, V, col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 6162
            g.ColorEditSavedHue = H;
1193b4be Poul Sander 2025-02-03 21:28 6163
            g.ColorEditSavedSat = S;
1193b4be Poul Sander 2025-02-03 21:28 6164
            g.ColorEditSavedID = g.ColorEditCurrentID;
1193b4be Poul Sander 2025-02-03 21:28 6165
            g.ColorEditSavedColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
1193b4be Poul Sander 2025-02-03 21:28 6166
        }
1193b4be Poul Sander 2025-02-03 21:28 6167
        else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6168
        {
1193b4be Poul Sander 2025-02-03 21:28 6169
            col[0] = H;
1193b4be Poul Sander 2025-02-03 21:28 6170
            col[1] = S;
1193b4be Poul Sander 2025-02-03 21:28 6171
            col[2] = V;
1193b4be Poul Sander 2025-02-03 21:28 6172
        }
1193b4be Poul Sander 2025-02-03 21:28 6173
    }
1193b4be Poul Sander 2025-02-03 21:28 6174
1193b4be Poul Sander 2025-02-03 21:28 6175
    // R,G,B and H,S,V slider color editor
1193b4be Poul Sander 2025-02-03 21:28 6176
    bool value_changed_fix_hue_wrap = false;
1193b4be Poul Sander 2025-02-03 21:28 6177
    if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6178
    {
1193b4be Poul Sander 2025-02-03 21:28 6179
        PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
4e9e2555 Poul Sander 2025-10-05 10:15 6180
        ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaMask_ | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoSmallPreview;
1193b4be Poul Sander 2025-02-03 21:28 6181
        ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
1193b4be Poul Sander 2025-02-03 21:28 6182
        if (flags & ImGuiColorEditFlags_DisplayRGB || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6183
            if (ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_DisplayRGB))
1193b4be Poul Sander 2025-02-03 21:28 6184
            {
1193b4be Poul Sander 2025-02-03 21:28 6185
                // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget.
1193b4be Poul Sander 2025-02-03 21:28 6186
                // 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)
1193b4be Poul Sander 2025-02-03 21:28 6187
                value_changed_fix_hue_wrap = (g.ActiveId != 0 && !g.ActiveIdAllowOverlap);
1193b4be Poul Sander 2025-02-03 21:28 6188
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 6189
            }
1193b4be Poul Sander 2025-02-03 21:28 6190
        if (flags & ImGuiColorEditFlags_DisplayHSV || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6191
            value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_DisplayHSV);
1193b4be Poul Sander 2025-02-03 21:28 6192
        if (flags & ImGuiColorEditFlags_DisplayHex || (flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6193
            value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_DisplayHex);
1193b4be Poul Sander 2025-02-03 21:28 6194
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 6195
    }
1193b4be Poul Sander 2025-02-03 21:28 6196
1193b4be Poul Sander 2025-02-03 21:28 6197
    // Try to cancel hue wrap (after ColorEdit4 call), if any
1193b4be Poul Sander 2025-02-03 21:28 6198
    if (value_changed_fix_hue_wrap && (flags & ImGuiColorEditFlags_InputRGB))
1193b4be Poul Sander 2025-02-03 21:28 6199
    {
1193b4be Poul Sander 2025-02-03 21:28 6200
        float new_H, new_S, new_V;
1193b4be Poul Sander 2025-02-03 21:28 6201
        ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
1193b4be Poul Sander 2025-02-03 21:28 6202
        if (new_H <= 0 && H > 0)
1193b4be Poul Sander 2025-02-03 21:28 6203
        {
1193b4be Poul Sander 2025-02-03 21:28 6204
            if (new_V <= 0 && V != new_V)
1193b4be Poul Sander 2025-02-03 21:28 6205
                ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 6206
            else if (new_S <= 0)
1193b4be Poul Sander 2025-02-03 21:28 6207
                ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 6208
        }
1193b4be Poul Sander 2025-02-03 21:28 6209
    }
1193b4be Poul Sander 2025-02-03 21:28 6210
1193b4be Poul Sander 2025-02-03 21:28 6211
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 6212
    {
1193b4be Poul Sander 2025-02-03 21:28 6213
        if (flags & ImGuiColorEditFlags_InputRGB)
1193b4be Poul Sander 2025-02-03 21:28 6214
        {
1193b4be Poul Sander 2025-02-03 21:28 6215
            R = col[0];
1193b4be Poul Sander 2025-02-03 21:28 6216
            G = col[1];
1193b4be Poul Sander 2025-02-03 21:28 6217
            B = col[2];
1193b4be Poul Sander 2025-02-03 21:28 6218
            ColorConvertRGBtoHSV(R, G, B, H, S, V);
1193b4be Poul Sander 2025-02-03 21:28 6219
            ColorEditRestoreHS(col, &H, &S, &V);   // Fix local Hue as display below will use it immediately.
1193b4be Poul Sander 2025-02-03 21:28 6220
        }
1193b4be Poul Sander 2025-02-03 21:28 6221
        else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6222
        {
1193b4be Poul Sander 2025-02-03 21:28 6223
            H = col[0];
1193b4be Poul Sander 2025-02-03 21:28 6224
            S = col[1];
1193b4be Poul Sander 2025-02-03 21:28 6225
            V = col[2];
1193b4be Poul Sander 2025-02-03 21:28 6226
            ColorConvertHSVtoRGB(H, S, V, R, G, B);
1193b4be Poul Sander 2025-02-03 21:28 6227
        }
1193b4be Poul Sander 2025-02-03 21:28 6228
    }
1193b4be Poul Sander 2025-02-03 21:28 6229
1193b4be Poul Sander 2025-02-03 21:28 6230
    const int style_alpha8 = IM_F32_TO_INT8_SAT(style.Alpha);
1193b4be Poul Sander 2025-02-03 21:28 6231
    const ImU32 col_black = IM_COL32(0,0,0,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 6232
    const ImU32 col_white = IM_COL32(255,255,255,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 6233
    const ImU32 col_midgrey = IM_COL32(128,128,128,style_alpha8);
1193b4be Poul Sander 2025-02-03 21:28 6234
    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) };
1193b4be Poul Sander 2025-02-03 21:28 6235
1193b4be Poul Sander 2025-02-03 21:28 6236
    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);
1193b4be Poul Sander 2025-02-03 21:28 6237
    ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
1193b4be Poul Sander 2025-02-03 21:28 6238
    ImU32 user_col32_striped_of_alpha = ColorConvertFloat4ToU32(ImVec4(R, G, B, style.Alpha)); // Important: this is still including the main rendering/style alpha!!
1193b4be Poul Sander 2025-02-03 21:28 6239
1193b4be Poul Sander 2025-02-03 21:28 6240
    ImVec2 sv_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 6241
1193b4be Poul Sander 2025-02-03 21:28 6242
    if (flags & ImGuiColorEditFlags_PickerHueWheel)
1193b4be Poul Sander 2025-02-03 21:28 6243
    {
1193b4be Poul Sander 2025-02-03 21:28 6244
        // Render Hue Wheel
1193b4be Poul Sander 2025-02-03 21:28 6245
        const float aeps = 0.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
1193b4be Poul Sander 2025-02-03 21:28 6246
        const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
1193b4be Poul Sander 2025-02-03 21:28 6247
        for (int n = 0; n < 6; n++)
1193b4be Poul Sander 2025-02-03 21:28 6248
        {
1193b4be Poul Sander 2025-02-03 21:28 6249
            const float a0 = (n)     /6.0f * 2.0f * IM_PI - aeps;
1193b4be Poul Sander 2025-02-03 21:28 6250
            const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
1193b4be Poul Sander 2025-02-03 21:28 6251
            const int vert_start_idx = draw_list->VtxBuffer.Size;
1193b4be Poul Sander 2025-02-03 21:28 6252
            draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
1193b4be Poul Sander 2025-02-03 21:28 6253
            draw_list->PathStroke(col_white, 0, wheel_thickness);
1193b4be Poul Sander 2025-02-03 21:28 6254
            const int vert_end_idx = draw_list->VtxBuffer.Size;
1193b4be Poul Sander 2025-02-03 21:28 6255
1193b4be Poul Sander 2025-02-03 21:28 6256
            // Paint colors over existing vertices
1193b4be Poul Sander 2025-02-03 21:28 6257
            ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
1193b4be Poul Sander 2025-02-03 21:28 6258
            ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
1193b4be Poul Sander 2025-02-03 21:28 6259
            ShadeVertsLinearColorGradientKeepAlpha(draw_list, vert_start_idx, vert_end_idx, gradient_p0, gradient_p1, col_hues[n], col_hues[n + 1]);
1193b4be Poul Sander 2025-02-03 21:28 6260
        }
1193b4be Poul Sander 2025-02-03 21:28 6261
1193b4be Poul Sander 2025-02-03 21:28 6262
        // Render Cursor + preview on Hue Wheel
1193b4be Poul Sander 2025-02-03 21:28 6263
        float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 6264
        float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
1193b4be Poul Sander 2025-02-03 21:28 6265
        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);
1193b4be Poul Sander 2025-02-03 21:28 6266
        float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
1193b4be Poul Sander 2025-02-03 21:28 6267
        int hue_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(hue_cursor_rad); // Lock segment count so the +1 one matches others.
1193b4be Poul Sander 2025-02-03 21:28 6268
        draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6269
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, col_midgrey, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6270
        draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, col_white, hue_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6271
1193b4be Poul Sander 2025-02-03 21:28 6272
        // Render SV triangle (rotated according to hue)
1193b4be Poul Sander 2025-02-03 21:28 6273
        ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6274
        ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6275
        ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
1193b4be Poul Sander 2025-02-03 21:28 6276
        ImVec2 uv_white = GetFontTexUvWhitePixel();
1193b4be Poul Sander 2025-02-03 21:28 6277
        draw_list->PrimReserve(3, 3);
1193b4be Poul Sander 2025-02-03 21:28 6278
        draw_list->PrimVtx(tra, uv_white, hue_color32);
1193b4be Poul Sander 2025-02-03 21:28 6279
        draw_list->PrimVtx(trb, uv_white, col_black);
1193b4be Poul Sander 2025-02-03 21:28 6280
        draw_list->PrimVtx(trc, uv_white, col_white);
1193b4be Poul Sander 2025-02-03 21:28 6281
        draw_list->AddTriangle(tra, trb, trc, col_midgrey, 1.5f);
1193b4be Poul Sander 2025-02-03 21:28 6282
        sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
1193b4be Poul Sander 2025-02-03 21:28 6283
    }
1193b4be Poul Sander 2025-02-03 21:28 6284
    else if (flags & ImGuiColorEditFlags_PickerHueBar)
1193b4be Poul Sander 2025-02-03 21:28 6285
    {
1193b4be Poul Sander 2025-02-03 21:28 6286
        // Render SV Square
1193b4be Poul Sander 2025-02-03 21:28 6287
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
1193b4be Poul Sander 2025-02-03 21:28 6288
        draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
1193b4be Poul Sander 2025-02-03 21:28 6289
        RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6290
        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
1193b4be Poul Sander 2025-02-03 21:28 6291
        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);
1193b4be Poul Sander 2025-02-03 21:28 6292
1193b4be Poul Sander 2025-02-03 21:28 6293
        // Render Hue Bar
1193b4be Poul Sander 2025-02-03 21:28 6294
        for (int i = 0; i < 6; ++i)
1193b4be Poul Sander 2025-02-03 21:28 6295
            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]);
1193b4be Poul Sander 2025-02-03 21:28 6296
        float bar0_line_y = IM_ROUND(picker_pos.y + H * sv_picker_size);
1193b4be Poul Sander 2025-02-03 21:28 6297
        RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6298
        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);
1193b4be Poul Sander 2025-02-03 21:28 6299
    }
1193b4be Poul Sander 2025-02-03 21:28 6300
1193b4be Poul Sander 2025-02-03 21:28 6301
    // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
1193b4be Poul Sander 2025-02-03 21:28 6302
    float sv_cursor_rad = value_changed_sv ? wheel_thickness * 0.55f : wheel_thickness * 0.40f;
1193b4be Poul Sander 2025-02-03 21:28 6303
    int sv_cursor_segments = draw_list->_CalcCircleAutoSegmentCount(sv_cursor_rad); // Lock segment count so the +1 one matches others.
1193b4be Poul Sander 2025-02-03 21:28 6304
    draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, user_col32_striped_of_alpha, sv_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6305
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, col_midgrey, sv_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6306
    draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, col_white, sv_cursor_segments);
1193b4be Poul Sander 2025-02-03 21:28 6307
1193b4be Poul Sander 2025-02-03 21:28 6308
    // Render alpha bar
1193b4be Poul Sander 2025-02-03 21:28 6309
    if (alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 6310
    {
1193b4be Poul Sander 2025-02-03 21:28 6311
        float alpha = ImSaturate(col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6312
        ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
1193b4be Poul Sander 2025-02-03 21:28 6313
        RenderColorRectWithAlphaCheckerboard(draw_list, bar1_bb.Min, bar1_bb.Max, 0, bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 6314
        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);
1193b4be Poul Sander 2025-02-03 21:28 6315
        float bar1_line_y = IM_ROUND(picker_pos.y + (1.0f - alpha) * sv_picker_size);
1193b4be Poul Sander 2025-02-03 21:28 6316
        RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6317
        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);
1193b4be Poul Sander 2025-02-03 21:28 6318
    }
1193b4be Poul Sander 2025-02-03 21:28 6319
1193b4be Poul Sander 2025-02-03 21:28 6320
    EndGroup();
1193b4be Poul Sander 2025-02-03 21:28 6321
1193b4be Poul Sander 2025-02-03 21:28 6322
    if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6323
        value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 6324
    if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
1193b4be Poul Sander 2025-02-03 21:28 6325
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 6326
1193b4be Poul Sander 2025-02-03 21:28 6327
    if (set_current_color_edit_id)
1193b4be Poul Sander 2025-02-03 21:28 6328
        g.ColorEditCurrentID = 0;
1193b4be Poul Sander 2025-02-03 21:28 6329
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 6330
1193b4be Poul Sander 2025-02-03 21:28 6331
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 6332
}
1193b4be Poul Sander 2025-02-03 21:28 6333
1193b4be Poul Sander 2025-02-03 21:28 6334
// A little color square. Return true when clicked.
1193b4be Poul Sander 2025-02-03 21:28 6335
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
1193b4be Poul Sander 2025-02-03 21:28 6336
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
1193b4be Poul Sander 2025-02-03 21:28 6337
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
1193b4be Poul Sander 2025-02-03 21:28 6338
bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 6339
{
1193b4be Poul Sander 2025-02-03 21:28 6340
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6341
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6342
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6343
1193b4be Poul Sander 2025-02-03 21:28 6344
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6345
    const ImGuiID id = window->GetID(desc_id);
1193b4be Poul Sander 2025-02-03 21:28 6346
    const float default_size = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 6347
    const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
1193b4be Poul Sander 2025-02-03 21:28 6348
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
1193b4be Poul Sander 2025-02-03 21:28 6349
    ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6350
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 6351
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6352
1193b4be Poul Sander 2025-02-03 21:28 6353
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 6354
    bool pressed = ButtonBehavior(bb, id, &hovered, &held);
1193b4be Poul Sander 2025-02-03 21:28 6355
4e9e2555 Poul Sander 2025-10-05 10:15 6356
    if (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaOpaque))
4e9e2555 Poul Sander 2025-10-05 10:15 6357
        flags &= ~(ImGuiColorEditFlags_AlphaNoBg | ImGuiColorEditFlags_AlphaPreviewHalf);
1193b4be Poul Sander 2025-02-03 21:28 6358
1193b4be Poul Sander 2025-02-03 21:28 6359
    ImVec4 col_rgb = col;
1193b4be Poul Sander 2025-02-03 21:28 6360
    if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6361
        ColorConvertHSVtoRGB(col_rgb.x, col_rgb.y, col_rgb.z, col_rgb.x, col_rgb.y, col_rgb.z);
1193b4be Poul Sander 2025-02-03 21:28 6362
1193b4be Poul Sander 2025-02-03 21:28 6363
    ImVec4 col_rgb_without_alpha(col_rgb.x, col_rgb.y, col_rgb.z, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 6364
    float grid_step = ImMin(size.x, size.y) / 2.99f;
1193b4be Poul Sander 2025-02-03 21:28 6365
    float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 6366
    ImRect bb_inner = bb;
1193b4be Poul Sander 2025-02-03 21:28 6367
    float off = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 6368
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6369
    {
1193b4be Poul Sander 2025-02-03 21:28 6370
        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.
1193b4be Poul Sander 2025-02-03 21:28 6371
        bb_inner.Expand(off);
1193b4be Poul Sander 2025-02-03 21:28 6372
    }
1193b4be Poul Sander 2025-02-03 21:28 6373
    if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col_rgb.w < 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 6374
    {
1193b4be Poul Sander 2025-02-03 21:28 6375
        float mid_x = IM_ROUND((bb_inner.Min.x + bb_inner.Max.x) * 0.5f);
4e9e2555 Poul Sander 2025-10-05 10:15 6376
        if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 6377
            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);
4e9e2555 Poul Sander 2025-10-05 10:15 6378
        else
4e9e2555 Poul Sander 2025-10-05 10:15 6379
            window->DrawList->AddRectFilled(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight);
1193b4be Poul Sander 2025-02-03 21:28 6380
        window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft);
1193b4be Poul Sander 2025-02-03 21:28 6381
    }
1193b4be Poul Sander 2025-02-03 21:28 6382
    else
1193b4be Poul Sander 2025-02-03 21:28 6383
    {
1193b4be Poul Sander 2025-02-03 21:28 6384
        // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
4e9e2555 Poul Sander 2025-10-05 10:15 6385
        ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaOpaque) ? col_rgb_without_alpha : col_rgb;
4e9e2555 Poul Sander 2025-10-05 10:15 6386
        if (col_source.w < 1.0f && (flags & ImGuiColorEditFlags_AlphaNoBg) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6387
            RenderColorRectWithAlphaCheckerboard(window->DrawList, bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
1193b4be Poul Sander 2025-02-03 21:28 6388
        else
1193b4be Poul Sander 2025-02-03 21:28 6389
            window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding);
1193b4be Poul Sander 2025-02-03 21:28 6390
    }
1193b4be Poul Sander 2025-02-03 21:28 6391
    RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6392
    if ((flags & ImGuiColorEditFlags_NoBorder) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6393
    {
1193b4be Poul Sander 2025-02-03 21:28 6394
        if (g.Style.FrameBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 6395
            RenderFrameBorder(bb.Min, bb.Max, rounding);
1193b4be Poul Sander 2025-02-03 21:28 6396
        else
4e9e2555 Poul Sander 2025-10-05 10:15 6397
            window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border // FIXME-DPI
1193b4be Poul Sander 2025-02-03 21:28 6398
    }
1193b4be Poul Sander 2025-02-03 21:28 6399
1193b4be Poul Sander 2025-02-03 21:28 6400
    // Drag and Drop Source
1193b4be Poul Sander 2025-02-03 21:28 6401
    // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
1193b4be Poul Sander 2025-02-03 21:28 6402
    if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
1193b4be Poul Sander 2025-02-03 21:28 6403
    {
1193b4be Poul Sander 2025-02-03 21:28 6404
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6405
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col_rgb, sizeof(float) * 3, ImGuiCond_Once);
1193b4be Poul Sander 2025-02-03 21:28 6406
        else
1193b4be Poul Sander 2025-02-03 21:28 6407
            SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col_rgb, sizeof(float) * 4, ImGuiCond_Once);
1193b4be Poul Sander 2025-02-03 21:28 6408
        ColorButton(desc_id, col, flags);
1193b4be Poul Sander 2025-02-03 21:28 6409
        SameLine();
1193b4be Poul Sander 2025-02-03 21:28 6410
        TextEx("Color");
1193b4be Poul Sander 2025-02-03 21:28 6411
        EndDragDropSource();
1193b4be Poul Sander 2025-02-03 21:28 6412
    }
1193b4be Poul Sander 2025-02-03 21:28 6413
1193b4be Poul Sander 2025-02-03 21:28 6414
    // Tooltip
1193b4be Poul Sander 2025-02-03 21:28 6415
    if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered && IsItemHovered(ImGuiHoveredFlags_ForTooltip))
4e9e2555 Poul Sander 2025-10-05 10:15 6416
        ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_));
1193b4be Poul Sander 2025-02-03 21:28 6417
1193b4be Poul Sander 2025-02-03 21:28 6418
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 6419
}
1193b4be Poul Sander 2025-02-03 21:28 6420
1193b4be Poul Sander 2025-02-03 21:28 6421
// Initialize/override default color options
1193b4be Poul Sander 2025-02-03 21:28 6422
void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6423
{
1193b4be Poul Sander 2025-02-03 21:28 6424
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6425
    if ((flags & ImGuiColorEditFlags_DisplayMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6426
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DisplayMask_;
1193b4be Poul Sander 2025-02-03 21:28 6427
    if ((flags & ImGuiColorEditFlags_DataTypeMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6428
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_DataTypeMask_;
1193b4be Poul Sander 2025-02-03 21:28 6429
    if ((flags & ImGuiColorEditFlags_PickerMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6430
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_PickerMask_;
1193b4be Poul Sander 2025-02-03 21:28 6431
    if ((flags & ImGuiColorEditFlags_InputMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6432
        flags |= ImGuiColorEditFlags_DefaultOptions_ & ImGuiColorEditFlags_InputMask_;
1193b4be Poul Sander 2025-02-03 21:28 6433
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DisplayMask_));    // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6434
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_DataTypeMask_));   // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6435
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_PickerMask_));     // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6436
    IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiColorEditFlags_InputMask_));      // Check only 1 option is selected
1193b4be Poul Sander 2025-02-03 21:28 6437
    g.ColorEditOptions = flags;
1193b4be Poul Sander 2025-02-03 21:28 6438
}
1193b4be Poul Sander 2025-02-03 21:28 6439
1193b4be Poul Sander 2025-02-03 21:28 6440
// Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
1193b4be Poul Sander 2025-02-03 21:28 6441
void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6442
{
1193b4be Poul Sander 2025-02-03 21:28 6443
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6444
1193b4be Poul Sander 2025-02-03 21:28 6445
    if (!BeginTooltipEx(ImGuiTooltipFlags_OverridePrevious, ImGuiWindowFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 6446
        return;
1193b4be Poul Sander 2025-02-03 21:28 6447
    const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
1193b4be Poul Sander 2025-02-03 21:28 6448
    if (text_end > text)
1193b4be Poul Sander 2025-02-03 21:28 6449
    {
1193b4be Poul Sander 2025-02-03 21:28 6450
        TextEx(text, text_end);
1193b4be Poul Sander 2025-02-03 21:28 6451
        Separator();
1193b4be Poul Sander 2025-02-03 21:28 6452
    }
1193b4be Poul Sander 2025-02-03 21:28 6453
1193b4be Poul Sander 2025-02-03 21:28 6454
    ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
1193b4be Poul Sander 2025-02-03 21:28 6455
    ImVec4 cf(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6456
    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]);
4e9e2555 Poul Sander 2025-10-05 10:15 6457
    ImGuiColorEditFlags flags_to_forward = ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_AlphaMask_;
4e9e2555 Poul Sander 2025-10-05 10:15 6458
    ColorButton("##preview", cf, (flags & flags_to_forward) | ImGuiColorEditFlags_NoTooltip, sz);
1193b4be Poul Sander 2025-02-03 21:28 6459
    SameLine();
1193b4be Poul Sander 2025-02-03 21:28 6460
    if ((flags & ImGuiColorEditFlags_InputRGB) || !(flags & ImGuiColorEditFlags_InputMask_))
1193b4be Poul Sander 2025-02-03 21:28 6461
    {
1193b4be Poul Sander 2025-02-03 21:28 6462
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6463
            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]);
1193b4be Poul Sander 2025-02-03 21:28 6464
        else
1193b4be Poul Sander 2025-02-03 21:28 6465
            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]);
1193b4be Poul Sander 2025-02-03 21:28 6466
    }
1193b4be Poul Sander 2025-02-03 21:28 6467
    else if (flags & ImGuiColorEditFlags_InputHSV)
1193b4be Poul Sander 2025-02-03 21:28 6468
    {
1193b4be Poul Sander 2025-02-03 21:28 6469
        if (flags & ImGuiColorEditFlags_NoAlpha)
1193b4be Poul Sander 2025-02-03 21:28 6470
            Text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]);
1193b4be Poul Sander 2025-02-03 21:28 6471
        else
1193b4be Poul Sander 2025-02-03 21:28 6472
            Text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6473
    }
1193b4be Poul Sander 2025-02-03 21:28 6474
    EndTooltip();
1193b4be Poul Sander 2025-02-03 21:28 6475
}
1193b4be Poul Sander 2025-02-03 21:28 6476
1193b4be Poul Sander 2025-02-03 21:28 6477
void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6478
{
1193b4be Poul Sander 2025-02-03 21:28 6479
    bool allow_opt_inputs = !(flags & ImGuiColorEditFlags_DisplayMask_);
1193b4be Poul Sander 2025-02-03 21:28 6480
    bool allow_opt_datatype = !(flags & ImGuiColorEditFlags_DataTypeMask_);
1193b4be Poul Sander 2025-02-03 21:28 6481
    if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
1193b4be Poul Sander 2025-02-03 21:28 6482
        return;
1193b4be Poul Sander 2025-02-03 21:28 6483
1193b4be Poul Sander 2025-02-03 21:28 6484
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6485
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
1193b4be Poul Sander 2025-02-03 21:28 6486
    ImGuiColorEditFlags opts = g.ColorEditOptions;
1193b4be Poul Sander 2025-02-03 21:28 6487
    if (allow_opt_inputs)
1193b4be Poul Sander 2025-02-03 21:28 6488
    {
1193b4be Poul Sander 2025-02-03 21:28 6489
        if (RadioButton("RGB", (opts & ImGuiColorEditFlags_DisplayRGB) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayRGB;
1193b4be Poul Sander 2025-02-03 21:28 6490
        if (RadioButton("HSV", (opts & ImGuiColorEditFlags_DisplayHSV) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHSV;
1193b4be Poul Sander 2025-02-03 21:28 6491
        if (RadioButton("Hex", (opts & ImGuiColorEditFlags_DisplayHex) != 0)) opts = (opts & ~ImGuiColorEditFlags_DisplayMask_) | ImGuiColorEditFlags_DisplayHex;
1193b4be Poul Sander 2025-02-03 21:28 6492
    }
1193b4be Poul Sander 2025-02-03 21:28 6493
    if (allow_opt_datatype)
1193b4be Poul Sander 2025-02-03 21:28 6494
    {
1193b4be Poul Sander 2025-02-03 21:28 6495
        if (allow_opt_inputs) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6496
        if (RadioButton("0..255",     (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Uint8;
1193b4be Poul Sander 2025-02-03 21:28 6497
        if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags_DataTypeMask_) | ImGuiColorEditFlags_Float;
1193b4be Poul Sander 2025-02-03 21:28 6498
    }
1193b4be Poul Sander 2025-02-03 21:28 6499
1193b4be Poul Sander 2025-02-03 21:28 6500
    if (allow_opt_inputs || allow_opt_datatype)
1193b4be Poul Sander 2025-02-03 21:28 6501
        Separator();
1193b4be Poul Sander 2025-02-03 21:28 6502
    if (Button("Copy as..", ImVec2(-1, 0)))
1193b4be Poul Sander 2025-02-03 21:28 6503
        OpenPopup("Copy");
1193b4be Poul Sander 2025-02-03 21:28 6504
    if (BeginPopup("Copy"))
1193b4be Poul Sander 2025-02-03 21:28 6505
    {
1193b4be Poul Sander 2025-02-03 21:28 6506
        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]);
1193b4be Poul Sander 2025-02-03 21:28 6507
        char buf[64];
1193b4be Poul Sander 2025-02-03 21:28 6508
        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
1193b4be Poul Sander 2025-02-03 21:28 6509
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6510
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6511
        ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
1193b4be Poul Sander 2025-02-03 21:28 6512
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6513
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6514
        ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", cr, cg, cb);
1193b4be Poul Sander 2025-02-03 21:28 6515
        if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6516
            SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6517
        if (!(flags & ImGuiColorEditFlags_NoAlpha))
1193b4be Poul Sander 2025-02-03 21:28 6518
        {
1193b4be Poul Sander 2025-02-03 21:28 6519
            ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", cr, cg, cb, ca);
1193b4be Poul Sander 2025-02-03 21:28 6520
            if (Selectable(buf))
1193b4be Poul Sander 2025-02-03 21:28 6521
                SetClipboardText(buf);
1193b4be Poul Sander 2025-02-03 21:28 6522
        }
1193b4be Poul Sander 2025-02-03 21:28 6523
        EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 6524
    }
1193b4be Poul Sander 2025-02-03 21:28 6525
1193b4be Poul Sander 2025-02-03 21:28 6526
    g.ColorEditOptions = opts;
1193b4be Poul Sander 2025-02-03 21:28 6527
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 6528
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 6529
}
1193b4be Poul Sander 2025-02-03 21:28 6530
1193b4be Poul Sander 2025-02-03 21:28 6531
void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6532
{
1193b4be Poul Sander 2025-02-03 21:28 6533
    bool allow_opt_picker = !(flags & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 6534
    bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 6535
    if ((!allow_opt_picker && !allow_opt_alpha_bar) || !BeginPopup("context"))
1193b4be Poul Sander 2025-02-03 21:28 6536
        return;
1193b4be Poul Sander 2025-02-03 21:28 6537
1193b4be Poul Sander 2025-02-03 21:28 6538
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6539
    PushItemFlag(ImGuiItemFlags_NoMarkEdited, true);
1193b4be Poul Sander 2025-02-03 21:28 6540
    if (allow_opt_picker)
1193b4be Poul Sander 2025-02-03 21:28 6541
    {
1193b4be Poul Sander 2025-02-03 21:28 6542
        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
1193b4be Poul Sander 2025-02-03 21:28 6543
        PushItemWidth(picker_size.x);
1193b4be Poul Sander 2025-02-03 21:28 6544
        for (int picker_type = 0; picker_type < 2; picker_type++)
1193b4be Poul Sander 2025-02-03 21:28 6545
        {
1193b4be Poul Sander 2025-02-03 21:28 6546
            // Draw small/thumbnail version of each picker type (over an invisible button for selection)
1193b4be Poul Sander 2025-02-03 21:28 6547
            if (picker_type > 0) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6548
            PushID(picker_type);
1193b4be Poul Sander 2025-02-03 21:28 6549
            ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
1193b4be Poul Sander 2025-02-03 21:28 6550
            if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
1193b4be Poul Sander 2025-02-03 21:28 6551
            if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
1193b4be Poul Sander 2025-02-03 21:28 6552
            ImVec2 backup_pos = GetCursorScreenPos();
1193b4be Poul Sander 2025-02-03 21:28 6553
            if (Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
1193b4be Poul Sander 2025-02-03 21:28 6554
                g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags_PickerMask_) | (picker_flags & ImGuiColorEditFlags_PickerMask_);
1193b4be Poul Sander 2025-02-03 21:28 6555
            SetCursorScreenPos(backup_pos);
1193b4be Poul Sander 2025-02-03 21:28 6556
            ImVec4 previewing_ref_col;
1193b4be Poul Sander 2025-02-03 21:28 6557
            memcpy(&previewing_ref_col, ref_col, sizeof(float) * ((picker_flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4));
1193b4be Poul Sander 2025-02-03 21:28 6558
            ColorPicker4("##previewing_picker", &previewing_ref_col.x, picker_flags);
1193b4be Poul Sander 2025-02-03 21:28 6559
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 6560
        }
1193b4be Poul Sander 2025-02-03 21:28 6561
        PopItemWidth();
1193b4be Poul Sander 2025-02-03 21:28 6562
    }
1193b4be Poul Sander 2025-02-03 21:28 6563
    if (allow_opt_alpha_bar)
1193b4be Poul Sander 2025-02-03 21:28 6564
    {
1193b4be Poul Sander 2025-02-03 21:28 6565
        if (allow_opt_picker) Separator();
1193b4be Poul Sander 2025-02-03 21:28 6566
        CheckboxFlags("Alpha Bar", &g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
1193b4be Poul Sander 2025-02-03 21:28 6567
    }
1193b4be Poul Sander 2025-02-03 21:28 6568
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 6569
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 6570
}
1193b4be Poul Sander 2025-02-03 21:28 6571
1193b4be Poul Sander 2025-02-03 21:28 6572
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6573
// [SECTION] Widgets: TreeNode, CollapsingHeader, etc.
1193b4be Poul Sander 2025-02-03 21:28 6574
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6575
// - TreeNode()
1193b4be Poul Sander 2025-02-03 21:28 6576
// - TreeNodeV()
1193b4be Poul Sander 2025-02-03 21:28 6577
// - TreeNodeEx()
1193b4be Poul Sander 2025-02-03 21:28 6578
// - TreeNodeExV()
4e9e2555 Poul Sander 2025-10-05 10:15 6579
// - TreeNodeStoreStackData() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 6580
// - TreeNodeBehavior() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 6581
// - TreePush()
1193b4be Poul Sander 2025-02-03 21:28 6582
// - TreePop()
1193b4be Poul Sander 2025-02-03 21:28 6583
// - GetTreeNodeToLabelSpacing()
1193b4be Poul Sander 2025-02-03 21:28 6584
// - SetNextItemOpen()
1193b4be Poul Sander 2025-02-03 21:28 6585
// - CollapsingHeader()
1193b4be Poul Sander 2025-02-03 21:28 6586
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 6587
1193b4be Poul Sander 2025-02-03 21:28 6588
bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6589
{
1193b4be Poul Sander 2025-02-03 21:28 6590
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6591
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6592
    bool is_open = TreeNodeExV(str_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6593
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6594
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6595
}
1193b4be Poul Sander 2025-02-03 21:28 6596
1193b4be Poul Sander 2025-02-03 21:28 6597
bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6598
{
1193b4be Poul Sander 2025-02-03 21:28 6599
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6600
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6601
    bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6602
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6603
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6604
}
1193b4be Poul Sander 2025-02-03 21:28 6605
1193b4be Poul Sander 2025-02-03 21:28 6606
bool ImGui::TreeNode(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 6607
{
1193b4be Poul Sander 2025-02-03 21:28 6608
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6609
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6610
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6611
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6612
    return TreeNodeBehavior(id, ImGuiTreeNodeFlags_None, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 6613
}
1193b4be Poul Sander 2025-02-03 21:28 6614
1193b4be Poul Sander 2025-02-03 21:28 6615
bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6616
{
1193b4be Poul Sander 2025-02-03 21:28 6617
    return TreeNodeExV(str_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6618
}
1193b4be Poul Sander 2025-02-03 21:28 6619
1193b4be Poul Sander 2025-02-03 21:28 6620
bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6621
{
1193b4be Poul Sander 2025-02-03 21:28 6622
    return TreeNodeExV(ptr_id, 0, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6623
}
1193b4be Poul Sander 2025-02-03 21:28 6624
1193b4be Poul Sander 2025-02-03 21:28 6625
bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6626
{
1193b4be Poul Sander 2025-02-03 21:28 6627
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6628
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6629
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6630
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 6631
    return TreeNodeBehavior(id, flags, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 6632
}
1193b4be Poul Sander 2025-02-03 21:28 6633
1193b4be Poul Sander 2025-02-03 21:28 6634
bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6635
{
1193b4be Poul Sander 2025-02-03 21:28 6636
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6637
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6638
    bool is_open = TreeNodeExV(str_id, flags, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6639
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6640
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6641
}
1193b4be Poul Sander 2025-02-03 21:28 6642
1193b4be Poul Sander 2025-02-03 21:28 6643
bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
1193b4be Poul Sander 2025-02-03 21:28 6644
{
1193b4be Poul Sander 2025-02-03 21:28 6645
    va_list args;
1193b4be Poul Sander 2025-02-03 21:28 6646
    va_start(args, fmt);
1193b4be Poul Sander 2025-02-03 21:28 6647
    bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6648
    va_end(args);
1193b4be Poul Sander 2025-02-03 21:28 6649
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6650
}
1193b4be Poul Sander 2025-02-03 21:28 6651
1193b4be Poul Sander 2025-02-03 21:28 6652
bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6653
{
1193b4be Poul Sander 2025-02-03 21:28 6654
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6655
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6656
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6657
1193b4be Poul Sander 2025-02-03 21:28 6658
    ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 6659
    const char* label, *label_end;
1193b4be Poul Sander 2025-02-03 21:28 6660
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6661
    return TreeNodeBehavior(id, flags, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 6662
}
1193b4be Poul Sander 2025-02-03 21:28 6663
1193b4be Poul Sander 2025-02-03 21:28 6664
bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
1193b4be Poul Sander 2025-02-03 21:28 6665
{
1193b4be Poul Sander 2025-02-03 21:28 6666
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6667
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6668
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6669
1193b4be Poul Sander 2025-02-03 21:28 6670
    ImGuiID id = window->GetID(ptr_id);
1193b4be Poul Sander 2025-02-03 21:28 6671
    const char* label, *label_end;
1193b4be Poul Sander 2025-02-03 21:28 6672
    ImFormatStringToTempBufferV(&label, &label_end, fmt, args);
1193b4be Poul Sander 2025-02-03 21:28 6673
    return TreeNodeBehavior(id, flags, label, label_end);
1193b4be Poul Sander 2025-02-03 21:28 6674
}
1193b4be Poul Sander 2025-02-03 21:28 6675
1193b4be Poul Sander 2025-02-03 21:28 6676
bool ImGui::TreeNodeGetOpen(ImGuiID storage_id)
1193b4be Poul Sander 2025-02-03 21:28 6677
{
1193b4be Poul Sander 2025-02-03 21:28 6678
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6679
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6680
    return storage->GetInt(storage_id, 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6681
}
1193b4be Poul Sander 2025-02-03 21:28 6682
1193b4be Poul Sander 2025-02-03 21:28 6683
void ImGui::TreeNodeSetOpen(ImGuiID storage_id, bool open)
1193b4be Poul Sander 2025-02-03 21:28 6684
{
1193b4be Poul Sander 2025-02-03 21:28 6685
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6686
    ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6687
    storage->SetInt(storage_id, open ? 1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 6688
}
1193b4be Poul Sander 2025-02-03 21:28 6689
1193b4be Poul Sander 2025-02-03 21:28 6690
bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 6691
{
1193b4be Poul Sander 2025-02-03 21:28 6692
    if (flags & ImGuiTreeNodeFlags_Leaf)
1193b4be Poul Sander 2025-02-03 21:28 6693
        return true;
1193b4be Poul Sander 2025-02-03 21:28 6694
1193b4be Poul Sander 2025-02-03 21:28 6695
    // We only write to the tree storage if the user clicks, or explicitly use the SetNextItemOpen function
1193b4be Poul Sander 2025-02-03 21:28 6696
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6697
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6698
    ImGuiStorage* storage = window->DC.StateStorage;
1193b4be Poul Sander 2025-02-03 21:28 6699
1193b4be Poul Sander 2025-02-03 21:28 6700
    bool is_open;
1193b4be Poul Sander 2025-02-03 21:28 6701
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasOpen)
1193b4be Poul Sander 2025-02-03 21:28 6702
    {
1193b4be Poul Sander 2025-02-03 21:28 6703
        if (g.NextItemData.OpenCond & ImGuiCond_Always)
1193b4be Poul Sander 2025-02-03 21:28 6704
        {
1193b4be Poul Sander 2025-02-03 21:28 6705
            is_open = g.NextItemData.OpenVal;
1193b4be Poul Sander 2025-02-03 21:28 6706
            TreeNodeSetOpen(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6707
        }
1193b4be Poul Sander 2025-02-03 21:28 6708
        else
1193b4be Poul Sander 2025-02-03 21:28 6709
        {
1193b4be Poul Sander 2025-02-03 21:28 6710
            // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
1193b4be Poul Sander 2025-02-03 21:28 6711
            const int stored_value = storage->GetInt(storage_id, -1);
1193b4be Poul Sander 2025-02-03 21:28 6712
            if (stored_value == -1)
1193b4be Poul Sander 2025-02-03 21:28 6713
            {
1193b4be Poul Sander 2025-02-03 21:28 6714
                is_open = g.NextItemData.OpenVal;
1193b4be Poul Sander 2025-02-03 21:28 6715
                TreeNodeSetOpen(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6716
            }
1193b4be Poul Sander 2025-02-03 21:28 6717
            else
1193b4be Poul Sander 2025-02-03 21:28 6718
            {
1193b4be Poul Sander 2025-02-03 21:28 6719
                is_open = stored_value != 0;
1193b4be Poul Sander 2025-02-03 21:28 6720
            }
1193b4be Poul Sander 2025-02-03 21:28 6721
        }
1193b4be Poul Sander 2025-02-03 21:28 6722
    }
1193b4be Poul Sander 2025-02-03 21:28 6723
    else
1193b4be Poul Sander 2025-02-03 21:28 6724
    {
1193b4be Poul Sander 2025-02-03 21:28 6725
        is_open = storage->GetInt(storage_id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6726
    }
1193b4be Poul Sander 2025-02-03 21:28 6727
1193b4be Poul Sander 2025-02-03 21:28 6728
    // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
1193b4be Poul Sander 2025-02-03 21:28 6729
    // NB- If we are above max depth we still allow manually opened nodes to be logged.
1193b4be Poul Sander 2025-02-03 21:28 6730
    if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && (window->DC.TreeDepth - g.LogDepthRef) < g.LogDepthToExpand)
1193b4be Poul Sander 2025-02-03 21:28 6731
        is_open = true;
1193b4be Poul Sander 2025-02-03 21:28 6732
1193b4be Poul Sander 2025-02-03 21:28 6733
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6734
}
1193b4be Poul Sander 2025-02-03 21:28 6735
1193b4be Poul Sander 2025-02-03 21:28 6736
// Store ImGuiTreeNodeStackData for just submitted node.
1193b4be Poul Sander 2025-02-03 21:28 6737
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
4e9e2555 Poul Sander 2025-10-05 10:15 6738
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1)
1193b4be Poul Sander 2025-02-03 21:28 6739
{
1193b4be Poul Sander 2025-02-03 21:28 6740
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6741
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 6742
1193b4be Poul Sander 2025-02-03 21:28 6743
    g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
4e9e2555 Poul Sander 2025-10-05 10:15 6744
    ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
1193b4be Poul Sander 2025-02-03 21:28 6745
    tree_node_data->ID = g.LastItemData.ID;
1193b4be Poul Sander 2025-02-03 21:28 6746
    tree_node_data->TreeFlags = flags;
1193b4be Poul Sander 2025-02-03 21:28 6747
    tree_node_data->ItemFlags = g.LastItemData.ItemFlags;
1193b4be Poul Sander 2025-02-03 21:28 6748
    tree_node_data->NavRect = g.LastItemData.NavRect;
4e9e2555 Poul Sander 2025-10-05 10:15 6749
4e9e2555 Poul Sander 2025-10-05 10:15 6750
    // Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees.
4e9e2555 Poul Sander 2025-10-05 10:15 6751
    const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0;
4e9e2555 Poul Sander 2025-10-05 10:15 6752
    tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX;
4e9e2555 Poul Sander 2025-10-05 10:15 6753
    tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1;
4e9e2555 Poul Sander 2025-10-05 10:15 6754
    tree_node_data->DrawLinesToNodesY2 = -FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 6755
    window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
4e9e2555 Poul Sander 2025-10-05 10:15 6756
    if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes)
4e9e2555 Poul Sander 2025-10-05 10:15 6757
        window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth);
1193b4be Poul Sander 2025-02-03 21:28 6758
}
1193b4be Poul Sander 2025-02-03 21:28 6759
1193b4be Poul Sander 2025-02-03 21:28 6760
// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.
1193b4be Poul Sander 2025-02-03 21:28 6761
bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
1193b4be Poul Sander 2025-02-03 21:28 6762
{
1193b4be Poul Sander 2025-02-03 21:28 6763
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 6764
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 6765
        return false;
1193b4be Poul Sander 2025-02-03 21:28 6766
1193b4be Poul Sander 2025-02-03 21:28 6767
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 6768
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 6769
    const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6770
    const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, ImMin(window->DC.CurrLineTextBaseOffset, style.FramePadding.y));
1193b4be Poul Sander 2025-02-03 21:28 6771
1193b4be Poul Sander 2025-02-03 21:28 6772
    if (!label_end)
1193b4be Poul Sander 2025-02-03 21:28 6773
        label_end = FindRenderedTextEnd(label);
1193b4be Poul Sander 2025-02-03 21:28 6774
    const ImVec2 label_size = CalcTextSize(label, label_end, false);
1193b4be Poul Sander 2025-02-03 21:28 6775
1193b4be Poul Sander 2025-02-03 21:28 6776
    const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2);   // Collapsing arrow width + Spacing
1193b4be Poul Sander 2025-02-03 21:28 6777
    const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset);            // Latch before ItemSize changes it
1193b4be Poul Sander 2025-02-03 21:28 6778
    const float text_width = g.FontSize + label_size.x + padding.x * 2;                         // Include collapsing arrow
1193b4be Poul Sander 2025-02-03 21:28 6779
1193b4be Poul Sander 2025-02-03 21:28 6780
    // We vertically grow up to current line height up the typical widget height.
1193b4be Poul Sander 2025-02-03 21:28 6781
    const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
1193b4be Poul Sander 2025-02-03 21:28 6782
    const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL);
4e9e2555 Poul Sander 2025-10-05 10:15 6783
    const bool span_all_columns_label = (flags & ImGuiTreeNodeFlags_LabelSpanAllColumns) != 0 && (g.CurrentTable != NULL);
1193b4be Poul Sander 2025-02-03 21:28 6784
    ImRect frame_bb;
1193b4be Poul Sander 2025-02-03 21:28 6785
    frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x;
1193b4be Poul Sander 2025-02-03 21:28 6786
    frame_bb.Min.y = window->DC.CursorPos.y;
4e9e2555 Poul Sander 2025-10-05 10:15 6787
    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;
1193b4be Poul Sander 2025-02-03 21:28 6788
    frame_bb.Max.y = window->DC.CursorPos.y + frame_height;
1193b4be Poul Sander 2025-02-03 21:28 6789
    if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 6790
    {
1193b4be Poul Sander 2025-02-03 21:28 6791
        const float outer_extend = IM_TRUNC(window->WindowPadding.x * 0.5f); // Framed header expand a little outside of current limits
1193b4be Poul Sander 2025-02-03 21:28 6792
        frame_bb.Min.x -= outer_extend;
1193b4be Poul Sander 2025-02-03 21:28 6793
        frame_bb.Max.x += outer_extend;
1193b4be Poul Sander 2025-02-03 21:28 6794
    }
1193b4be Poul Sander 2025-02-03 21:28 6795
1193b4be Poul Sander 2025-02-03 21:28 6796
    ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y);
1193b4be Poul Sander 2025-02-03 21:28 6797
    ItemSize(ImVec2(text_width, frame_height), padding.y);
1193b4be Poul Sander 2025-02-03 21:28 6798
1193b4be Poul Sander 2025-02-03 21:28 6799
    // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
1193b4be Poul Sander 2025-02-03 21:28 6800
    ImRect interact_bb = frame_bb;
4e9e2555 Poul Sander 2025-10-05 10:15 6801
    if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanLabelWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 6802
        interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6803
1193b4be Poul Sander 2025-02-03 21:28 6804
    // Compute open and multi-select states before ItemAdd() as it clear NextItem data.
1193b4be Poul Sander 2025-02-03 21:28 6805
    ImGuiID storage_id = (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasStorageID) ? g.NextItemData.StorageId : id;
1193b4be Poul Sander 2025-02-03 21:28 6806
    bool is_open = TreeNodeUpdateNextOpen(storage_id, flags);
1193b4be Poul Sander 2025-02-03 21:28 6807
1193b4be Poul Sander 2025-02-03 21:28 6808
    bool is_visible;
4e9e2555 Poul Sander 2025-10-05 10:15 6809
    if (span_all_columns || span_all_columns_label)
1193b4be Poul Sander 2025-02-03 21:28 6810
    {
1193b4be Poul Sander 2025-02-03 21:28 6811
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
1193b4be Poul Sander 2025-02-03 21:28 6812
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6813
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6814
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 6815
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 6816
        is_visible = ItemAdd(interact_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6817
        window->ClipRect.Min.x = backup_clip_rect_min_x;
1193b4be Poul Sander 2025-02-03 21:28 6818
        window->ClipRect.Max.x = backup_clip_rect_max_x;
1193b4be Poul Sander 2025-02-03 21:28 6819
    }
1193b4be Poul Sander 2025-02-03 21:28 6820
    else
1193b4be Poul Sander 2025-02-03 21:28 6821
    {
1193b4be Poul Sander 2025-02-03 21:28 6822
        is_visible = ItemAdd(interact_bb, id);
1193b4be Poul Sander 2025-02-03 21:28 6823
    }
1193b4be Poul Sander 2025-02-03 21:28 6824
    g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
1193b4be Poul Sander 2025-02-03 21:28 6825
    g.LastItemData.DisplayRect = frame_bb;
1193b4be Poul Sander 2025-02-03 21:28 6826
4e9e2555 Poul Sander 2025-10-05 10:15 6827
    // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsToParent enabled:
1193b4be Poul Sander 2025-02-03 21:28 6828
    // Store data for the current depth to allow returning to this node from any child item.
1193b4be Poul Sander 2025-02-03 21:28 6829
    // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
4e9e2555 Poul Sander 2025-10-05 10:15 6830
    // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsToParent by default or move it to ImGuiStyle.
1193b4be Poul Sander 2025-02-03 21:28 6831
    bool store_tree_node_stack_data = false;
4e9e2555 Poul Sander 2025-10-05 10:15 6832
    if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 6833
        flags |= g.Style.TreeLinesFlags;
4e9e2555 Poul Sander 2025-10-05 10:15 6834
    const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 6835
    if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 6836
    {
4e9e2555 Poul Sander 2025-10-05 10:15 6837
        store_tree_node_stack_data = draw_tree_lines;
4e9e2555 Poul Sander 2025-10-05 10:15 6838
        if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive)
1193b4be Poul Sander 2025-02-03 21:28 6839
            if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 6840
                store_tree_node_stack_data = true;
1193b4be Poul Sander 2025-02-03 21:28 6841
    }
1193b4be Poul Sander 2025-02-03 21:28 6842
1193b4be Poul Sander 2025-02-03 21:28 6843
    const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6844
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 6845
    {
4e9e2555 Poul Sander 2025-10-05 10:15 6846
        if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1))))
4e9e2555 Poul Sander 2025-10-05 10:15 6847
        {
4e9e2555 Poul Sander 2025-10-05 10:15 6848
            ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
4e9e2555 Poul Sander 2025-10-05 10:15 6849
            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.
4e9e2555 Poul Sander 2025-10-05 10:15 6850
            if (frame_bb.Min.y >= window->ClipRect.Max.y)
4e9e2555 Poul Sander 2025-10-05 10:15 6851
                window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done
4e9e2555 Poul Sander 2025-10-05 10:15 6852
        }
4e9e2555 Poul Sander 2025-10-05 10:15 6853
        if (is_open && store_tree_node_stack_data)
4e9e2555 Poul Sander 2025-10-05 10:15 6854
            TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
1193b4be Poul Sander 2025-02-03 21:28 6855
        if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 6856
            TreePushOverrideID(id);
1193b4be Poul Sander 2025-02-03 21:28 6857
        IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
1193b4be Poul Sander 2025-02-03 21:28 6858
        return is_open;
1193b4be Poul Sander 2025-02-03 21:28 6859
    }
1193b4be Poul Sander 2025-02-03 21:28 6860
4e9e2555 Poul Sander 2025-10-05 10:15 6861
    if (span_all_columns || span_all_columns_label)
1193b4be Poul Sander 2025-02-03 21:28 6862
    {
1193b4be Poul Sander 2025-02-03 21:28 6863
        TablePushBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 6864
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6865
        g.LastItemData.ClipRect = window->ClipRect;
1193b4be Poul Sander 2025-02-03 21:28 6866
    }
1193b4be Poul Sander 2025-02-03 21:28 6867
1193b4be Poul Sander 2025-02-03 21:28 6868
    ImGuiButtonFlags button_flags = ImGuiTreeNodeFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 6869
    if ((flags & ImGuiTreeNodeFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap))
1193b4be Poul Sander 2025-02-03 21:28 6870
        button_flags |= ImGuiButtonFlags_AllowOverlap;
1193b4be Poul Sander 2025-02-03 21:28 6871
    if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6872
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
1193b4be Poul Sander 2025-02-03 21:28 6873
1193b4be Poul Sander 2025-02-03 21:28 6874
    // We allow clicking on the arrow section with keyboard modifiers held, in order to easily
1193b4be Poul Sander 2025-02-03 21:28 6875
    // allow browsing a tree while preserving selection with code implementing multi-selection patterns.
1193b4be Poul Sander 2025-02-03 21:28 6876
    // When clicking on the rest of the tree node we always disallow keyboard modifiers.
1193b4be Poul Sander 2025-02-03 21:28 6877
    const float arrow_hit_x1 = (text_pos.x - text_offset_x) - style.TouchExtraPadding.x;
1193b4be Poul Sander 2025-02-03 21:28 6878
    const float arrow_hit_x2 = (text_pos.x - text_offset_x) + (g.FontSize + padding.x * 2.0f) + style.TouchExtraPadding.x;
1193b4be Poul Sander 2025-02-03 21:28 6879
    const bool is_mouse_x_over_arrow = (g.IO.MousePos.x >= arrow_hit_x1 && g.IO.MousePos.x < arrow_hit_x2);
1193b4be Poul Sander 2025-02-03 21:28 6880
1193b4be Poul Sander 2025-02-03 21:28 6881
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6882
    if (is_multi_select) // We absolutely need to distinguish open vs select so _OpenOnArrow comes by default
1193b4be Poul Sander 2025-02-03 21:28 6883
        flags |= (flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 ? ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick : ImGuiTreeNodeFlags_OpenOnArrow;
1193b4be Poul Sander 2025-02-03 21:28 6884
1193b4be Poul Sander 2025-02-03 21:28 6885
    // Open behaviors can be altered with the _OpenOnArrow and _OnOnDoubleClick flags.
1193b4be Poul Sander 2025-02-03 21:28 6886
    // Some alteration have subtle effects (e.g. toggle on MouseUp vs MouseDown events) due to requirements for multi-selection and drag and drop support.
1193b4be Poul Sander 2025-02-03 21:28 6887
    // - Single-click on label = Toggle on MouseUp (default, when _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6888
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6889
    // - Single-click on arrow = Toggle on MouseDown (when _OpenOnArrow=1)
1193b4be Poul Sander 2025-02-03 21:28 6890
    // - Double-click on label = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1)
1193b4be Poul Sander 2025-02-03 21:28 6891
    // - Double-click on arrow = Toggle on MouseDoubleClick (when _OpenOnDoubleClick=1 and _OpenOnArrow=0)
1193b4be Poul Sander 2025-02-03 21:28 6892
    // It is rather standard that arrow click react on Down rather than Up.
1193b4be Poul Sander 2025-02-03 21:28 6893
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 6894
    if (is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6895
        button_flags |= ImGuiButtonFlags_PressedOnClick;
1193b4be Poul Sander 2025-02-03 21:28 6896
    else if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
1193b4be Poul Sander 2025-02-03 21:28 6897
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
1193b4be Poul Sander 2025-02-03 21:28 6898
    else
1193b4be Poul Sander 2025-02-03 21:28 6899
        button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
4e9e2555 Poul Sander 2025-10-05 10:15 6900
    if (flags & ImGuiTreeNodeFlags_NoNavFocus)
4e9e2555 Poul Sander 2025-10-05 10:15 6901
        button_flags |= ImGuiButtonFlags_NoNavFocus;
1193b4be Poul Sander 2025-02-03 21:28 6902
1193b4be Poul Sander 2025-02-03 21:28 6903
    bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
1193b4be Poul Sander 2025-02-03 21:28 6904
    const bool was_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 6905
1193b4be Poul Sander 2025-02-03 21:28 6906
    // Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 6907
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6908
    {
1193b4be Poul Sander 2025-02-03 21:28 6909
        // Handle multi-select + alter button flags for it
1193b4be Poul Sander 2025-02-03 21:28 6910
        MultiSelectItemHeader(id, &selected, &button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6911
        if (is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6912
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 6913
    }
1193b4be Poul Sander 2025-02-03 21:28 6914
    else
1193b4be Poul Sander 2025-02-03 21:28 6915
    {
1193b4be Poul Sander 2025-02-03 21:28 6916
        if (window != g.HoveredWindow || !is_mouse_x_over_arrow)
1193b4be Poul Sander 2025-02-03 21:28 6917
            button_flags |= ImGuiButtonFlags_NoKeyModsAllowed;
1193b4be Poul Sander 2025-02-03 21:28 6918
    }
1193b4be Poul Sander 2025-02-03 21:28 6919
1193b4be Poul Sander 2025-02-03 21:28 6920
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 6921
    bool pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 6922
    bool toggled = false;
1193b4be Poul Sander 2025-02-03 21:28 6923
    if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6924
    {
1193b4be Poul Sander 2025-02-03 21:28 6925
        if (pressed && g.DragDropHoldJustPressedId != id)
1193b4be Poul Sander 2025-02-03 21:28 6926
        {
1193b4be Poul Sander 2025-02-03 21:28 6927
            if ((flags & ImGuiTreeNodeFlags_OpenOnMask_) == 0 || (g.NavActivateId == id && !is_multi_select))
1193b4be Poul Sander 2025-02-03 21:28 6928
                toggled = true; // Single click
1193b4be Poul Sander 2025-02-03 21:28 6929
            if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
1193b4be Poul Sander 2025-02-03 21:28 6930
                toggled |= is_mouse_x_over_arrow && !g.NavHighlightItemUnderNav; // Lightweight equivalent of IsMouseHoveringRect() since ButtonBehavior() already did the job
1193b4be Poul Sander 2025-02-03 21:28 6931
            if ((flags & ImGuiTreeNodeFlags_OpenOnDoubleClick) && g.IO.MouseClickedCount[0] == 2)
1193b4be Poul Sander 2025-02-03 21:28 6932
                toggled = true; // Double click
1193b4be Poul Sander 2025-02-03 21:28 6933
        }
1193b4be Poul Sander 2025-02-03 21:28 6934
        else if (pressed && g.DragDropHoldJustPressedId == id)
1193b4be Poul Sander 2025-02-03 21:28 6935
        {
1193b4be Poul Sander 2025-02-03 21:28 6936
            IM_ASSERT(button_flags & ImGuiButtonFlags_PressedOnDragDropHold);
1193b4be Poul Sander 2025-02-03 21:28 6937
            if (!is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
1193b4be Poul Sander 2025-02-03 21:28 6938
                toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6939
            else
1193b4be Poul Sander 2025-02-03 21:28 6940
                pressed = false; // Cancel press so it doesn't trigger selection.
1193b4be Poul Sander 2025-02-03 21:28 6941
        }
1193b4be Poul Sander 2025-02-03 21:28 6942
1193b4be Poul Sander 2025-02-03 21:28 6943
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Left && is_open)
1193b4be Poul Sander 2025-02-03 21:28 6944
        {
1193b4be Poul Sander 2025-02-03 21:28 6945
            toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6946
            NavClearPreferredPosForAxis(ImGuiAxis_X);
1193b4be Poul Sander 2025-02-03 21:28 6947
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 6948
        }
1193b4be Poul Sander 2025-02-03 21:28 6949
        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?
1193b4be Poul Sander 2025-02-03 21:28 6950
        {
1193b4be Poul Sander 2025-02-03 21:28 6951
            toggled = true;
1193b4be Poul Sander 2025-02-03 21:28 6952
            NavClearPreferredPosForAxis(ImGuiAxis_X);
1193b4be Poul Sander 2025-02-03 21:28 6953
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 6954
        }
1193b4be Poul Sander 2025-02-03 21:28 6955
1193b4be Poul Sander 2025-02-03 21:28 6956
        if (toggled)
1193b4be Poul Sander 2025-02-03 21:28 6957
        {
1193b4be Poul Sander 2025-02-03 21:28 6958
            is_open = !is_open;
1193b4be Poul Sander 2025-02-03 21:28 6959
            window->DC.StateStorage->SetInt(storage_id, is_open);
1193b4be Poul Sander 2025-02-03 21:28 6960
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledOpen;
1193b4be Poul Sander 2025-02-03 21:28 6961
        }
1193b4be Poul Sander 2025-02-03 21:28 6962
    }
1193b4be Poul Sander 2025-02-03 21:28 6963
1193b4be Poul Sander 2025-02-03 21:28 6964
    // Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 6965
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6966
    {
1193b4be Poul Sander 2025-02-03 21:28 6967
        bool pressed_copy = pressed && !toggled;
1193b4be Poul Sander 2025-02-03 21:28 6968
        MultiSelectItemFooter(id, &selected, &pressed_copy);
1193b4be Poul Sander 2025-02-03 21:28 6969
        if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 6970
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, interact_bb);
1193b4be Poul Sander 2025-02-03 21:28 6971
    }
1193b4be Poul Sander 2025-02-03 21:28 6972
1193b4be Poul Sander 2025-02-03 21:28 6973
    if (selected != was_selected)
1193b4be Poul Sander 2025-02-03 21:28 6974
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
1193b4be Poul Sander 2025-02-03 21:28 6975
1193b4be Poul Sander 2025-02-03 21:28 6976
    // Render
1193b4be Poul Sander 2025-02-03 21:28 6977
    {
1193b4be Poul Sander 2025-02-03 21:28 6978
        const ImU32 text_col = GetColorU32(ImGuiCol_Text);
1193b4be Poul Sander 2025-02-03 21:28 6979
        ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact;
1193b4be Poul Sander 2025-02-03 21:28 6980
        if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 6981
            nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
1193b4be Poul Sander 2025-02-03 21:28 6982
        if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 6983
        {
1193b4be Poul Sander 2025-02-03 21:28 6984
            // Framed type
1193b4be Poul Sander 2025-02-03 21:28 6985
            const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 6986
            RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 6987
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
4e9e2555 Poul Sander 2025-10-05 10:15 6988
            if (span_all_columns && !span_all_columns_label)
4e9e2555 Poul Sander 2025-10-05 10:15 6989
                TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 6990
            if (flags & ImGuiTreeNodeFlags_Bullet)
1193b4be Poul Sander 2025-02-03 21:28 6991
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
1193b4be Poul Sander 2025-02-03 21:28 6992
            else if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 6993
                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);
1193b4be Poul Sander 2025-02-03 21:28 6994
            else // Leaf without bullet, left-adjusted text
1193b4be Poul Sander 2025-02-03 21:28 6995
                text_pos.x -= text_offset_x - padding.x;
1193b4be Poul Sander 2025-02-03 21:28 6996
            if (flags & ImGuiTreeNodeFlags_ClipLabelForTrailingButton)
1193b4be Poul Sander 2025-02-03 21:28 6997
                frame_bb.Max.x -= g.FontSize + style.FramePadding.x;
1193b4be Poul Sander 2025-02-03 21:28 6998
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 6999
                LogSetNextTextDecoration("###", "###");
1193b4be Poul Sander 2025-02-03 21:28 7000
        }
1193b4be Poul Sander 2025-02-03 21:28 7001
        else
1193b4be Poul Sander 2025-02-03 21:28 7002
        {
1193b4be Poul Sander 2025-02-03 21:28 7003
            // Unframed typed for tree nodes
1193b4be Poul Sander 2025-02-03 21:28 7004
            if (hovered || selected)
1193b4be Poul Sander 2025-02-03 21:28 7005
            {
1193b4be Poul Sander 2025-02-03 21:28 7006
                const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 7007
                RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
1193b4be Poul Sander 2025-02-03 21:28 7008
            }
1193b4be Poul Sander 2025-02-03 21:28 7009
            RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
4e9e2555 Poul Sander 2025-10-05 10:15 7010
            if (span_all_columns && !span_all_columns_label)
4e9e2555 Poul Sander 2025-10-05 10:15 7011
                TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 7012
            if (flags & ImGuiTreeNodeFlags_Bullet)
1193b4be Poul Sander 2025-02-03 21:28 7013
                RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
1193b4be Poul Sander 2025-02-03 21:28 7014
            else if (!is_leaf)
1193b4be Poul Sander 2025-02-03 21:28 7015
                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);
1193b4be Poul Sander 2025-02-03 21:28 7016
            if (g.LogEnabled)
1193b4be Poul Sander 2025-02-03 21:28 7017
                LogSetNextTextDecoration(">", NULL);
1193b4be Poul Sander 2025-02-03 21:28 7018
        }
1193b4be Poul Sander 2025-02-03 21:28 7019
4e9e2555 Poul Sander 2025-10-05 10:15 7020
        if (draw_tree_lines)
4e9e2555 Poul Sander 2025-10-05 10:15 7021
            TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f));
1193b4be Poul Sander 2025-02-03 21:28 7022
1193b4be Poul Sander 2025-02-03 21:28 7023
        // Label
1193b4be Poul Sander 2025-02-03 21:28 7024
        if (display_frame)
1193b4be Poul Sander 2025-02-03 21:28 7025
            RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 7026
        else
1193b4be Poul Sander 2025-02-03 21:28 7027
            RenderText(text_pos, label, label_end, false);
4e9e2555 Poul Sander 2025-10-05 10:15 7028
4e9e2555 Poul Sander 2025-10-05 10:15 7029
        if (span_all_columns_label)
4e9e2555 Poul Sander 2025-10-05 10:15 7030
            TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 7031
    }
1193b4be Poul Sander 2025-02-03 21:28 7032
4e9e2555 Poul Sander 2025-10-05 10:15 7033
    if (is_open && store_tree_node_stack_data)
4e9e2555 Poul Sander 2025-10-05 10:15 7034
        TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
1193b4be Poul Sander 2025-02-03 21:28 7035
    if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
1193b4be Poul Sander 2025-02-03 21:28 7036
        TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
1193b4be Poul Sander 2025-02-03 21:28 7037
1193b4be Poul Sander 2025-02-03 21:28 7038
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
1193b4be Poul Sander 2025-02-03 21:28 7039
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 7040
}
1193b4be Poul Sander 2025-02-03 21:28 7041
4e9e2555 Poul Sander 2025-10-05 10:15 7042
// Draw horizontal line from our parent node
4e9e2555 Poul Sander 2025-10-05 10:15 7043
// This is only called for visible child nodes so we are not too fussy anymore about performances
4e9e2555 Poul Sander 2025-10-05 10:15 7044
void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos)
4e9e2555 Poul Sander 2025-10-05 10:15 7045
{
4e9e2555 Poul Sander 2025-10-05 10:15 7046
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 7047
    ImGuiWindow* window = g.CurrentWindow;
4e9e2555 Poul Sander 2025-10-05 10:15 7048
    if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 7049
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 7050
4e9e2555 Poul Sander 2025-10-05 10:15 7051
    ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
4e9e2555 Poul Sander 2025-10-05 10:15 7052
    float x1 = ImTrunc(parent_data->DrawLinesX1);
4e9e2555 Poul Sander 2025-10-05 10:15 7053
    float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x);
4e9e2555 Poul Sander 2025-10-05 10:15 7054
    float y = ImTrunc(target_pos.y);
4e9e2555 Poul Sander 2025-10-05 10:15 7055
    float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 7056
    parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding);
4e9e2555 Poul Sander 2025-10-05 10:15 7057
    if (x1 >= x2)
4e9e2555 Poul Sander 2025-10-05 10:15 7058
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 7059
    if (rounding > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 7060
    {
4e9e2555 Poul Sander 2025-10-05 10:15 7061
        x1 += 0.5f + rounding;
4e9e2555 Poul Sander 2025-10-05 10:15 7062
        window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3);
4e9e2555 Poul Sander 2025-10-05 10:15 7063
        if (x1 < x2)
4e9e2555 Poul Sander 2025-10-05 10:15 7064
            window->DrawList->PathLineTo(ImVec2(x2, y));
4e9e2555 Poul Sander 2025-10-05 10:15 7065
        window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize);
4e9e2555 Poul Sander 2025-10-05 10:15 7066
    }
4e9e2555 Poul Sander 2025-10-05 10:15 7067
    else
4e9e2555 Poul Sander 2025-10-05 10:15 7068
    {
4e9e2555 Poul Sander 2025-10-05 10:15 7069
        window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
4e9e2555 Poul Sander 2025-10-05 10:15 7070
    }
4e9e2555 Poul Sander 2025-10-05 10:15 7071
}
4e9e2555 Poul Sander 2025-10-05 10:15 7072
4e9e2555 Poul Sander 2025-10-05 10:15 7073
// Draw vertical line of the hierarchy
4e9e2555 Poul Sander 2025-10-05 10:15 7074
void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data)
4e9e2555 Poul Sander 2025-10-05 10:15 7075
{
4e9e2555 Poul Sander 2025-10-05 10:15 7076
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 7077
    ImGuiWindow* window = g.CurrentWindow;
4e9e2555 Poul Sander 2025-10-05 10:15 7078
    float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y);
4e9e2555 Poul Sander 2025-10-05 10:15 7079
    float y2 = data->DrawLinesToNodesY2;
4e9e2555 Poul Sander 2025-10-05 10:15 7080
    if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull)
4e9e2555 Poul Sander 2025-10-05 10:15 7081
    {
4e9e2555 Poul Sander 2025-10-05 10:15 7082
        float y2_full = window->DC.CursorPos.y;
4e9e2555 Poul Sander 2025-10-05 10:15 7083
        if (g.CurrentTable)
4e9e2555 Poul Sander 2025-10-05 10:15 7084
            y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full);
4e9e2555 Poul Sander 2025-10-05 10:15 7085
        y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f);
4e9e2555 Poul Sander 2025-10-05 10:15 7086
        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
4e9e2555 Poul Sander 2025-10-05 10:15 7087
            y2 = y2_full;
4e9e2555 Poul Sander 2025-10-05 10:15 7088
    }
4e9e2555 Poul Sander 2025-10-05 10:15 7089
    y2 = ImMin(y2, window->ClipRect.Max.y);
4e9e2555 Poul Sander 2025-10-05 10:15 7090
    if (y2 <= y1)
4e9e2555 Poul Sander 2025-10-05 10:15 7091
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 7092
    float x = ImTrunc(data->DrawLinesX1);
4e9e2555 Poul Sander 2025-10-05 10:15 7093
    if (data->DrawLinesTableColumn != -1)
4e9e2555 Poul Sander 2025-10-05 10:15 7094
        TablePushColumnChannel(data->DrawLinesTableColumn);
4e9e2555 Poul Sander 2025-10-05 10:15 7095
    window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
4e9e2555 Poul Sander 2025-10-05 10:15 7096
    if (data->DrawLinesTableColumn != -1)
4e9e2555 Poul Sander 2025-10-05 10:15 7097
        TablePopColumnChannel();
4e9e2555 Poul Sander 2025-10-05 10:15 7098
}
4e9e2555 Poul Sander 2025-10-05 10:15 7099
1193b4be Poul Sander 2025-02-03 21:28 7100
void ImGui::TreePush(const char* str_id)
1193b4be Poul Sander 2025-02-03 21:28 7101
{
1193b4be Poul Sander 2025-02-03 21:28 7102
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 7103
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 7104
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 7105
    PushID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 7106
}
1193b4be Poul Sander 2025-02-03 21:28 7107
1193b4be Poul Sander 2025-02-03 21:28 7108
void ImGui::TreePush(const void* ptr_id)
1193b4be Poul Sander 2025-02-03 21:28 7109
{
1193b4be Poul Sander 2025-02-03 21:28 7110
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 7111
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 7112
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 7113
    PushID(ptr_id);
1193b4be Poul Sander 2025-02-03 21:28 7114
}
1193b4be Poul Sander 2025-02-03 21:28 7115
1193b4be Poul Sander 2025-02-03 21:28 7116
void ImGui::TreePushOverrideID(ImGuiID id)
1193b4be Poul Sander 2025-02-03 21:28 7117
{
1193b4be Poul Sander 2025-02-03 21:28 7118
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7119
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7120
    Indent();
1193b4be Poul Sander 2025-02-03 21:28 7121
    window->DC.TreeDepth++;
1193b4be Poul Sander 2025-02-03 21:28 7122
    PushOverrideID(id);
1193b4be Poul Sander 2025-02-03 21:28 7123
}
1193b4be Poul Sander 2025-02-03 21:28 7124
1193b4be Poul Sander 2025-02-03 21:28 7125
void ImGui::TreePop()
1193b4be Poul Sander 2025-02-03 21:28 7126
{
1193b4be Poul Sander 2025-02-03 21:28 7127
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7128
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7129
    Unindent();
1193b4be Poul Sander 2025-02-03 21:28 7130
1193b4be Poul Sander 2025-02-03 21:28 7131
    window->DC.TreeDepth--;
1193b4be Poul Sander 2025-02-03 21:28 7132
    ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
1193b4be Poul Sander 2025-02-03 21:28 7133
4e9e2555 Poul Sander 2025-10-05 10:15 7134
    if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask)
1193b4be Poul Sander 2025-02-03 21:28 7135
    {
4e9e2555 Poul Sander 2025-10-05 10:15 7136
        const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
1193b4be Poul Sander 2025-02-03 21:28 7137
        IM_ASSERT(data->ID == window->IDStack.back());
4e9e2555 Poul Sander 2025-10-05 10:15 7138
4e9e2555 Poul Sander 2025-10-05 10:15 7139
        // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsToParent is enabled)
4e9e2555 Poul Sander 2025-10-05 10:15 7140
        if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsToParent)
1193b4be Poul Sander 2025-02-03 21:28 7141
            if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 7142
                NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
4e9e2555 Poul Sander 2025-10-05 10:15 7143
4e9e2555 Poul Sander 2025-10-05 10:15 7144
        // Draw hierarchy lines
4e9e2555 Poul Sander 2025-10-05 10:15 7145
        if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y)
4e9e2555 Poul Sander 2025-10-05 10:15 7146
            TreeNodeDrawLineToTreePop(data);
4e9e2555 Poul Sander 2025-10-05 10:15 7147
1193b4be Poul Sander 2025-02-03 21:28 7148
        g.TreeNodeStack.pop_back();
1193b4be Poul Sander 2025-02-03 21:28 7149
        window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
4e9e2555 Poul Sander 2025-10-05 10:15 7150
        window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask;
1193b4be Poul Sander 2025-02-03 21:28 7151
    }
1193b4be Poul Sander 2025-02-03 21:28 7152
1193b4be Poul Sander 2025-02-03 21:28 7153
    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.
1193b4be Poul Sander 2025-02-03 21:28 7154
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 7155
}
1193b4be Poul Sander 2025-02-03 21:28 7156
1193b4be Poul Sander 2025-02-03 21:28 7157
// Horizontal distance preceding label when using TreeNode() or Bullet()
1193b4be Poul Sander 2025-02-03 21:28 7158
float ImGui::GetTreeNodeToLabelSpacing()
1193b4be Poul Sander 2025-02-03 21:28 7159
{
1193b4be Poul Sander 2025-02-03 21:28 7160
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7161
    return g.FontSize + (g.Style.FramePadding.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 7162
}
1193b4be Poul Sander 2025-02-03 21:28 7163
1193b4be Poul Sander 2025-02-03 21:28 7164
// Set next TreeNode/CollapsingHeader open state.
1193b4be Poul Sander 2025-02-03 21:28 7165
void ImGui::SetNextItemOpen(bool is_open, ImGuiCond cond)
1193b4be Poul Sander 2025-02-03 21:28 7166
{
1193b4be Poul Sander 2025-02-03 21:28 7167
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7168
    if (g.CurrentWindow->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 7169
        return;
1193b4be Poul Sander 2025-02-03 21:28 7170
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasOpen;
1193b4be Poul Sander 2025-02-03 21:28 7171
    g.NextItemData.OpenVal = is_open;
1193b4be Poul Sander 2025-02-03 21:28 7172
    g.NextItemData.OpenCond = (ImU8)(cond ? cond : ImGuiCond_Always);
1193b4be Poul Sander 2025-02-03 21:28 7173
}
1193b4be Poul Sander 2025-02-03 21:28 7174
1193b4be Poul Sander 2025-02-03 21:28 7175
// Set next TreeNode/CollapsingHeader storage id.
1193b4be Poul Sander 2025-02-03 21:28 7176
void ImGui::SetNextItemStorageID(ImGuiID storage_id)
1193b4be Poul Sander 2025-02-03 21:28 7177
{
1193b4be Poul Sander 2025-02-03 21:28 7178
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7179
    if (g.CurrentWindow->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 7180
        return;
1193b4be Poul Sander 2025-02-03 21:28 7181
    g.NextItemData.HasFlags |= ImGuiNextItemDataFlags_HasStorageID;
1193b4be Poul Sander 2025-02-03 21:28 7182
    g.NextItemData.StorageId = storage_id;
1193b4be Poul Sander 2025-02-03 21:28 7183
}
1193b4be Poul Sander 2025-02-03 21:28 7184
1193b4be Poul Sander 2025-02-03 21:28 7185
// CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
1193b4be Poul Sander 2025-02-03 21:28 7186
// 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().
1193b4be Poul Sander 2025-02-03 21:28 7187
bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 7188
{
1193b4be Poul Sander 2025-02-03 21:28 7189
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 7190
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 7191
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7192
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 7193
    return TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
1193b4be Poul Sander 2025-02-03 21:28 7194
}
1193b4be Poul Sander 2025-02-03 21:28 7195
1193b4be Poul Sander 2025-02-03 21:28 7196
// p_visible == NULL                        : regular collapsing header
1193b4be Poul Sander 2025-02-03 21:28 7197
// 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
1193b4be Poul Sander 2025-02-03 21:28 7198
// p_visible != NULL && *p_visible == false : do not show the header at all
1193b4be Poul Sander 2025-02-03 21:28 7199
// Do not mistake this with the Open state of the header itself, which you can adjust with SetNextItemOpen() or ImGuiTreeNodeFlags_DefaultOpen.
1193b4be Poul Sander 2025-02-03 21:28 7200
bool ImGui::CollapsingHeader(const char* label, bool* p_visible, ImGuiTreeNodeFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 7201
{
1193b4be Poul Sander 2025-02-03 21:28 7202
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 7203
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 7204
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7205
1193b4be Poul Sander 2025-02-03 21:28 7206
    if (p_visible && !*p_visible)
1193b4be Poul Sander 2025-02-03 21:28 7207
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7208
1193b4be Poul Sander 2025-02-03 21:28 7209
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 7210
    flags |= ImGuiTreeNodeFlags_CollapsingHeader;
1193b4be Poul Sander 2025-02-03 21:28 7211
    if (p_visible)
1193b4be Poul Sander 2025-02-03 21:28 7212
        flags |= ImGuiTreeNodeFlags_AllowOverlap | (ImGuiTreeNodeFlags)ImGuiTreeNodeFlags_ClipLabelForTrailingButton;
1193b4be Poul Sander 2025-02-03 21:28 7213
    bool is_open = TreeNodeBehavior(id, flags, label);
1193b4be Poul Sander 2025-02-03 21:28 7214
    if (p_visible != NULL)
1193b4be Poul Sander 2025-02-03 21:28 7215
    {
1193b4be Poul Sander 2025-02-03 21:28 7216
        // Create a small overlapping close button
1193b4be Poul Sander 2025-02-03 21:28 7217
        // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
1193b4be Poul Sander 2025-02-03 21:28 7218
        // FIXME: CloseButton can overlap into text, need find a way to clip the text somehow.
1193b4be Poul Sander 2025-02-03 21:28 7219
        ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7220
        ImGuiLastItemData last_item_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 7221
        float button_size = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 7222
        float button_x = ImMax(g.LastItemData.Rect.Min.x, g.LastItemData.Rect.Max.x - g.Style.FramePadding.x - button_size);
1193b4be Poul Sander 2025-02-03 21:28 7223
        float button_y = g.LastItemData.Rect.Min.y + g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 7224
        ImGuiID close_button_id = GetIDWithSeed("#CLOSE", NULL, id);
1193b4be Poul Sander 2025-02-03 21:28 7225
        if (CloseButton(close_button_id, ImVec2(button_x, button_y)))
1193b4be Poul Sander 2025-02-03 21:28 7226
            *p_visible = false;
1193b4be Poul Sander 2025-02-03 21:28 7227
        g.LastItemData = last_item_backup;
1193b4be Poul Sander 2025-02-03 21:28 7228
    }
1193b4be Poul Sander 2025-02-03 21:28 7229
1193b4be Poul Sander 2025-02-03 21:28 7230
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 7231
}
1193b4be Poul Sander 2025-02-03 21:28 7232
1193b4be Poul Sander 2025-02-03 21:28 7233
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7234
// [SECTION] Widgets: Selectable
1193b4be Poul Sander 2025-02-03 21:28 7235
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7236
// - Selectable()
1193b4be Poul Sander 2025-02-03 21:28 7237
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7238
1193b4be Poul Sander 2025-02-03 21:28 7239
// Tip: pass a non-visible label (e.g. "##hello") then you can use the space to draw other text or image.
1193b4be Poul Sander 2025-02-03 21:28 7240
// But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
1193b4be Poul Sander 2025-02-03 21:28 7241
// With this scheme, ImGuiSelectableFlags_SpanAllColumns and ImGuiSelectableFlags_AllowOverlap are also frequently used flags.
1193b4be Poul Sander 2025-02-03 21:28 7242
// FIXME: Selectable() with (size.x == 0.0f) and (SelectableTextAlign.x > 0.0f) followed by SameLine() is currently not supported.
1193b4be Poul Sander 2025-02-03 21:28 7243
bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 7244
{
1193b4be Poul Sander 2025-02-03 21:28 7245
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 7246
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 7247
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7248
1193b4be Poul Sander 2025-02-03 21:28 7249
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7250
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 7251
1193b4be Poul Sander 2025-02-03 21:28 7252
    // Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
1193b4be Poul Sander 2025-02-03 21:28 7253
    ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 7254
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 7255
    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
1193b4be Poul Sander 2025-02-03 21:28 7256
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 7257
    pos.y += window->DC.CurrLineTextBaseOffset;
1193b4be Poul Sander 2025-02-03 21:28 7258
    ItemSize(size, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 7259
1193b4be Poul Sander 2025-02-03 21:28 7260
    // Fill horizontal space
1193b4be Poul Sander 2025-02-03 21:28 7261
    // We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
1193b4be Poul Sander 2025-02-03 21:28 7262
    const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7263
    const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
1193b4be Poul Sander 2025-02-03 21:28 7264
    const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 7265
    if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
1193b4be Poul Sander 2025-02-03 21:28 7266
        size.x = ImMax(label_size.x, max_x - min_x);
1193b4be Poul Sander 2025-02-03 21:28 7267
1193b4be Poul Sander 2025-02-03 21:28 7268
    // Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
1193b4be Poul Sander 2025-02-03 21:28 7269
    // FIXME: Not part of layout so not included in clipper calculation, but ItemSize currently doesn't allow offsetting CursorPos.
4e9e2555 Poul Sander 2025-10-05 10:15 7270
    ImRect bb(min_x, pos.y, min_x + size.x, pos.y + size.y);
1193b4be Poul Sander 2025-02-03 21:28 7271
    if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7272
    {
1193b4be Poul Sander 2025-02-03 21:28 7273
        const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 7274
        const float spacing_y = style.ItemSpacing.y;
1193b4be Poul Sander 2025-02-03 21:28 7275
        const float spacing_L = IM_TRUNC(spacing_x * 0.50f);
1193b4be Poul Sander 2025-02-03 21:28 7276
        const float spacing_U = IM_TRUNC(spacing_y * 0.50f);
1193b4be Poul Sander 2025-02-03 21:28 7277
        bb.Min.x -= spacing_L;
1193b4be Poul Sander 2025-02-03 21:28 7278
        bb.Min.y -= spacing_U;
1193b4be Poul Sander 2025-02-03 21:28 7279
        bb.Max.x += (spacing_x - spacing_L);
1193b4be Poul Sander 2025-02-03 21:28 7280
        bb.Max.y += (spacing_y - spacing_U);
1193b4be Poul Sander 2025-02-03 21:28 7281
    }
1193b4be Poul Sander 2025-02-03 21:28 7282
    //if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
1193b4be Poul Sander 2025-02-03 21:28 7283
1193b4be Poul Sander 2025-02-03 21:28 7284
    const bool disabled_item = (flags & ImGuiSelectableFlags_Disabled) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7285
    const ImGuiItemFlags extra_item_flags = disabled_item ? (ImGuiItemFlags)ImGuiItemFlags_Disabled : ImGuiItemFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 7286
    bool is_visible;
1193b4be Poul Sander 2025-02-03 21:28 7287
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 7288
    {
1193b4be Poul Sander 2025-02-03 21:28 7289
        // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable..
1193b4be Poul Sander 2025-02-03 21:28 7290
        const float backup_clip_rect_min_x = window->ClipRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 7291
        const float backup_clip_rect_max_x = window->ClipRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 7292
        window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
1193b4be Poul Sander 2025-02-03 21:28 7293
        window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 7294
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
1193b4be Poul Sander 2025-02-03 21:28 7295
        window->ClipRect.Min.x = backup_clip_rect_min_x;
1193b4be Poul Sander 2025-02-03 21:28 7296
        window->ClipRect.Max.x = backup_clip_rect_max_x;
1193b4be Poul Sander 2025-02-03 21:28 7297
    }
1193b4be Poul Sander 2025-02-03 21:28 7298
    else
1193b4be Poul Sander 2025-02-03 21:28 7299
    {
1193b4be Poul Sander 2025-02-03 21:28 7300
        is_visible = ItemAdd(bb, id, NULL, extra_item_flags);
1193b4be Poul Sander 2025-02-03 21:28 7301
    }
1193b4be Poul Sander 2025-02-03 21:28 7302
1193b4be Poul Sander 2025-02-03 21:28 7303
    const bool is_multi_select = (g.LastItemData.ItemFlags & ImGuiItemFlags_IsMultiSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7304
    if (!is_visible)
1193b4be Poul Sander 2025-02-03 21:28 7305
        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)
1193b4be Poul Sander 2025-02-03 21:28 7306
            return false;
1193b4be Poul Sander 2025-02-03 21:28 7307
1193b4be Poul Sander 2025-02-03 21:28 7308
    const bool disabled_global = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
1193b4be Poul Sander 2025-02-03 21:28 7309
    if (disabled_item && !disabled_global) // Only testing this as an optimization
1193b4be Poul Sander 2025-02-03 21:28 7310
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 7311
1193b4be Poul Sander 2025-02-03 21:28 7312
    // FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
1193b4be Poul Sander 2025-02-03 21:28 7313
    // which would be advantageous since most selectable are not selected.
1193b4be Poul Sander 2025-02-03 21:28 7314
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 7315
    {
1193b4be Poul Sander 2025-02-03 21:28 7316
        if (g.CurrentTable)
1193b4be Poul Sander 2025-02-03 21:28 7317
            TablePushBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 7318
        else if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 7319
            PushColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 7320
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7321
        g.LastItemData.ClipRect = window->ClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7322
    }
1193b4be Poul Sander 2025-02-03 21:28 7323
1193b4be Poul Sander 2025-02-03 21:28 7324
    // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
1193b4be Poul Sander 2025-02-03 21:28 7325
    ImGuiButtonFlags button_flags = 0;
1193b4be Poul Sander 2025-02-03 21:28 7326
    if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
1193b4be Poul Sander 2025-02-03 21:28 7327
    if (flags & ImGuiSelectableFlags_NoSetKeyOwner)     { button_flags |= ImGuiButtonFlags_NoSetKeyOwner; }
1193b4be Poul Sander 2025-02-03 21:28 7328
    if (flags & ImGuiSelectableFlags_SelectOnClick)     { button_flags |= ImGuiButtonFlags_PressedOnClick; }
1193b4be Poul Sander 2025-02-03 21:28 7329
    if (flags & ImGuiSelectableFlags_SelectOnRelease)   { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
1193b4be Poul Sander 2025-02-03 21:28 7330
    if (flags & ImGuiSelectableFlags_AllowDoubleClick)  { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
1193b4be Poul Sander 2025-02-03 21:28 7331
    if ((flags & ImGuiSelectableFlags_AllowOverlap) || (g.LastItemData.ItemFlags & ImGuiItemFlags_AllowOverlap)) { button_flags |= ImGuiButtonFlags_AllowOverlap; }
1193b4be Poul Sander 2025-02-03 21:28 7332
1193b4be Poul Sander 2025-02-03 21:28 7333
    // Multi-selection support (header)
1193b4be Poul Sander 2025-02-03 21:28 7334
    const bool was_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 7335
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 7336
    {
1193b4be Poul Sander 2025-02-03 21:28 7337
        // Handle multi-select + alter button flags for it
1193b4be Poul Sander 2025-02-03 21:28 7338
        MultiSelectItemHeader(id, &selected, &button_flags);
1193b4be Poul Sander 2025-02-03 21:28 7339
    }
1193b4be Poul Sander 2025-02-03 21:28 7340
1193b4be Poul Sander 2025-02-03 21:28 7341
    bool hovered, held;
1193b4be Poul Sander 2025-02-03 21:28 7342
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
4e9e2555 Poul Sander 2025-10-05 10:15 7343
    bool auto_selected = false;
1193b4be Poul Sander 2025-02-03 21:28 7344
1193b4be Poul Sander 2025-02-03 21:28 7345
    // Multi-selection support (footer)
1193b4be Poul Sander 2025-02-03 21:28 7346
    if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 7347
    {
1193b4be Poul Sander 2025-02-03 21:28 7348
        MultiSelectItemFooter(id, &selected, &pressed);
1193b4be Poul Sander 2025-02-03 21:28 7349
    }
1193b4be Poul Sander 2025-02-03 21:28 7350
    else
1193b4be Poul Sander 2025-02-03 21:28 7351
    {
1193b4be Poul Sander 2025-02-03 21:28 7352
        // Auto-select when moved into
1193b4be Poul Sander 2025-02-03 21:28 7353
        // - This will be more fully fleshed in the range-select branch
1193b4be Poul Sander 2025-02-03 21:28 7354
        // - This is not exposed as it won't nicely work with some user side handling of shift/control
1193b4be Poul Sander 2025-02-03 21:28 7355
        // - We cannot do 'if (g.NavJustMovedToId != id) { selected = false; pressed = was_selected; }' for two reasons
1193b4be Poul Sander 2025-02-03 21:28 7356
        //   - (1) it would require focus scope to be set, need exposing PushFocusScope() or equivalent (e.g. BeginSelection() calling PushFocusScope())
1193b4be Poul Sander 2025-02-03 21:28 7357
        //   - (2) usage will fail with clipped items
1193b4be Poul Sander 2025-02-03 21:28 7358
        //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
1193b4be Poul Sander 2025-02-03 21:28 7359
        if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
4e9e2555 Poul Sander 2025-10-05 10:15 7360
            if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 7361
                selected = pressed = auto_selected = true;
1193b4be Poul Sander 2025-02-03 21:28 7362
    }
1193b4be Poul Sander 2025-02-03 21:28 7363
1193b4be Poul Sander 2025-02-03 21:28 7364
    // Update NavId when clicking or when Hovering (this doesn't happen on most widgets), so navigation can be resumed with keyboard/gamepad
1193b4be Poul Sander 2025-02-03 21:28 7365
    if (pressed || (hovered && (flags & ImGuiSelectableFlags_SetNavIdOnHover)))
1193b4be Poul Sander 2025-02-03 21:28 7366
    {
1193b4be Poul Sander 2025-02-03 21:28 7367
        if (!g.NavHighlightItemUnderNav && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
1193b4be Poul Sander 2025-02-03 21:28 7368
        {
1193b4be Poul Sander 2025-02-03 21:28 7369
            SetNavID(id, window->DC.NavLayerCurrent, g.CurrentFocusScopeId, WindowRectAbsToRel(window, bb)); // (bb == NavRect)
1193b4be Poul Sander 2025-02-03 21:28 7370
            if (g.IO.ConfigNavCursorVisibleAuto)
1193b4be Poul Sander 2025-02-03 21:28 7371
                g.NavCursorVisible = false;
1193b4be Poul Sander 2025-02-03 21:28 7372
        }
1193b4be Poul Sander 2025-02-03 21:28 7373
    }
1193b4be Poul Sander 2025-02-03 21:28 7374
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 7375
        MarkItemEdited(id);
1193b4be Poul Sander 2025-02-03 21:28 7376
1193b4be Poul Sander 2025-02-03 21:28 7377
    if (selected != was_selected)
1193b4be Poul Sander 2025-02-03 21:28 7378
        g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_ToggledSelection;
1193b4be Poul Sander 2025-02-03 21:28 7379
1193b4be Poul Sander 2025-02-03 21:28 7380
    // Render
1193b4be Poul Sander 2025-02-03 21:28 7381
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 7382
    {
1193b4be Poul Sander 2025-02-03 21:28 7383
        const bool highlighted = hovered || (flags & ImGuiSelectableFlags_Highlight);
1193b4be Poul Sander 2025-02-03 21:28 7384
        if (highlighted || selected)
1193b4be Poul Sander 2025-02-03 21:28 7385
        {
4e9e2555 Poul Sander 2025-10-05 10:15 7386
            // Between 1.91.0 and 1.91.4 we made selected Selectable use an arbitrary lerp between _Header and _HeaderHovered. Removed that now. (#8106)
4e9e2555 Poul Sander 2025-10-05 10:15 7387
            ImU32 col = GetColorU32((held && highlighted) ? ImGuiCol_HeaderActive : highlighted ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
1193b4be Poul Sander 2025-02-03 21:28 7388
            RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 7389
        }
1193b4be Poul Sander 2025-02-03 21:28 7390
        if (g.NavId == id)
1193b4be Poul Sander 2025-02-03 21:28 7391
        {
1193b4be Poul Sander 2025-02-03 21:28 7392
            ImGuiNavRenderCursorFlags nav_render_cursor_flags = ImGuiNavRenderCursorFlags_Compact | ImGuiNavRenderCursorFlags_NoRounding;
1193b4be Poul Sander 2025-02-03 21:28 7393
            if (is_multi_select)
1193b4be Poul Sander 2025-02-03 21:28 7394
                nav_render_cursor_flags |= ImGuiNavRenderCursorFlags_AlwaysDraw; // Always show the nav rectangle
1193b4be Poul Sander 2025-02-03 21:28 7395
            RenderNavCursor(bb, id, nav_render_cursor_flags);
1193b4be Poul Sander 2025-02-03 21:28 7396
        }
1193b4be Poul Sander 2025-02-03 21:28 7397
    }
1193b4be Poul Sander 2025-02-03 21:28 7398
1193b4be Poul Sander 2025-02-03 21:28 7399
    if (span_all_columns)
1193b4be Poul Sander 2025-02-03 21:28 7400
    {
1193b4be Poul Sander 2025-02-03 21:28 7401
        if (g.CurrentTable)
1193b4be Poul Sander 2025-02-03 21:28 7402
            TablePopBackgroundChannel();
1193b4be Poul Sander 2025-02-03 21:28 7403
        else if (window->DC.CurrentColumns)
1193b4be Poul Sander 2025-02-03 21:28 7404
            PopColumnsBackground();
1193b4be Poul Sander 2025-02-03 21:28 7405
    }
1193b4be Poul Sander 2025-02-03 21:28 7406
4e9e2555 Poul Sander 2025-10-05 10:15 7407
    // Text stays at the submission position. Alignment/clipping extents ignore SpanAllColumns.
1193b4be Poul Sander 2025-02-03 21:28 7408
    if (is_visible)
4e9e2555 Poul Sander 2025-10-05 10:15 7409
        RenderTextClipped(pos, ImVec2(ImMin(pos.x + size.x, window->WorkRect.Max.x), pos.y + size.y), label, NULL, &label_size, style.SelectableTextAlign, &bb);
1193b4be Poul Sander 2025-02-03 21:28 7410
1193b4be Poul Sander 2025-02-03 21:28 7411
    // Automatically close popups
4e9e2555 Poul Sander 2025-10-05 10:15 7412
    if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
1193b4be Poul Sander 2025-02-03 21:28 7413
        CloseCurrentPopup();
1193b4be Poul Sander 2025-02-03 21:28 7414
1193b4be Poul Sander 2025-02-03 21:28 7415
    if (disabled_item && !disabled_global)
1193b4be Poul Sander 2025-02-03 21:28 7416
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 7417
1193b4be Poul Sander 2025-02-03 21:28 7418
    // Selectable() always returns a pressed state!
1193b4be Poul Sander 2025-02-03 21:28 7419
    // Users of BeginMultiSelect()/EndMultiSelect() scope: you may call ImGui::IsItemToggledSelection() to retrieve
1193b4be Poul Sander 2025-02-03 21:28 7420
    // selection toggle, only useful if you need that state updated (e.g. for rendering purpose) before reaching EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 7421
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 7422
    return pressed; //-V1020
1193b4be Poul Sander 2025-02-03 21:28 7423
}
1193b4be Poul Sander 2025-02-03 21:28 7424
1193b4be Poul Sander 2025-02-03 21:28 7425
bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 7426
{
1193b4be Poul Sander 2025-02-03 21:28 7427
    if (Selectable(label, *p_selected, flags, size_arg))
1193b4be Poul Sander 2025-02-03 21:28 7428
    {
1193b4be Poul Sander 2025-02-03 21:28 7429
        *p_selected = !*p_selected;
1193b4be Poul Sander 2025-02-03 21:28 7430
        return true;
1193b4be Poul Sander 2025-02-03 21:28 7431
    }
1193b4be Poul Sander 2025-02-03 21:28 7432
    return false;
1193b4be Poul Sander 2025-02-03 21:28 7433
}
1193b4be Poul Sander 2025-02-03 21:28 7434
1193b4be Poul Sander 2025-02-03 21:28 7435
1193b4be Poul Sander 2025-02-03 21:28 7436
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7437
// [SECTION] Widgets: Typing-Select support
1193b4be Poul Sander 2025-02-03 21:28 7438
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7439
1193b4be Poul Sander 2025-02-03 21:28 7440
// [Experimental] Currently not exposed in public API.
1193b4be Poul Sander 2025-02-03 21:28 7441
// Consume character inputs and return search request, if any.
1193b4be Poul Sander 2025-02-03 21:28 7442
// This would typically only be called on the focused window or location you want to grab inputs for, e.g.
1193b4be Poul Sander 2025-02-03 21:28 7443
//   if (ImGui::IsWindowFocused(...))
1193b4be Poul Sander 2025-02-03 21:28 7444
//       if (ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest())
1193b4be Poul Sander 2025-02-03 21:28 7445
//           focus_idx = ImGui::TypingSelectFindMatch(req, my_items.size(), [](void*, int n) { return my_items[n]->Name; }, &my_items, -1);
1193b4be Poul Sander 2025-02-03 21:28 7446
// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer).
1193b4be Poul Sander 2025-02-03 21:28 7447
ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 7448
{
1193b4be Poul Sander 2025-02-03 21:28 7449
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7450
    ImGuiTypingSelectState* data = &g.TypingSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7451
    ImGuiTypingSelectRequest* out_request = &data->Request;
1193b4be Poul Sander 2025-02-03 21:28 7452
1193b4be Poul Sander 2025-02-03 21:28 7453
    // Clear buffer
1193b4be Poul Sander 2025-02-03 21:28 7454
    const float TYPING_SELECT_RESET_TIMER = 1.80f;          // FIXME: Potentially move to IO config.
1193b4be Poul Sander 2025-02-03 21:28 7455
    const int TYPING_SELECT_SINGLE_CHAR_COUNT_FOR_LOCK = 4; // Lock single char matching when repeating same char 4 times
1193b4be Poul Sander 2025-02-03 21:28 7456
    if (data->SearchBuffer[0] != 0)
1193b4be Poul Sander 2025-02-03 21:28 7457
    {
1193b4be Poul Sander 2025-02-03 21:28 7458
        bool clear_buffer = false;
1193b4be Poul Sander 2025-02-03 21:28 7459
        clear_buffer |= (g.NavFocusScopeId != data->FocusScope);
1193b4be Poul Sander 2025-02-03 21:28 7460
        clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time);
1193b4be Poul Sander 2025-02-03 21:28 7461
        clear_buffer |= g.NavAnyRequest;
1193b4be Poul Sander 2025-02-03 21:28 7462
        clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere
1193b4be Poul Sander 2025-02-03 21:28 7463
        clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter);
1193b4be Poul Sander 2025-02-03 21:28 7464
        clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0;
1193b4be Poul Sander 2025-02-03 21:28 7465
        //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); }
1193b4be Poul Sander 2025-02-03 21:28 7466
        if (clear_buffer)
1193b4be Poul Sander 2025-02-03 21:28 7467
            data->Clear();
1193b4be Poul Sander 2025-02-03 21:28 7468
    }
1193b4be Poul Sander 2025-02-03 21:28 7469
1193b4be Poul Sander 2025-02-03 21:28 7470
    // Append to buffer
1193b4be Poul Sander 2025-02-03 21:28 7471
    const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1;
4e9e2555 Poul Sander 2025-10-05 10:15 7472
    int buffer_len = (int)ImStrlen(data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7473
    bool select_request = false;
1193b4be Poul Sander 2025-02-03 21:28 7474
    for (ImWchar w : g.IO.InputQueueCharacters)
1193b4be Poul Sander 2025-02-03 21:28 7475
    {
1193b4be Poul Sander 2025-02-03 21:28 7476
        const int w_len = ImTextCountUtf8BytesFromStr(&w, &w + 1);
1193b4be Poul Sander 2025-02-03 21:28 7477
        if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w)) || (buffer_len + w_len > buffer_max_len)) // Ignore leading blanks
1193b4be Poul Sander 2025-02-03 21:28 7478
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7479
        char w_buf[5];
1193b4be Poul Sander 2025-02-03 21:28 7480
        ImTextCharToUtf8(w_buf, (unsigned int)w);
1193b4be Poul Sander 2025-02-03 21:28 7481
        if (data->SingleCharModeLock && w_len == out_request->SingleCharSize && memcmp(w_buf, data->SearchBuffer, w_len) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7482
        {
1193b4be Poul Sander 2025-02-03 21:28 7483
            select_request = true; // Same character: don't need to append to buffer.
1193b4be Poul Sander 2025-02-03 21:28 7484
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7485
        }
1193b4be Poul Sander 2025-02-03 21:28 7486
        if (data->SingleCharModeLock)
1193b4be Poul Sander 2025-02-03 21:28 7487
        {
1193b4be Poul Sander 2025-02-03 21:28 7488
            data->Clear(); // Different character: clear
1193b4be Poul Sander 2025-02-03 21:28 7489
            buffer_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7490
        }
1193b4be Poul Sander 2025-02-03 21:28 7491
        memcpy(data->SearchBuffer + buffer_len, w_buf, w_len + 1); // Append
1193b4be Poul Sander 2025-02-03 21:28 7492
        buffer_len += w_len;
1193b4be Poul Sander 2025-02-03 21:28 7493
        select_request = true;
1193b4be Poul Sander 2025-02-03 21:28 7494
    }
1193b4be Poul Sander 2025-02-03 21:28 7495
    g.IO.InputQueueCharacters.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7496
1193b4be Poul Sander 2025-02-03 21:28 7497
    // Handle backspace
1193b4be Poul Sander 2025-02-03 21:28 7498
    if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, ImGuiInputFlags_Repeat))
1193b4be Poul Sander 2025-02-03 21:28 7499
    {
1193b4be Poul Sander 2025-02-03 21:28 7500
        char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len);
1193b4be Poul Sander 2025-02-03 21:28 7501
        *p = 0;
1193b4be Poul Sander 2025-02-03 21:28 7502
        buffer_len = (int)(p - data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7503
    }
1193b4be Poul Sander 2025-02-03 21:28 7504
1193b4be Poul Sander 2025-02-03 21:28 7505
    // Return request if any
1193b4be Poul Sander 2025-02-03 21:28 7506
    if (buffer_len == 0)
1193b4be Poul Sander 2025-02-03 21:28 7507
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 7508
    if (select_request)
1193b4be Poul Sander 2025-02-03 21:28 7509
    {
1193b4be Poul Sander 2025-02-03 21:28 7510
        data->FocusScope = g.NavFocusScopeId;
1193b4be Poul Sander 2025-02-03 21:28 7511
        data->LastRequestFrame = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 7512
        data->LastRequestTime = (float)g.Time;
1193b4be Poul Sander 2025-02-03 21:28 7513
    }
1193b4be Poul Sander 2025-02-03 21:28 7514
    out_request->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 7515
    out_request->SearchBufferLen = buffer_len;
1193b4be Poul Sander 2025-02-03 21:28 7516
    out_request->SearchBuffer = data->SearchBuffer;
1193b4be Poul Sander 2025-02-03 21:28 7517
    out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 7518
    out_request->SingleCharMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7519
    out_request->SingleCharSize = 0;
1193b4be Poul Sander 2025-02-03 21:28 7520
1193b4be Poul Sander 2025-02-03 21:28 7521
    // Calculate if buffer contains the same character repeated.
1193b4be Poul Sander 2025-02-03 21:28 7522
    // - This can be used to implement a special search mode on first character.
1193b4be Poul Sander 2025-02-03 21:28 7523
    // - Performed on UTF-8 codepoint for correctness.
1193b4be Poul Sander 2025-02-03 21:28 7524
    // - SingleCharMode is always set for first input character, because it usually leads to a "next".
1193b4be Poul Sander 2025-02-03 21:28 7525
    if (flags & ImGuiTypingSelectFlags_AllowSingleCharMode)
1193b4be Poul Sander 2025-02-03 21:28 7526
    {
1193b4be Poul Sander 2025-02-03 21:28 7527
        const char* buf_begin = out_request->SearchBuffer;
1193b4be Poul Sander 2025-02-03 21:28 7528
        const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen;
1193b4be Poul Sander 2025-02-03 21:28 7529
        const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end);
1193b4be Poul Sander 2025-02-03 21:28 7530
        const char* p = buf_begin + c0_len;
1193b4be Poul Sander 2025-02-03 21:28 7531
        for (; p < buf_end; p += c0_len)
1193b4be Poul Sander 2025-02-03 21:28 7532
            if (memcmp(buf_begin, p, (size_t)c0_len) != 0)
1193b4be Poul Sander 2025-02-03 21:28 7533
                break;
1193b4be Poul Sander 2025-02-03 21:28 7534
        const int single_char_count = (p == buf_end) ? (out_request->SearchBufferLen / c0_len) : 0;
1193b4be Poul Sander 2025-02-03 21:28 7535
        out_request->SingleCharMode = (single_char_count > 0 || data->SingleCharModeLock);
1193b4be Poul Sander 2025-02-03 21:28 7536
        out_request->SingleCharSize = (ImS8)c0_len;
1193b4be Poul Sander 2025-02-03 21:28 7537
        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.
1193b4be Poul Sander 2025-02-03 21:28 7538
    }
1193b4be Poul Sander 2025-02-03 21:28 7539
1193b4be Poul Sander 2025-02-03 21:28 7540
    return out_request;
1193b4be Poul Sander 2025-02-03 21:28 7541
}
1193b4be Poul Sander 2025-02-03 21:28 7542
1193b4be Poul Sander 2025-02-03 21:28 7543
static int ImStrimatchlen(const char* s1, const char* s1_end, const char* s2)
1193b4be Poul Sander 2025-02-03 21:28 7544
{
1193b4be Poul Sander 2025-02-03 21:28 7545
    int match_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7546
    while (s1 < s1_end && ImToUpper(*s1++) == ImToUpper(*s2++))
1193b4be Poul Sander 2025-02-03 21:28 7547
        match_len++;
1193b4be Poul Sander 2025-02-03 21:28 7548
    return match_len;
1193b4be Poul Sander 2025-02-03 21:28 7549
}
1193b4be Poul Sander 2025-02-03 21:28 7550
1193b4be Poul Sander 2025-02-03 21:28 7551
// Default handler for finding a result for typing-select. You may implement your own.
1193b4be Poul Sander 2025-02-03 21:28 7552
// You might want to display a tooltip to visualize the current request SearchBuffer
1193b4be Poul Sander 2025-02-03 21:28 7553
// When SingleCharMode is set:
1193b4be Poul Sander 2025-02-03 21:28 7554
// - it is better to NOT display a tooltip of other on-screen display indicator.
1193b4be Poul Sander 2025-02-03 21:28 7555
// - the index of the currently focused item is required.
1193b4be Poul Sander 2025-02-03 21:28 7556
//   if your SetNextItemSelectionUserData() values are indices, you can obtain it from ImGuiMultiSelectIO::NavIdItem, otherwise from g.NavLastValidSelectionUserData.
1193b4be Poul Sander 2025-02-03 21:28 7557
int ImGui::TypingSelectFindMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
1193b4be Poul Sander 2025-02-03 21:28 7558
{
1193b4be Poul Sander 2025-02-03 21:28 7559
    if (req == NULL || req->SelectRequest == false) // Support NULL parameter so both calls can be done from same spot.
1193b4be Poul Sander 2025-02-03 21:28 7560
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 7561
    int idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7562
    if (req->SingleCharMode && (req->Flags & ImGuiTypingSelectFlags_AllowSingleCharMode))
1193b4be Poul Sander 2025-02-03 21:28 7563
        idx = TypingSelectFindNextSingleCharMatch(req, items_count, get_item_name_func, user_data, nav_item_idx);
1193b4be Poul Sander 2025-02-03 21:28 7564
    else
1193b4be Poul Sander 2025-02-03 21:28 7565
        idx = TypingSelectFindBestLeadingMatch(req, items_count, get_item_name_func, user_data);
1193b4be Poul Sander 2025-02-03 21:28 7566
    if (idx != -1)
1193b4be Poul Sander 2025-02-03 21:28 7567
        SetNavCursorVisibleAfterMove();
1193b4be Poul Sander 2025-02-03 21:28 7568
    return idx;
1193b4be Poul Sander 2025-02-03 21:28 7569
}
1193b4be Poul Sander 2025-02-03 21:28 7570
1193b4be Poul Sander 2025-02-03 21:28 7571
// Special handling when a single character is repeated: perform search on a single letter and goes to next.
1193b4be Poul Sander 2025-02-03 21:28 7572
int ImGui::TypingSelectFindNextSingleCharMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data, int nav_item_idx)
1193b4be Poul Sander 2025-02-03 21:28 7573
{
1193b4be Poul Sander 2025-02-03 21:28 7574
    // FIXME: Assume selection user data is index. Would be extremely practical.
1193b4be Poul Sander 2025-02-03 21:28 7575
    //if (nav_item_idx == -1)
1193b4be Poul Sander 2025-02-03 21:28 7576
    //    nav_item_idx = (int)g.NavLastValidSelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 7577
1193b4be Poul Sander 2025-02-03 21:28 7578
    int first_match_idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7579
    bool return_next_match = false;
1193b4be Poul Sander 2025-02-03 21:28 7580
    for (int idx = 0; idx < items_count; idx++)
1193b4be Poul Sander 2025-02-03 21:28 7581
    {
1193b4be Poul Sander 2025-02-03 21:28 7582
        const char* item_name = get_item_name_func(user_data, idx);
1193b4be Poul Sander 2025-02-03 21:28 7583
        if (ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SingleCharSize, item_name) < req->SingleCharSize)
1193b4be Poul Sander 2025-02-03 21:28 7584
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7585
        if (return_next_match)                           // Return next matching item after current item.
1193b4be Poul Sander 2025-02-03 21:28 7586
            return idx;
1193b4be Poul Sander 2025-02-03 21:28 7587
        if (first_match_idx == -1 && nav_item_idx == -1) // Return first match immediately if we don't have a nav_item_idx value.
1193b4be Poul Sander 2025-02-03 21:28 7588
            return idx;
1193b4be Poul Sander 2025-02-03 21:28 7589
        if (first_match_idx == -1)                       // Record first match for wrapping.
1193b4be Poul Sander 2025-02-03 21:28 7590
            first_match_idx = idx;
1193b4be Poul Sander 2025-02-03 21:28 7591
        if (nav_item_idx == idx)                         // Record that we encountering nav_item so we can return next match.
1193b4be Poul Sander 2025-02-03 21:28 7592
            return_next_match = true;
1193b4be Poul Sander 2025-02-03 21:28 7593
    }
1193b4be Poul Sander 2025-02-03 21:28 7594
    return first_match_idx; // First result
1193b4be Poul Sander 2025-02-03 21:28 7595
}
1193b4be Poul Sander 2025-02-03 21:28 7596
1193b4be Poul Sander 2025-02-03 21:28 7597
int ImGui::TypingSelectFindBestLeadingMatch(ImGuiTypingSelectRequest* req, int items_count, const char* (*get_item_name_func)(void*, int), void* user_data)
1193b4be Poul Sander 2025-02-03 21:28 7598
{
1193b4be Poul Sander 2025-02-03 21:28 7599
    int longest_match_idx = -1;
1193b4be Poul Sander 2025-02-03 21:28 7600
    int longest_match_len = 0;
1193b4be Poul Sander 2025-02-03 21:28 7601
    for (int idx = 0; idx < items_count; idx++)
1193b4be Poul Sander 2025-02-03 21:28 7602
    {
1193b4be Poul Sander 2025-02-03 21:28 7603
        const char* item_name = get_item_name_func(user_data, idx);
1193b4be Poul Sander 2025-02-03 21:28 7604
        const int match_len = ImStrimatchlen(req->SearchBuffer, req->SearchBuffer + req->SearchBufferLen, item_name);
1193b4be Poul Sander 2025-02-03 21:28 7605
        if (match_len <= longest_match_len)
1193b4be Poul Sander 2025-02-03 21:28 7606
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7607
        longest_match_idx = idx;
1193b4be Poul Sander 2025-02-03 21:28 7608
        longest_match_len = match_len;
1193b4be Poul Sander 2025-02-03 21:28 7609
        if (match_len == req->SearchBufferLen)
1193b4be Poul Sander 2025-02-03 21:28 7610
            break;
1193b4be Poul Sander 2025-02-03 21:28 7611
    }
1193b4be Poul Sander 2025-02-03 21:28 7612
    return longest_match_idx;
1193b4be Poul Sander 2025-02-03 21:28 7613
}
1193b4be Poul Sander 2025-02-03 21:28 7614
1193b4be Poul Sander 2025-02-03 21:28 7615
void ImGui::DebugNodeTypingSelectState(ImGuiTypingSelectState* data)
1193b4be Poul Sander 2025-02-03 21:28 7616
{
1193b4be Poul Sander 2025-02-03 21:28 7617
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 7618
    Text("SearchBuffer = \"%s\"", data->SearchBuffer);
1193b4be Poul Sander 2025-02-03 21:28 7619
    Text("SingleCharMode = %d, Size = %d, Lock = %d", data->Request.SingleCharMode, data->Request.SingleCharSize, data->SingleCharModeLock);
1193b4be Poul Sander 2025-02-03 21:28 7620
    Text("LastRequest = time: %.2f, frame: %d", data->LastRequestTime, data->LastRequestFrame);
1193b4be Poul Sander 2025-02-03 21:28 7621
#else
1193b4be Poul Sander 2025-02-03 21:28 7622
    IM_UNUSED(data);
1193b4be Poul Sander 2025-02-03 21:28 7623
#endif
1193b4be Poul Sander 2025-02-03 21:28 7624
}
1193b4be Poul Sander 2025-02-03 21:28 7625
1193b4be Poul Sander 2025-02-03 21:28 7626
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7627
// [SECTION] Widgets: Box-Select support
1193b4be Poul Sander 2025-02-03 21:28 7628
// This has been extracted away from Multi-Select logic in the hope that it could eventually be used elsewhere, but hasn't been yet.
1193b4be Poul Sander 2025-02-03 21:28 7629
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7630
// Extra logic in MultiSelectItemFooter() and ImGuiListClipper::Step()
1193b4be Poul Sander 2025-02-03 21:28 7631
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7632
// - BoxSelectPreStartDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7633
// - BoxSelectActivateDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7634
// - BoxSelectDeactivateDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7635
// - BoxSelectScrollWithMouseDrag() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7636
// - BeginBoxSelect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7637
// - EndBoxSelect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7638
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7639
1193b4be Poul Sander 2025-02-03 21:28 7640
// Call on the initial click.
1193b4be Poul Sander 2025-02-03 21:28 7641
static void BoxSelectPreStartDrag(ImGuiID id, ImGuiSelectionUserData clicked_item)
1193b4be Poul Sander 2025-02-03 21:28 7642
{
1193b4be Poul Sander 2025-02-03 21:28 7643
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7644
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7645
    bs->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 7646
    bs->IsStarting = true; // Consider starting box-select.
1193b4be Poul Sander 2025-02-03 21:28 7647
    bs->IsStartedFromVoid = (clicked_item == ImGuiSelectionUserData_Invalid);
1193b4be Poul Sander 2025-02-03 21:28 7648
    bs->IsStartedSetNavIdOnce = bs->IsStartedFromVoid;
1193b4be Poul Sander 2025-02-03 21:28 7649
    bs->KeyMods = g.IO.KeyMods;
1193b4be Poul Sander 2025-02-03 21:28 7650
    bs->StartPosRel = bs->EndPosRel = ImGui::WindowPosAbsToRel(g.CurrentWindow, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7651
    bs->ScrollAccum = ImVec2(0.0f, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 7652
}
1193b4be Poul Sander 2025-02-03 21:28 7653
1193b4be Poul Sander 2025-02-03 21:28 7654
static void BoxSelectActivateDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 7655
{
1193b4be Poul Sander 2025-02-03 21:28 7656
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7657
    IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Activate\n", bs->ID);
1193b4be Poul Sander 2025-02-03 21:28 7658
    bs->IsActive = true;
1193b4be Poul Sander 2025-02-03 21:28 7659
    bs->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 7660
    bs->IsStarting = false;
1193b4be Poul Sander 2025-02-03 21:28 7661
    ImGui::SetActiveID(bs->ID, window);
1193b4be Poul Sander 2025-02-03 21:28 7662
    ImGui::SetActiveIdUsingAllKeyboardKeys();
1193b4be Poul Sander 2025-02-03 21:28 7663
    if (bs->IsStartedFromVoid && (bs->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7664
        bs->RequestClear = true;
1193b4be Poul Sander 2025-02-03 21:28 7665
}
1193b4be Poul Sander 2025-02-03 21:28 7666
1193b4be Poul Sander 2025-02-03 21:28 7667
static void BoxSelectDeactivateDrag(ImGuiBoxSelectState* bs)
1193b4be Poul Sander 2025-02-03 21:28 7668
{
1193b4be Poul Sander 2025-02-03 21:28 7669
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7670
    bs->IsActive = bs->IsStarting = false;
1193b4be Poul Sander 2025-02-03 21:28 7671
    if (g.ActiveId == bs->ID)
1193b4be Poul Sander 2025-02-03 21:28 7672
    {
1193b4be Poul Sander 2025-02-03 21:28 7673
        IMGUI_DEBUG_LOG_SELECTION("[selection] BeginBoxSelect() 0X%08X: Deactivate\n", bs->ID);
1193b4be Poul Sander 2025-02-03 21:28 7674
        ImGui::ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 7675
    }
1193b4be Poul Sander 2025-02-03 21:28 7676
    bs->ID = 0;
1193b4be Poul Sander 2025-02-03 21:28 7677
}
1193b4be Poul Sander 2025-02-03 21:28 7678
1193b4be Poul Sander 2025-02-03 21:28 7679
static void BoxSelectScrollWithMouseDrag(ImGuiBoxSelectState* bs, ImGuiWindow* window, const ImRect& inner_r)
1193b4be Poul Sander 2025-02-03 21:28 7680
{
1193b4be Poul Sander 2025-02-03 21:28 7681
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7682
    IM_ASSERT(bs->Window == window);
1193b4be Poul Sander 2025-02-03 21:28 7683
    for (int n = 0; n < 2; n++) // each axis
1193b4be Poul Sander 2025-02-03 21:28 7684
    {
1193b4be Poul Sander 2025-02-03 21:28 7685
        const float mouse_pos = g.IO.MousePos[n];
1193b4be Poul Sander 2025-02-03 21:28 7686
        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;
1193b4be Poul Sander 2025-02-03 21:28 7687
        const float scroll_curr = window->Scroll[n];
1193b4be Poul Sander 2025-02-03 21:28 7688
        if (dist == 0.0f || (dist < 0.0f && scroll_curr < 0.0f) || (dist > 0.0f && scroll_curr >= window->ScrollMax[n]))
1193b4be Poul Sander 2025-02-03 21:28 7689
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7690
1193b4be Poul Sander 2025-02-03 21:28 7691
        const float speed_multiplier = ImLinearRemapClamp(g.FontSize, g.FontSize * 5.0f, 1.0f, 4.0f, ImAbs(dist)); // x1 to x4 depending on distance
1193b4be Poul Sander 2025-02-03 21:28 7692
        const float scroll_step = g.FontSize * 35.0f * speed_multiplier * ImSign(dist) * g.IO.DeltaTime;
1193b4be Poul Sander 2025-02-03 21:28 7693
        bs->ScrollAccum[n] += scroll_step;
1193b4be Poul Sander 2025-02-03 21:28 7694
1193b4be Poul Sander 2025-02-03 21:28 7695
        // Accumulate into a stored value so we can handle high-framerate
1193b4be Poul Sander 2025-02-03 21:28 7696
        const float scroll_step_i = ImFloor(bs->ScrollAccum[n]);
1193b4be Poul Sander 2025-02-03 21:28 7697
        if (scroll_step_i == 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 7698
            continue;
1193b4be Poul Sander 2025-02-03 21:28 7699
        if (n == 0)
1193b4be Poul Sander 2025-02-03 21:28 7700
            ImGui::SetScrollX(window, scroll_curr + scroll_step_i);
1193b4be Poul Sander 2025-02-03 21:28 7701
        else
1193b4be Poul Sander 2025-02-03 21:28 7702
            ImGui::SetScrollY(window, scroll_curr + scroll_step_i);
1193b4be Poul Sander 2025-02-03 21:28 7703
        bs->ScrollAccum[n] -= scroll_step_i;
1193b4be Poul Sander 2025-02-03 21:28 7704
    }
1193b4be Poul Sander 2025-02-03 21:28 7705
}
1193b4be Poul Sander 2025-02-03 21:28 7706
1193b4be Poul Sander 2025-02-03 21:28 7707
bool ImGui::BeginBoxSelect(const ImRect& scope_rect, ImGuiWindow* window, ImGuiID box_select_id, ImGuiMultiSelectFlags ms_flags)
1193b4be Poul Sander 2025-02-03 21:28 7708
{
1193b4be Poul Sander 2025-02-03 21:28 7709
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7710
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7711
    KeepAliveID(box_select_id);
1193b4be Poul Sander 2025-02-03 21:28 7712
    if (bs->ID != box_select_id)
1193b4be Poul Sander 2025-02-03 21:28 7713
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7714
1193b4be Poul Sander 2025-02-03 21:28 7715
    // IsStarting is set by MultiSelectItemFooter() when considering a possible box-select. We validate it here and lock geometry.
1193b4be Poul Sander 2025-02-03 21:28 7716
    bs->UnclipMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7717
    bs->RequestClear = false;
1193b4be Poul Sander 2025-02-03 21:28 7718
    if (bs->IsStarting && IsMouseDragPastThreshold(0))
1193b4be Poul Sander 2025-02-03 21:28 7719
        BoxSelectActivateDrag(bs, window);
1193b4be Poul Sander 2025-02-03 21:28 7720
    else if ((bs->IsStarting || bs->IsActive) && g.IO.MouseDown[0] == false)
1193b4be Poul Sander 2025-02-03 21:28 7721
        BoxSelectDeactivateDrag(bs);
1193b4be Poul Sander 2025-02-03 21:28 7722
    if (!bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7723
        return false;
1193b4be Poul Sander 2025-02-03 21:28 7724
1193b4be Poul Sander 2025-02-03 21:28 7725
    // Current frame absolute prev/current rectangles are used to toggle selection.
1193b4be Poul Sander 2025-02-03 21:28 7726
    // They are derived from positions relative to scrolling space.
1193b4be Poul Sander 2025-02-03 21:28 7727
    ImVec2 start_pos_abs = WindowPosRelToAbs(window, bs->StartPosRel);
1193b4be Poul Sander 2025-02-03 21:28 7728
    ImVec2 prev_end_pos_abs = WindowPosRelToAbs(window, bs->EndPosRel); // Clamped already
1193b4be Poul Sander 2025-02-03 21:28 7729
    ImVec2 curr_end_pos_abs = g.IO.MousePos;
1193b4be Poul Sander 2025-02-03 21:28 7730
    if (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) // Box-select scrolling only happens with ScopeWindow
1193b4be Poul Sander 2025-02-03 21:28 7731
        curr_end_pos_abs = ImClamp(curr_end_pos_abs, scope_rect.Min, scope_rect.Max);
1193b4be Poul Sander 2025-02-03 21:28 7732
    bs->BoxSelectRectPrev.Min = ImMin(start_pos_abs, prev_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7733
    bs->BoxSelectRectPrev.Max = ImMax(start_pos_abs, prev_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7734
    bs->BoxSelectRectCurr.Min = ImMin(start_pos_abs, curr_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7735
    bs->BoxSelectRectCurr.Max = ImMax(start_pos_abs, curr_end_pos_abs);
1193b4be Poul Sander 2025-02-03 21:28 7736
1193b4be Poul Sander 2025-02-03 21:28 7737
    // Box-select 2D mode detects horizontal changes (vertical ones are already picked by Clipper)
1193b4be Poul Sander 2025-02-03 21:28 7738
    // Storing an extra rect used by widgets supporting box-select.
1193b4be Poul Sander 2025-02-03 21:28 7739
    if (ms_flags & ImGuiMultiSelectFlags_BoxSelect2d)
1193b4be Poul Sander 2025-02-03 21:28 7740
        if (bs->BoxSelectRectPrev.Min.x != bs->BoxSelectRectCurr.Min.x || bs->BoxSelectRectPrev.Max.x != bs->BoxSelectRectCurr.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 7741
        {
1193b4be Poul Sander 2025-02-03 21:28 7742
            bs->UnclipMode = true;
1193b4be Poul Sander 2025-02-03 21:28 7743
            bs->UnclipRect = bs->BoxSelectRectPrev; // FIXME-OPT: UnclipRect x coordinates could be intersection of Prev and Curr rect on X axis.
1193b4be Poul Sander 2025-02-03 21:28 7744
            bs->UnclipRect.Add(bs->BoxSelectRectCurr);
1193b4be Poul Sander 2025-02-03 21:28 7745
        }
1193b4be Poul Sander 2025-02-03 21:28 7746
1193b4be Poul Sander 2025-02-03 21:28 7747
    //GetForegroundDrawList()->AddRect(bs->UnclipRect.Min, bs->UnclipRect.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
1193b4be Poul Sander 2025-02-03 21:28 7748
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectPrev.Min, bs->BoxSelectRectPrev.Max, IM_COL32(255,0,0,200), 0.0f, 0, 3.0f);
1193b4be Poul Sander 2025-02-03 21:28 7749
    //GetForegroundDrawList()->AddRect(bs->BoxSelectRectCurr.Min, bs->BoxSelectRectCurr.Max, IM_COL32(0,255,0,200), 0.0f, 0, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 7750
    return true;
1193b4be Poul Sander 2025-02-03 21:28 7751
}
1193b4be Poul Sander 2025-02-03 21:28 7752
1193b4be Poul Sander 2025-02-03 21:28 7753
void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flags)
1193b4be Poul Sander 2025-02-03 21:28 7754
{
1193b4be Poul Sander 2025-02-03 21:28 7755
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7756
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7757
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7758
    IM_ASSERT(bs->IsActive);
1193b4be Poul Sander 2025-02-03 21:28 7759
    bs->UnclipMode = false;
1193b4be Poul Sander 2025-02-03 21:28 7760
1193b4be Poul Sander 2025-02-03 21:28 7761
    // Render selection rectangle
1193b4be Poul Sander 2025-02-03 21:28 7762
    bs->EndPosRel = WindowPosAbsToRel(window, ImClamp(g.IO.MousePos, scope_rect.Min, scope_rect.Max)); // Clamp stored position according to current scrolling view
1193b4be Poul Sander 2025-02-03 21:28 7763
    ImRect box_select_r = bs->BoxSelectRectCurr;
1193b4be Poul Sander 2025-02-03 21:28 7764
    box_select_r.ClipWith(scope_rect);
1193b4be Poul Sander 2025-02-03 21:28 7765
    window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
4e9e2555 Poul Sander 2025-10-05 10:15 7766
    window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling
1193b4be Poul Sander 2025-02-03 21:28 7767
1193b4be Poul Sander 2025-02-03 21:28 7768
    // Scroll
1193b4be Poul Sander 2025-02-03 21:28 7769
    const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
1193b4be Poul Sander 2025-02-03 21:28 7770
    if (enable_scroll)
1193b4be Poul Sander 2025-02-03 21:28 7771
    {
1193b4be Poul Sander 2025-02-03 21:28 7772
        ImRect scroll_r = scope_rect;
1193b4be Poul Sander 2025-02-03 21:28 7773
        scroll_r.Expand(-g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 7774
        //GetForegroundDrawList()->AddRect(scroll_r.Min, scroll_r.Max, IM_COL32(0, 255, 0, 255));
1193b4be Poul Sander 2025-02-03 21:28 7775
        if (!scroll_r.Contains(g.IO.MousePos))
1193b4be Poul Sander 2025-02-03 21:28 7776
            BoxSelectScrollWithMouseDrag(bs, window, scroll_r);
1193b4be Poul Sander 2025-02-03 21:28 7777
    }
1193b4be Poul Sander 2025-02-03 21:28 7778
}
1193b4be Poul Sander 2025-02-03 21:28 7779
1193b4be Poul Sander 2025-02-03 21:28 7780
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7781
// [SECTION] Widgets: Multi-Select support
1193b4be Poul Sander 2025-02-03 21:28 7782
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7783
// - DebugLogMultiSelectRequests() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7784
// - CalcScopeRect() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7785
// - BeginMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7786
// - EndMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7787
// - SetNextItemSelectionUserData()
1193b4be Poul Sander 2025-02-03 21:28 7788
// - MultiSelectItemHeader() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7789
// - MultiSelectItemFooter() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7790
// - DebugNodeMultiSelectState() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 7791
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 7792
1193b4be Poul Sander 2025-02-03 21:28 7793
static void DebugLogMultiSelectRequests(const char* function, const ImGuiMultiSelectIO* io)
1193b4be Poul Sander 2025-02-03 21:28 7794
{
1193b4be Poul Sander 2025-02-03 21:28 7795
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7796
    IM_UNUSED(function);
1193b4be Poul Sander 2025-02-03 21:28 7797
    for (const ImGuiSelectionRequest& req : io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 7798
    {
1193b4be Poul Sander 2025-02-03 21:28 7799
        if (req.Type == ImGuiSelectionRequestType_SetAll)    IMGUI_DEBUG_LOG_SELECTION("[selection] %s: Request: SetAll %d (= %s)\n", function, req.Selected, req.Selected ? "SelectAll" : "Clear");
1193b4be Poul Sander 2025-02-03 21:28 7800
        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);
1193b4be Poul Sander 2025-02-03 21:28 7801
    }
1193b4be Poul Sander 2025-02-03 21:28 7802
}
1193b4be Poul Sander 2025-02-03 21:28 7803
1193b4be Poul Sander 2025-02-03 21:28 7804
static ImRect CalcScopeRect(ImGuiMultiSelectTempData* ms, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 7805
{
1193b4be Poul Sander 2025-02-03 21:28 7806
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7807
    if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
1193b4be Poul Sander 2025-02-03 21:28 7808
    {
1193b4be Poul Sander 2025-02-03 21:28 7809
        // Warning: this depends on CursorMaxPos so it means to be called by EndMultiSelect() only
1193b4be Poul Sander 2025-02-03 21:28 7810
        return ImRect(ms->ScopeRectMin, ImMax(window->DC.CursorMaxPos, ms->ScopeRectMin));
1193b4be Poul Sander 2025-02-03 21:28 7811
    }
1193b4be Poul Sander 2025-02-03 21:28 7812
    else
1193b4be Poul Sander 2025-02-03 21:28 7813
    {
1193b4be Poul Sander 2025-02-03 21:28 7814
        // When a table, pull HostClipRect, which allows us to predict ClipRect before first row/layout is performed. (#7970)
1193b4be Poul Sander 2025-02-03 21:28 7815
        ImRect scope_rect = window->InnerClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7816
        if (g.CurrentTable != NULL)
1193b4be Poul Sander 2025-02-03 21:28 7817
            scope_rect = g.CurrentTable->HostClipRect;
1193b4be Poul Sander 2025-02-03 21:28 7818
1193b4be Poul Sander 2025-02-03 21:28 7819
        // Add inner table decoration (#7821) // FIXME: Why not baking in InnerClipRect?
1193b4be Poul Sander 2025-02-03 21:28 7820
        scope_rect.Min = ImMin(scope_rect.Min + ImVec2(window->DecoInnerSizeX1, window->DecoInnerSizeY1), scope_rect.Max);
1193b4be Poul Sander 2025-02-03 21:28 7821
        return scope_rect;
1193b4be Poul Sander 2025-02-03 21:28 7822
    }
1193b4be Poul Sander 2025-02-03 21:28 7823
}
1193b4be Poul Sander 2025-02-03 21:28 7824
1193b4be Poul Sander 2025-02-03 21:28 7825
// Return ImGuiMultiSelectIO structure.
1193b4be Poul Sander 2025-02-03 21:28 7826
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 7827
// Passing 'selection_size' and 'items_count' parameters is currently optional.
1193b4be Poul Sander 2025-02-03 21:28 7828
// - 'selection_size' is useful to disable some shortcut routing: e.g. ImGuiMultiSelectFlags_ClearOnEscape won't claim Escape key when selection_size 0,
1193b4be Poul Sander 2025-02-03 21:28 7829
//    allowing a first press to clear selection THEN the second press to leave child window and return to parent.
1193b4be Poul Sander 2025-02-03 21:28 7830
// - '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).
1193b4be Poul Sander 2025-02-03 21:28 7831
// - If they are costly for you to compute (e.g. external intrusive selection without maintaining size), you may avoid them and pass -1.
1193b4be Poul Sander 2025-02-03 21:28 7832
//   - 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.
1193b4be Poul Sander 2025-02-03 21:28 7833
ImGuiMultiSelectIO* ImGui::BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size, int items_count)
1193b4be Poul Sander 2025-02-03 21:28 7834
{
1193b4be Poul Sander 2025-02-03 21:28 7835
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7836
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7837
1193b4be Poul Sander 2025-02-03 21:28 7838
    if (++g.MultiSelectTempDataStacked > g.MultiSelectTempData.Size)
1193b4be Poul Sander 2025-02-03 21:28 7839
        g.MultiSelectTempData.resize(g.MultiSelectTempDataStacked, ImGuiMultiSelectTempData());
1193b4be Poul Sander 2025-02-03 21:28 7840
    ImGuiMultiSelectTempData* ms = &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1];
1193b4be Poul Sander 2025-02-03 21:28 7841
    IM_STATIC_ASSERT(offsetof(ImGuiMultiSelectTempData, IO) == 0); // Clear() relies on that.
1193b4be Poul Sander 2025-02-03 21:28 7842
    g.CurrentMultiSelect = ms;
1193b4be Poul Sander 2025-02-03 21:28 7843
    if ((flags & (ImGuiMultiSelectFlags_ScopeWindow | ImGuiMultiSelectFlags_ScopeRect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7844
        flags |= ImGuiMultiSelectFlags_ScopeWindow;
1193b4be Poul Sander 2025-02-03 21:28 7845
    if (flags & ImGuiMultiSelectFlags_SingleSelect)
1193b4be Poul Sander 2025-02-03 21:28 7846
        flags &= ~(ImGuiMultiSelectFlags_BoxSelect2d | ImGuiMultiSelectFlags_BoxSelect1d);
1193b4be Poul Sander 2025-02-03 21:28 7847
    if (flags & ImGuiMultiSelectFlags_BoxSelect2d)
1193b4be Poul Sander 2025-02-03 21:28 7848
        flags &= ~ImGuiMultiSelectFlags_BoxSelect1d;
1193b4be Poul Sander 2025-02-03 21:28 7849
4e9e2555 Poul Sander 2025-10-05 10:15 7850
    // FIXME: Workaround to the fact we override CursorMaxPos, meaning size measurement are lost. (#8250)
4e9e2555 Poul Sander 2025-10-05 10:15 7851
    // They should perhaps be stacked properly?
4e9e2555 Poul Sander 2025-10-05 10:15 7852
    if (ImGuiTable* table = g.CurrentTable)
4e9e2555 Poul Sander 2025-10-05 10:15 7853
        if (table->CurrentColumn != -1)
4e9e2555 Poul Sander 2025-10-05 10:15 7854
            TableEndCell(table); // This is currently safe to call multiple time. If that properly is lost we can extract the "save measurement" part of it.
4e9e2555 Poul Sander 2025-10-05 10:15 7855
1193b4be Poul Sander 2025-02-03 21:28 7856
    // FIXME: BeginFocusScope()
1193b4be Poul Sander 2025-02-03 21:28 7857
    const ImGuiID id = window->IDStack.back();
1193b4be Poul Sander 2025-02-03 21:28 7858
    ms->Clear();
1193b4be Poul Sander 2025-02-03 21:28 7859
    ms->FocusScopeId = id;
1193b4be Poul Sander 2025-02-03 21:28 7860
    ms->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 7861
    ms->IsFocused = (ms->FocusScopeId == g.NavFocusScopeId);
1193b4be Poul Sander 2025-02-03 21:28 7862
    ms->BackupCursorMaxPos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 7863
    ms->ScopeRectMin = window->DC.CursorMaxPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 7864
    PushFocusScope(ms->FocusScopeId);
1193b4be Poul Sander 2025-02-03 21:28 7865
    if (flags & ImGuiMultiSelectFlags_ScopeWindow) // Mark parent child window as navigable into, with highlight. Assume user will always submit interactive items.
1193b4be Poul Sander 2025-02-03 21:28 7866
        window->DC.NavLayersActiveMask |= 1 << ImGuiNavLayer_Main;
1193b4be Poul Sander 2025-02-03 21:28 7867
1193b4be Poul Sander 2025-02-03 21:28 7868
    // Use copy of keyboard mods at the time of the request, otherwise we would requires mods to be held for an extra frame.
1193b4be Poul Sander 2025-02-03 21:28 7869
    ms->KeyMods = g.NavJustMovedToId ? (g.NavJustMovedToIsTabbing ? 0 : g.NavJustMovedToKeyMods) : g.IO.KeyMods;
1193b4be Poul Sander 2025-02-03 21:28 7870
    if (flags & ImGuiMultiSelectFlags_NoRangeSelect)
1193b4be Poul Sander 2025-02-03 21:28 7871
        ms->KeyMods &= ~ImGuiMod_Shift;
1193b4be Poul Sander 2025-02-03 21:28 7872
1193b4be Poul Sander 2025-02-03 21:28 7873
    // Bind storage
1193b4be Poul Sander 2025-02-03 21:28 7874
    ImGuiMultiSelectState* storage = g.MultiSelectStorage.GetOrAddByKey(id);
1193b4be Poul Sander 2025-02-03 21:28 7875
    storage->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 7876
    storage->LastFrameActive = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 7877
    storage->LastSelectionSize = selection_size;
1193b4be Poul Sander 2025-02-03 21:28 7878
    storage->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 7879
    ms->Storage = storage;
1193b4be Poul Sander 2025-02-03 21:28 7880
1193b4be Poul Sander 2025-02-03 21:28 7881
    // Output to user
1193b4be Poul Sander 2025-02-03 21:28 7882
    ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7883
    ms->IO.RangeSrcItem = storage->RangeSrcItem;
1193b4be Poul Sander 2025-02-03 21:28 7884
    ms->IO.NavIdItem = storage->NavIdItem;
1193b4be Poul Sander 2025-02-03 21:28 7885
    ms->IO.NavIdSelected = (storage->NavIdSelected == 1) ? true : false;
1193b4be Poul Sander 2025-02-03 21:28 7886
    ms->IO.ItemsCount = items_count;
1193b4be Poul Sander 2025-02-03 21:28 7887
1193b4be Poul Sander 2025-02-03 21:28 7888
    // Clear when using Navigation to move within the scope
1193b4be Poul Sander 2025-02-03 21:28 7889
    // (we compare FocusScopeId so it possible to use multiple selections inside a same window)
1193b4be Poul Sander 2025-02-03 21:28 7890
    bool request_clear = false;
1193b4be Poul Sander 2025-02-03 21:28 7891
    bool request_select_all = false;
1193b4be Poul Sander 2025-02-03 21:28 7892
    if (g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == ms->FocusScopeId && g.NavJustMovedToHasSelectionData)
1193b4be Poul Sander 2025-02-03 21:28 7893
    {
1193b4be Poul Sander 2025-02-03 21:28 7894
        if (ms->KeyMods & ImGuiMod_Shift)
1193b4be Poul Sander 2025-02-03 21:28 7895
            ms->IsKeyboardSetRange = true;
1193b4be Poul Sander 2025-02-03 21:28 7896
        if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 7897
            IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid); // Not ready -> could clear?
1193b4be Poul Sander 2025-02-03 21:28 7898
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7899
            request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7900
    }
1193b4be Poul Sander 2025-02-03 21:28 7901
    else if (g.NavJustMovedFromFocusScopeId == ms->FocusScopeId)
1193b4be Poul Sander 2025-02-03 21:28 7902
    {
1193b4be Poul Sander 2025-02-03 21:28 7903
        // Also clear on leaving scope (may be optional?)
1193b4be Poul Sander 2025-02-03 21:28 7904
        if ((ms->KeyMods & (ImGuiMod_Ctrl | ImGuiMod_Shift)) == 0 && (flags & (ImGuiMultiSelectFlags_NoAutoClear | ImGuiMultiSelectFlags_NoAutoSelect)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 7905
            request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7906
    }
1193b4be Poul Sander 2025-02-03 21:28 7907
1193b4be Poul Sander 2025-02-03 21:28 7908
    // Box-select handling: update active state.
1193b4be Poul Sander 2025-02-03 21:28 7909
    ImGuiBoxSelectState* bs = &g.BoxSelectState;
1193b4be Poul Sander 2025-02-03 21:28 7910
    if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 7911
    {
1193b4be Poul Sander 2025-02-03 21:28 7912
        ms->BoxSelectId = GetID("##BoxSelect");
1193b4be Poul Sander 2025-02-03 21:28 7913
        if (BeginBoxSelect(CalcScopeRect(ms, window), window, ms->BoxSelectId, flags))
1193b4be Poul Sander 2025-02-03 21:28 7914
            request_clear |= bs->RequestClear;
1193b4be Poul Sander 2025-02-03 21:28 7915
    }
1193b4be Poul Sander 2025-02-03 21:28 7916
1193b4be Poul Sander 2025-02-03 21:28 7917
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 7918
    {
1193b4be Poul Sander 2025-02-03 21:28 7919
        // Shortcut: Clear selection (Escape)
1193b4be Poul Sander 2025-02-03 21:28 7920
        // - Only claim shortcut if selection is not empty, allowing further presses on Escape to e.g. leave current child window.
1193b4be Poul Sander 2025-02-03 21:28 7921
        // - Box select also handle Escape and needs to pass an id to bypass ActiveIdUsingAllKeyboardKeys lock.
1193b4be Poul Sander 2025-02-03 21:28 7922
        if (flags & ImGuiMultiSelectFlags_ClearOnEscape)
1193b4be Poul Sander 2025-02-03 21:28 7923
        {
1193b4be Poul Sander 2025-02-03 21:28 7924
            if (selection_size != 0 || bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7925
                if (Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, bs->IsActive ? bs->ID : 0))
1193b4be Poul Sander 2025-02-03 21:28 7926
                {
1193b4be Poul Sander 2025-02-03 21:28 7927
                    request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 7928
                    if (bs->IsActive)
1193b4be Poul Sander 2025-02-03 21:28 7929
                        BoxSelectDeactivateDrag(bs);
1193b4be Poul Sander 2025-02-03 21:28 7930
                }
1193b4be Poul Sander 2025-02-03 21:28 7931
        }
1193b4be Poul Sander 2025-02-03 21:28 7932
1193b4be Poul Sander 2025-02-03 21:28 7933
        // Shortcut: Select all (CTRL+A)
1193b4be Poul Sander 2025-02-03 21:28 7934
        if (!(flags & ImGuiMultiSelectFlags_SingleSelect) && !(flags & ImGuiMultiSelectFlags_NoSelectAll))
1193b4be Poul Sander 2025-02-03 21:28 7935
            if (Shortcut(ImGuiMod_Ctrl | ImGuiKey_A))
1193b4be Poul Sander 2025-02-03 21:28 7936
                request_select_all = true;
1193b4be Poul Sander 2025-02-03 21:28 7937
    }
1193b4be Poul Sander 2025-02-03 21:28 7938
1193b4be Poul Sander 2025-02-03 21:28 7939
    if (request_clear || request_select_all)
1193b4be Poul Sander 2025-02-03 21:28 7940
    {
1193b4be Poul Sander 2025-02-03 21:28 7941
        MultiSelectAddSetAll(ms, request_select_all);
1193b4be Poul Sander 2025-02-03 21:28 7942
        if (!request_select_all)
1193b4be Poul Sander 2025-02-03 21:28 7943
            storage->LastSelectionSize = 0;
1193b4be Poul Sander 2025-02-03 21:28 7944
    }
1193b4be Poul Sander 2025-02-03 21:28 7945
    ms->LoopRequestSetAll = request_select_all ? 1 : request_clear ? 0 : -1;
1193b4be Poul Sander 2025-02-03 21:28 7946
    ms->LastSubmittedItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7947
1193b4be Poul Sander 2025-02-03 21:28 7948
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
1193b4be Poul Sander 2025-02-03 21:28 7949
        DebugLogMultiSelectRequests("BeginMultiSelect", &ms->IO);
1193b4be Poul Sander 2025-02-03 21:28 7950
1193b4be Poul Sander 2025-02-03 21:28 7951
    return &ms->IO;
1193b4be Poul Sander 2025-02-03 21:28 7952
}
1193b4be Poul Sander 2025-02-03 21:28 7953
1193b4be Poul Sander 2025-02-03 21:28 7954
// Return updated ImGuiMultiSelectIO structure.
1193b4be Poul Sander 2025-02-03 21:28 7955
// Lifetime: don't hold on ImGuiMultiSelectIO* pointers over multiple frames or past any subsequent call to BeginMultiSelect() or EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 7956
ImGuiMultiSelectIO* ImGui::EndMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 7957
{
1193b4be Poul Sander 2025-02-03 21:28 7958
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 7959
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 7960
    ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 7961
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 7962
    IM_ASSERT_USER_ERROR(ms->FocusScopeId == g.CurrentFocusScopeId, "EndMultiSelect() FocusScope mismatch!");
1193b4be Poul Sander 2025-02-03 21:28 7963
    IM_ASSERT(g.CurrentMultiSelect != NULL && storage->Window == g.CurrentWindow);
1193b4be Poul Sander 2025-02-03 21:28 7964
    IM_ASSERT(g.MultiSelectTempDataStacked > 0 && &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] == g.CurrentMultiSelect);
1193b4be Poul Sander 2025-02-03 21:28 7965
1193b4be Poul Sander 2025-02-03 21:28 7966
    ImRect scope_rect = CalcScopeRect(ms, window);
1193b4be Poul Sander 2025-02-03 21:28 7967
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 7968
    {
1193b4be Poul Sander 2025-02-03 21:28 7969
        // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
4e9e2555 Poul Sander 2025-10-05 10:15 7970
        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)
1193b4be Poul Sander 2025-02-03 21:28 7971
        {
1193b4be Poul Sander 2025-02-03 21:28 7972
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
1193b4be Poul Sander 2025-02-03 21:28 7973
            storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7974
        }
1193b4be Poul Sander 2025-02-03 21:28 7975
        if (ms->NavIdPassedBy == false && storage->NavIdItem != ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 7976
        {
1193b4be Poul Sander 2025-02-03 21:28 7977
            IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset NavIdItem.\n");
1193b4be Poul Sander 2025-02-03 21:28 7978
            storage->NavIdItem = ImGuiSelectionUserData_Invalid;
1193b4be Poul Sander 2025-02-03 21:28 7979
            storage->NavIdSelected = -1;
1193b4be Poul Sander 2025-02-03 21:28 7980
        }
1193b4be Poul Sander 2025-02-03 21:28 7981
1193b4be Poul Sander 2025-02-03 21:28 7982
        if ((ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d)) && GetBoxSelectState(ms->BoxSelectId))
1193b4be Poul Sander 2025-02-03 21:28 7983
            EndBoxSelect(scope_rect, ms->Flags);
1193b4be Poul Sander 2025-02-03 21:28 7984
    }
1193b4be Poul Sander 2025-02-03 21:28 7985
1193b4be Poul Sander 2025-02-03 21:28 7986
    if (ms->IsEndIO == false)
1193b4be Poul Sander 2025-02-03 21:28 7987
        ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 7988
1193b4be Poul Sander 2025-02-03 21:28 7989
    // Clear selection when clicking void?
1193b4be Poul Sander 2025-02-03 21:28 7990
    // We specifically test for IsMouseDragPastThreshold(0) == false to allow box-selection!
1193b4be Poul Sander 2025-02-03 21:28 7991
    // The InnerRect test is necessary for non-child/decorated windows.
1193b4be Poul Sander 2025-02-03 21:28 7992
    bool scope_hovered = IsWindowHovered() && window->InnerRect.Contains(g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7993
    if (scope_hovered && (ms->Flags & ImGuiMultiSelectFlags_ScopeRect))
1193b4be Poul Sander 2025-02-03 21:28 7994
        scope_hovered &= scope_rect.Contains(g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 7995
    if (scope_hovered && g.HoveredId == 0 && g.ActiveId == 0)
1193b4be Poul Sander 2025-02-03 21:28 7996
    {
1193b4be Poul Sander 2025-02-03 21:28 7997
        if (ms->Flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 7998
        {
1193b4be Poul Sander 2025-02-03 21:28 7999
            if (!g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && g.IO.MouseClickedCount[0] == 1)
1193b4be Poul Sander 2025-02-03 21:28 8000
            {
1193b4be Poul Sander 2025-02-03 21:28 8001
                BoxSelectPreStartDrag(ms->BoxSelectId, ImGuiSelectionUserData_Invalid);
1193b4be Poul Sander 2025-02-03 21:28 8002
                FocusWindow(window, ImGuiFocusRequestFlags_UnlessBelowModal);
1193b4be Poul Sander 2025-02-03 21:28 8003
                SetHoveredID(ms->BoxSelectId);
1193b4be Poul Sander 2025-02-03 21:28 8004
                if (ms->Flags & ImGuiMultiSelectFlags_ScopeRect)
1193b4be Poul Sander 2025-02-03 21:28 8005
                    SetNavID(0, ImGuiNavLayer_Main, ms->FocusScopeId, ImRect(g.IO.MousePos, g.IO.MousePos)); // Automatically switch FocusScope for initial click from void to box-select.
1193b4be Poul Sander 2025-02-03 21:28 8006
            }
1193b4be Poul Sander 2025-02-03 21:28 8007
        }
1193b4be Poul Sander 2025-02-03 21:28 8008
1193b4be Poul Sander 2025-02-03 21:28 8009
        if (ms->Flags & ImGuiMultiSelectFlags_ClearOnClickVoid)
1193b4be Poul Sander 2025-02-03 21:28 8010
            if (IsMouseReleased(0) && IsMouseDragPastThreshold(0) == false && g.IO.KeyMods == ImGuiMod_None)
1193b4be Poul Sander 2025-02-03 21:28 8011
                MultiSelectAddSetAll(ms, false);
1193b4be Poul Sander 2025-02-03 21:28 8012
    }
1193b4be Poul Sander 2025-02-03 21:28 8013
1193b4be Poul Sander 2025-02-03 21:28 8014
    // Courtesy nav wrapping helper flag
1193b4be Poul Sander 2025-02-03 21:28 8015
    if (ms->Flags & ImGuiMultiSelectFlags_NavWrapX)
1193b4be Poul Sander 2025-02-03 21:28 8016
    {
1193b4be Poul Sander 2025-02-03 21:28 8017
        IM_ASSERT(ms->Flags & ImGuiMultiSelectFlags_ScopeWindow); // Only supported at window scope
1193b4be Poul Sander 2025-02-03 21:28 8018
        ImGui::NavMoveRequestTryWrapping(ImGui::GetCurrentWindow(), ImGuiNavMoveFlags_WrapX);
1193b4be Poul Sander 2025-02-03 21:28 8019
    }
1193b4be Poul Sander 2025-02-03 21:28 8020
1193b4be Poul Sander 2025-02-03 21:28 8021
    // Unwind
1193b4be Poul Sander 2025-02-03 21:28 8022
    window->DC.CursorMaxPos = ImMax(ms->BackupCursorMaxPos, window->DC.CursorMaxPos);
1193b4be Poul Sander 2025-02-03 21:28 8023
    PopFocusScope();
1193b4be Poul Sander 2025-02-03 21:28 8024
1193b4be Poul Sander 2025-02-03 21:28 8025
    if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection)
1193b4be Poul Sander 2025-02-03 21:28 8026
        DebugLogMultiSelectRequests("EndMultiSelect", &ms->IO);
1193b4be Poul Sander 2025-02-03 21:28 8027
1193b4be Poul Sander 2025-02-03 21:28 8028
    ms->FocusScopeId = 0;
1193b4be Poul Sander 2025-02-03 21:28 8029
    ms->Flags = ImGuiMultiSelectFlags_None;
1193b4be Poul Sander 2025-02-03 21:28 8030
    g.CurrentMultiSelect = (--g.MultiSelectTempDataStacked > 0) ? &g.MultiSelectTempData[g.MultiSelectTempDataStacked - 1] : NULL;
1193b4be Poul Sander 2025-02-03 21:28 8031
1193b4be Poul Sander 2025-02-03 21:28 8032
    return &ms->IO;
1193b4be Poul Sander 2025-02-03 21:28 8033
}
1193b4be Poul Sander 2025-02-03 21:28 8034
1193b4be Poul Sander 2025-02-03 21:28 8035
void ImGui::SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data)
1193b4be Poul Sander 2025-02-03 21:28 8036
{
1193b4be Poul Sander 2025-02-03 21:28 8037
    // Note that flags will be cleared by ItemAdd(), so it's only useful for Navigation code!
1193b4be Poul Sander 2025-02-03 21:28 8038
    // This designed so widgets can also cheaply set this before calling ItemAdd(), so we are not tied to MultiSelect api.
1193b4be Poul Sander 2025-02-03 21:28 8039
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8040
    g.NextItemData.SelectionUserData = selection_user_data;
1193b4be Poul Sander 2025-02-03 21:28 8041
    g.NextItemData.FocusScopeId = g.CurrentFocusScopeId;
1193b4be Poul Sander 2025-02-03 21:28 8042
1193b4be Poul Sander 2025-02-03 21:28 8043
    if (ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect)
1193b4be Poul Sander 2025-02-03 21:28 8044
    {
1193b4be Poul Sander 2025-02-03 21:28 8045
        // Auto updating RangeSrcPassedBy for cases were clipper is not used (done before ItemAdd() clipping)
1193b4be Poul Sander 2025-02-03 21:28 8046
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData | ImGuiItemFlags_IsMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 8047
        if (ms->IO.RangeSrcItem == selection_user_data)
1193b4be Poul Sander 2025-02-03 21:28 8048
            ms->RangeSrcPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 8049
    }
1193b4be Poul Sander 2025-02-03 21:28 8050
    else
1193b4be Poul Sander 2025-02-03 21:28 8051
    {
1193b4be Poul Sander 2025-02-03 21:28 8052
        g.NextItemData.ItemFlags |= ImGuiItemFlags_HasSelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 8053
    }
1193b4be Poul Sander 2025-02-03 21:28 8054
}
1193b4be Poul Sander 2025-02-03 21:28 8055
1193b4be Poul Sander 2025-02-03 21:28 8056
// In charge of:
1193b4be Poul Sander 2025-02-03 21:28 8057
// - Applying SetAll for submitted items.
1193b4be Poul Sander 2025-02-03 21:28 8058
// - Applying SetRange for submitted items and record end points.
1193b4be Poul Sander 2025-02-03 21:28 8059
// - Altering button behavior flags to facilitate use with drag and drop.
1193b4be Poul Sander 2025-02-03 21:28 8060
void ImGui::MultiSelectItemHeader(ImGuiID id, bool* p_selected, ImGuiButtonFlags* p_button_flags)
1193b4be Poul Sander 2025-02-03 21:28 8061
{
1193b4be Poul Sander 2025-02-03 21:28 8062
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8063
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 8064
1193b4be Poul Sander 2025-02-03 21:28 8065
    bool selected = *p_selected;
1193b4be Poul Sander 2025-02-03 21:28 8066
    if (ms->IsFocused)
1193b4be Poul Sander 2025-02-03 21:28 8067
    {
1193b4be Poul Sander 2025-02-03 21:28 8068
        ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 8069
        ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 8070
        IM_ASSERT(g.NextItemData.FocusScopeId == g.CurrentFocusScopeId && "Forgot to call SetNextItemSelectionUserData() prior to item, required in BeginMultiSelect()/EndMultiSelect() scope");
1193b4be Poul Sander 2025-02-03 21:28 8071
1193b4be Poul Sander 2025-02-03 21:28 8072
        // Apply SetAll (Clear/SelectAll) requests requested by BeginMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8073
        // This is only useful if the user hasn't processed them already, and this only works if the user isn't using the clipper.
1193b4be Poul Sander 2025-02-03 21:28 8074
        // If you are using a clipper you need to process the SetAll request after calling BeginMultiSelect()
1193b4be Poul Sander 2025-02-03 21:28 8075
        if (ms->LoopRequestSetAll != -1)
1193b4be Poul Sander 2025-02-03 21:28 8076
            selected = (ms->LoopRequestSetAll == 1);
1193b4be Poul Sander 2025-02-03 21:28 8077
1193b4be Poul Sander 2025-02-03 21:28 8078
        // 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)
1193b4be Poul Sander 2025-02-03 21:28 8079
        // For this to work, we need someone to set 'RangeSrcPassedBy = true' at some point (either clipper either SetNextItemSelectionUserData() function)
1193b4be Poul Sander 2025-02-03 21:28 8080
        if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 8081
        {
1193b4be Poul Sander 2025-02-03 21:28 8082
            IM_ASSERT(id != 0 && (ms->KeyMods & ImGuiMod_Shift) != 0);
1193b4be Poul Sander 2025-02-03 21:28 8083
            const bool is_range_dst = (ms->RangeDstPassedBy == false) && g.NavJustMovedToId == id;     // Assume that g.NavJustMovedToId is not clipped.
1193b4be Poul Sander 2025-02-03 21:28 8084
            if (is_range_dst)
1193b4be Poul Sander 2025-02-03 21:28 8085
                ms->RangeDstPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 8086
            if (is_range_dst && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid) // If we don't have RangeSrc, assign RangeSrc = RangeDst
1193b4be Poul Sander 2025-02-03 21:28 8087
            {
1193b4be Poul Sander 2025-02-03 21:28 8088
                storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8089
                storage->RangeSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 8090
            }
1193b4be Poul Sander 2025-02-03 21:28 8091
            const bool is_range_src = storage->RangeSrcItem == item_data;
1193b4be Poul Sander 2025-02-03 21:28 8092
            if (is_range_src || is_range_dst || ms->RangeSrcPassedBy != ms->RangeDstPassedBy)
1193b4be Poul Sander 2025-02-03 21:28 8093
            {
1193b4be Poul Sander 2025-02-03 21:28 8094
                // Apply range-select value to visible items
1193b4be Poul Sander 2025-02-03 21:28 8095
                IM_ASSERT(storage->RangeSrcItem != ImGuiSelectionUserData_Invalid && storage->RangeSelected != -1);
1193b4be Poul Sander 2025-02-03 21:28 8096
                selected = (storage->RangeSelected != 0);
1193b4be Poul Sander 2025-02-03 21:28 8097
            }
1193b4be Poul Sander 2025-02-03 21:28 8098
            else if ((ms->KeyMods & ImGuiMod_Ctrl) == 0 && (ms->Flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8099
            {
1193b4be Poul Sander 2025-02-03 21:28 8100
                // Clear other items
1193b4be Poul Sander 2025-02-03 21:28 8101
                selected = false;
1193b4be Poul Sander 2025-02-03 21:28 8102
            }
1193b4be Poul Sander 2025-02-03 21:28 8103
        }
1193b4be Poul Sander 2025-02-03 21:28 8104
        *p_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 8105
    }
1193b4be Poul Sander 2025-02-03 21:28 8106
1193b4be Poul Sander 2025-02-03 21:28 8107
    // Alter button behavior flags
1193b4be Poul Sander 2025-02-03 21:28 8108
    // To handle drag and drop of multiple items we need to avoid clearing selection on click.
1193b4be Poul Sander 2025-02-03 21:28 8109
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 8110
    if (p_button_flags != NULL)
1193b4be Poul Sander 2025-02-03 21:28 8111
    {
1193b4be Poul Sander 2025-02-03 21:28 8112
        ImGuiButtonFlags button_flags = *p_button_flags;
1193b4be Poul Sander 2025-02-03 21:28 8113
        button_flags |= ImGuiButtonFlags_NoHoveredOnFocus;
1193b4be Poul Sander 2025-02-03 21:28 8114
        if ((!selected || (g.ActiveId == id && g.ActiveIdHasBeenPressedBefore)) && !(ms->Flags & ImGuiMultiSelectFlags_SelectOnClickRelease))
1193b4be Poul Sander 2025-02-03 21:28 8115
            button_flags = (button_flags | ImGuiButtonFlags_PressedOnClick) & ~ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 8116
        else
1193b4be Poul Sander 2025-02-03 21:28 8117
            button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
1193b4be Poul Sander 2025-02-03 21:28 8118
        *p_button_flags = button_flags;
1193b4be Poul Sander 2025-02-03 21:28 8119
    }
1193b4be Poul Sander 2025-02-03 21:28 8120
}
1193b4be Poul Sander 2025-02-03 21:28 8121
1193b4be Poul Sander 2025-02-03 21:28 8122
// In charge of:
1193b4be Poul Sander 2025-02-03 21:28 8123
// - Auto-select on navigation.
1193b4be Poul Sander 2025-02-03 21:28 8124
// - Box-select toggle handling.
1193b4be Poul Sander 2025-02-03 21:28 8125
// - Right-click handling.
1193b4be Poul Sander 2025-02-03 21:28 8126
// - Altering selection based on Ctrl/Shift modifiers, both for keyboard and mouse.
1193b4be Poul Sander 2025-02-03 21:28 8127
// - Record current selection state for RangeSrc
1193b4be Poul Sander 2025-02-03 21:28 8128
// This is all rather complex, best to run and refer to "widgets_multiselect_xxx" tests in imgui_test_suite.
1193b4be Poul Sander 2025-02-03 21:28 8129
void ImGui::MultiSelectItemFooter(ImGuiID id, bool* p_selected, bool* p_pressed)
1193b4be Poul Sander 2025-02-03 21:28 8130
{
1193b4be Poul Sander 2025-02-03 21:28 8131
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8132
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8133
1193b4be Poul Sander 2025-02-03 21:28 8134
    bool selected = *p_selected;
1193b4be Poul Sander 2025-02-03 21:28 8135
    bool pressed = *p_pressed;
1193b4be Poul Sander 2025-02-03 21:28 8136
    ImGuiMultiSelectTempData* ms = g.CurrentMultiSelect;
1193b4be Poul Sander 2025-02-03 21:28 8137
    ImGuiMultiSelectState* storage = ms->Storage;
1193b4be Poul Sander 2025-02-03 21:28 8138
    if (pressed)
1193b4be Poul Sander 2025-02-03 21:28 8139
        ms->IsFocused = true;
1193b4be Poul Sander 2025-02-03 21:28 8140
1193b4be Poul Sander 2025-02-03 21:28 8141
    bool hovered = false;
1193b4be Poul Sander 2025-02-03 21:28 8142
    if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect)
1193b4be Poul Sander 2025-02-03 21:28 8143
        hovered = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
1193b4be Poul Sander 2025-02-03 21:28 8144
    if (!ms->IsFocused && !hovered)
1193b4be Poul Sander 2025-02-03 21:28 8145
        return;
1193b4be Poul Sander 2025-02-03 21:28 8146
1193b4be Poul Sander 2025-02-03 21:28 8147
    ImGuiSelectionUserData item_data = g.NextItemData.SelectionUserData;
1193b4be Poul Sander 2025-02-03 21:28 8148
1193b4be Poul Sander 2025-02-03 21:28 8149
    ImGuiMultiSelectFlags flags = ms->Flags;
1193b4be Poul Sander 2025-02-03 21:28 8150
    const bool is_singleselect = (flags & ImGuiMultiSelectFlags_SingleSelect) != 0;
1193b4be Poul Sander 2025-02-03 21:28 8151
    bool is_ctrl = (ms->KeyMods & ImGuiMod_Ctrl) != 0;
1193b4be Poul Sander 2025-02-03 21:28 8152
    bool is_shift = (ms->KeyMods & ImGuiMod_Shift) != 0;
1193b4be Poul Sander 2025-02-03 21:28 8153
1193b4be Poul Sander 2025-02-03 21:28 8154
    bool apply_to_range_src = false;
1193b4be Poul Sander 2025-02-03 21:28 8155
1193b4be Poul Sander 2025-02-03 21:28 8156
    if (g.NavId == id && storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 8157
        apply_to_range_src = true;
1193b4be Poul Sander 2025-02-03 21:28 8158
    if (ms->IsEndIO == false)
1193b4be Poul Sander 2025-02-03 21:28 8159
    {
1193b4be Poul Sander 2025-02-03 21:28 8160
        ms->IO.Requests.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 8161
        ms->IsEndIO = true;
1193b4be Poul Sander 2025-02-03 21:28 8162
    }
1193b4be Poul Sander 2025-02-03 21:28 8163
1193b4be Poul Sander 2025-02-03 21:28 8164
    // Auto-select as you navigate a list
1193b4be Poul Sander 2025-02-03 21:28 8165
    if (g.NavJustMovedToId == id)
1193b4be Poul Sander 2025-02-03 21:28 8166
    {
1193b4be Poul Sander 2025-02-03 21:28 8167
        if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8168
        {
1193b4be Poul Sander 2025-02-03 21:28 8169
            if (is_ctrl && is_shift)
1193b4be Poul Sander 2025-02-03 21:28 8170
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 8171
            else if (!is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 8172
                selected = pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 8173
        }
1193b4be Poul Sander 2025-02-03 21:28 8174
        else
1193b4be Poul Sander 2025-02-03 21:28 8175
        {
1193b4be Poul Sander 2025-02-03 21:28 8176
            // With NoAutoSelect, using Shift+keyboard performs a write/copy
1193b4be Poul Sander 2025-02-03 21:28 8177
            if (is_shift)
1193b4be Poul Sander 2025-02-03 21:28 8178
                pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 8179
            else if (!is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 8180
                apply_to_range_src = true; // Since if (pressed) {} main block is not running we update this
1193b4be Poul Sander 2025-02-03 21:28 8181
        }
1193b4be Poul Sander 2025-02-03 21:28 8182
    }
1193b4be Poul Sander 2025-02-03 21:28 8183
1193b4be Poul Sander 2025-02-03 21:28 8184
    if (apply_to_range_src)
1193b4be Poul Sander 2025-02-03 21:28 8185
    {
1193b4be Poul Sander 2025-02-03 21:28 8186
        storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8187
        storage->RangeSelected = selected; // Will be updated at the end of this function anyway.
1193b4be Poul Sander 2025-02-03 21:28 8188
    }
1193b4be Poul Sander 2025-02-03 21:28 8189
1193b4be Poul Sander 2025-02-03 21:28 8190
    // Box-select toggle handling
1193b4be Poul Sander 2025-02-03 21:28 8191
    if (ms->BoxSelectId != 0)
1193b4be Poul Sander 2025-02-03 21:28 8192
        if (ImGuiBoxSelectState* bs = GetBoxSelectState(ms->BoxSelectId))
1193b4be Poul Sander 2025-02-03 21:28 8193
        {
1193b4be Poul Sander 2025-02-03 21:28 8194
            const bool rect_overlap_curr = bs->BoxSelectRectCurr.Overlaps(g.LastItemData.Rect);
1193b4be Poul Sander 2025-02-03 21:28 8195
            const bool rect_overlap_prev = bs->BoxSelectRectPrev.Overlaps(g.LastItemData.Rect);
1193b4be Poul Sander 2025-02-03 21:28 8196
            if ((rect_overlap_curr && !rect_overlap_prev && !selected) || (rect_overlap_prev && !rect_overlap_curr))
1193b4be Poul Sander 2025-02-03 21:28 8197
            {
1193b4be Poul Sander 2025-02-03 21:28 8198
                if (storage->LastSelectionSize <= 0 && bs->IsStartedSetNavIdOnce)
1193b4be Poul Sander 2025-02-03 21:28 8199
                {
1193b4be Poul Sander 2025-02-03 21:28 8200
                    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)
1193b4be Poul Sander 2025-02-03 21:28 8201
                    bs->IsStartedSetNavIdOnce = false;
1193b4be Poul Sander 2025-02-03 21:28 8202
                }
1193b4be Poul Sander 2025-02-03 21:28 8203
                else
1193b4be Poul Sander 2025-02-03 21:28 8204
                {
1193b4be Poul Sander 2025-02-03 21:28 8205
                    selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 8206
                    MultiSelectAddSetRange(ms, selected, +1, item_data, item_data);
1193b4be Poul Sander 2025-02-03 21:28 8207
                }
1193b4be Poul Sander 2025-02-03 21:28 8208
                storage->LastSelectionSize = ImMax(storage->LastSelectionSize + 1, 1);
1193b4be Poul Sander 2025-02-03 21:28 8209
            }
1193b4be Poul Sander 2025-02-03 21:28 8210
        }
1193b4be Poul Sander 2025-02-03 21:28 8211
1193b4be Poul Sander 2025-02-03 21:28 8212
    // Right-click handling.
1193b4be Poul Sander 2025-02-03 21:28 8213
    // FIXME-MULTISELECT: Currently filtered out by ImGuiMultiSelectFlags_NoAutoSelect but maybe should be moved to Selectable(). See https://github.com/ocornut/imgui/pull/5816
1193b4be Poul Sander 2025-02-03 21:28 8214
    if (hovered && IsMouseClicked(1) && (flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8215
    {
1193b4be Poul Sander 2025-02-03 21:28 8216
        if (g.ActiveId != 0 && g.ActiveId != id)
1193b4be Poul Sander 2025-02-03 21:28 8217
            ClearActiveID();
1193b4be Poul Sander 2025-02-03 21:28 8218
        SetFocusID(id, window);
1193b4be Poul Sander 2025-02-03 21:28 8219
        if (!pressed && !selected)
1193b4be Poul Sander 2025-02-03 21:28 8220
        {
1193b4be Poul Sander 2025-02-03 21:28 8221
            pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 8222
            is_ctrl = is_shift = false;
1193b4be Poul Sander 2025-02-03 21:28 8223
        }
1193b4be Poul Sander 2025-02-03 21:28 8224
    }
1193b4be Poul Sander 2025-02-03 21:28 8225
1193b4be Poul Sander 2025-02-03 21:28 8226
    // Unlike Space, Enter doesn't alter selection (but can still return a press) unless current item is not selected.
1193b4be Poul Sander 2025-02-03 21:28 8227
    // The later, "unless current item is not select", may become optional? It seems like a better default if Enter doesn't necessarily open something
1193b4be Poul Sander 2025-02-03 21:28 8228
    // (unlike e.g. Windows explorer). For use case where Enter always open something, we might decide to make this optional?
1193b4be Poul Sander 2025-02-03 21:28 8229
    const bool enter_pressed = pressed && (g.NavActivateId == id) && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput);
1193b4be Poul Sander 2025-02-03 21:28 8230
1193b4be Poul Sander 2025-02-03 21:28 8231
    // Alter selection
1193b4be Poul Sander 2025-02-03 21:28 8232
    if (pressed && (!enter_pressed || !selected))
1193b4be Poul Sander 2025-02-03 21:28 8233
    {
1193b4be Poul Sander 2025-02-03 21:28 8234
        // Box-select
1193b4be Poul Sander 2025-02-03 21:28 8235
        ImGuiInputSource input_source = (g.NavJustMovedToId == id || g.NavActivateId == id) ? g.NavInputSource : ImGuiInputSource_Mouse;
1193b4be Poul Sander 2025-02-03 21:28 8236
        if (flags & (ImGuiMultiSelectFlags_BoxSelect1d | ImGuiMultiSelectFlags_BoxSelect2d))
1193b4be Poul Sander 2025-02-03 21:28 8237
            if (selected == false && !g.BoxSelectState.IsActive && !g.BoxSelectState.IsStarting && input_source == ImGuiInputSource_Mouse && g.IO.MouseClickedCount[0] == 1)
1193b4be Poul Sander 2025-02-03 21:28 8238
                BoxSelectPreStartDrag(ms->BoxSelectId, item_data);
1193b4be Poul Sander 2025-02-03 21:28 8239
1193b4be Poul Sander 2025-02-03 21:28 8240
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8241
        // ACTION                      | Begin  | Pressed/Activated  | End
1193b4be Poul Sander 2025-02-03 21:28 8242
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8243
        // Keys Navigated:             | Clear  | Src=item, Sel=1               SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8244
        // Keys Navigated: Ctrl        | n/a    | n/a
1193b4be Poul Sander 2025-02-03 21:28 8245
        // Keys Navigated:      Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8246
        // Keys Navigated: Ctrl+Shift  | n/a    | Dst=item, Sel=Src  => Clear + SetRange Src-Dst
1193b4be Poul Sander 2025-02-03 21:28 8247
        // Keys Activated:             | n/a    | Src=item, Sel=1    => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8248
        // Keys Activated: Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetSange 1
1193b4be Poul Sander 2025-02-03 21:28 8249
        // Keys Activated:      Shift  | n/a    | Dst=item, Sel=1    => Clear + SetSange 1
1193b4be Poul Sander 2025-02-03 21:28 8250
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8251
        // Mouse Pressed:              | n/a    | Src=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8252
        // Mouse Pressed:  Ctrl        | n/a    | Src=item, Sel=!Sel =>         SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8253
        // Mouse Pressed:       Shift  | n/a    | Dst=item, Sel=1,   => Clear + SetRange 1
1193b4be Poul Sander 2025-02-03 21:28 8254
        // Mouse Pressed:  Ctrl+Shift  | n/a    | Dst=item, Sel=!Sel =>         SetRange Src-Dst
1193b4be Poul Sander 2025-02-03 21:28 8255
        //----------------------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8256
1193b4be Poul Sander 2025-02-03 21:28 8257
        if ((flags & ImGuiMultiSelectFlags_NoAutoClear) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8258
        {
1193b4be Poul Sander 2025-02-03 21:28 8259
            bool request_clear = false;
1193b4be Poul Sander 2025-02-03 21:28 8260
            if (is_singleselect)
1193b4be Poul Sander 2025-02-03 21:28 8261
                request_clear = true;
1193b4be Poul Sander 2025-02-03 21:28 8262
            else if ((input_source == ImGuiInputSource_Mouse || g.NavActivateId == id) && !is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 8263
                request_clear = (flags & ImGuiMultiSelectFlags_NoAutoClearOnReselect) ? !selected : true;
1193b4be Poul Sander 2025-02-03 21:28 8264
            else if ((input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Gamepad) && is_shift && !is_ctrl)
1193b4be Poul Sander 2025-02-03 21:28 8265
                request_clear = true; // With is_shift==false the RequestClear was done in BeginIO, not necessary to do again.
1193b4be Poul Sander 2025-02-03 21:28 8266
            if (request_clear)
1193b4be Poul Sander 2025-02-03 21:28 8267
                MultiSelectAddSetAll(ms, false);
1193b4be Poul Sander 2025-02-03 21:28 8268
        }
1193b4be Poul Sander 2025-02-03 21:28 8269
1193b4be Poul Sander 2025-02-03 21:28 8270
        int range_direction;
1193b4be Poul Sander 2025-02-03 21:28 8271
        bool range_selected;
1193b4be Poul Sander 2025-02-03 21:28 8272
        if (is_shift && !is_singleselect)
1193b4be Poul Sander 2025-02-03 21:28 8273
        {
1193b4be Poul Sander 2025-02-03 21:28 8274
            //IM_ASSERT(storage->HasRangeSrc && storage->HasRangeValue);
1193b4be Poul Sander 2025-02-03 21:28 8275
            if (storage->RangeSrcItem == ImGuiSelectionUserData_Invalid)
1193b4be Poul Sander 2025-02-03 21:28 8276
                storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8277
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8278
            {
1193b4be Poul Sander 2025-02-03 21:28 8279
                // Shift+Arrow always select
1193b4be Poul Sander 2025-02-03 21:28 8280
                // Ctrl+Shift+Arrow copy source selection state (already stored by BeginMultiSelect() in storage->RangeSelected)
1193b4be Poul Sander 2025-02-03 21:28 8281
                range_selected = (is_ctrl && storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 8282
            }
1193b4be Poul Sander 2025-02-03 21:28 8283
            else
1193b4be Poul Sander 2025-02-03 21:28 8284
            {
1193b4be Poul Sander 2025-02-03 21:28 8285
                // Shift+Arrow copy source selection state
1193b4be Poul Sander 2025-02-03 21:28 8286
                // Shift+Click always copy from target selection state
1193b4be Poul Sander 2025-02-03 21:28 8287
                if (ms->IsKeyboardSetRange)
1193b4be Poul Sander 2025-02-03 21:28 8288
                    range_selected = (storage->RangeSelected != -1) ? (storage->RangeSelected != 0) : true;
1193b4be Poul Sander 2025-02-03 21:28 8289
                else
1193b4be Poul Sander 2025-02-03 21:28 8290
                    range_selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 8291
            }
1193b4be Poul Sander 2025-02-03 21:28 8292
            range_direction = ms->RangeSrcPassedBy ? +1 : -1;
1193b4be Poul Sander 2025-02-03 21:28 8293
        }
1193b4be Poul Sander 2025-02-03 21:28 8294
        else
1193b4be Poul Sander 2025-02-03 21:28 8295
        {
1193b4be Poul Sander 2025-02-03 21:28 8296
            // Ctrl inverts selection, otherwise always select
1193b4be Poul Sander 2025-02-03 21:28 8297
            if ((flags & ImGuiMultiSelectFlags_NoAutoSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8298
                selected = is_ctrl ? !selected : true;
1193b4be Poul Sander 2025-02-03 21:28 8299
            else
1193b4be Poul Sander 2025-02-03 21:28 8300
                selected = !selected;
1193b4be Poul Sander 2025-02-03 21:28 8301
            storage->RangeSrcItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8302
            range_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 8303
            range_direction = +1;
1193b4be Poul Sander 2025-02-03 21:28 8304
        }
1193b4be Poul Sander 2025-02-03 21:28 8305
        MultiSelectAddSetRange(ms, range_selected, range_direction, storage->RangeSrcItem, item_data);
1193b4be Poul Sander 2025-02-03 21:28 8306
    }
1193b4be Poul Sander 2025-02-03 21:28 8307
1193b4be Poul Sander 2025-02-03 21:28 8308
    // Update/store the selection state of the Source item (used by CTRL+SHIFT, when Source is unselected we perform a range unselect)
1193b4be Poul Sander 2025-02-03 21:28 8309
    if (storage->RangeSrcItem == item_data)
1193b4be Poul Sander 2025-02-03 21:28 8310
        storage->RangeSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 8311
1193b4be Poul Sander 2025-02-03 21:28 8312
    // Update/store the selection state of focused item
1193b4be Poul Sander 2025-02-03 21:28 8313
    if (g.NavId == id)
1193b4be Poul Sander 2025-02-03 21:28 8314
    {
1193b4be Poul Sander 2025-02-03 21:28 8315
        storage->NavIdItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8316
        storage->NavIdSelected = selected ? 1 : 0;
1193b4be Poul Sander 2025-02-03 21:28 8317
    }
1193b4be Poul Sander 2025-02-03 21:28 8318
    if (storage->NavIdItem == item_data)
1193b4be Poul Sander 2025-02-03 21:28 8319
        ms->NavIdPassedBy = true;
1193b4be Poul Sander 2025-02-03 21:28 8320
    ms->LastSubmittedItem = item_data;
1193b4be Poul Sander 2025-02-03 21:28 8321
1193b4be Poul Sander 2025-02-03 21:28 8322
    *p_selected = selected;
1193b4be Poul Sander 2025-02-03 21:28 8323
    *p_pressed = pressed;
1193b4be Poul Sander 2025-02-03 21:28 8324
}
1193b4be Poul Sander 2025-02-03 21:28 8325
1193b4be Poul Sander 2025-02-03 21:28 8326
void ImGui::MultiSelectAddSetAll(ImGuiMultiSelectTempData* ms, bool selected)
1193b4be Poul Sander 2025-02-03 21:28 8327
{
1193b4be Poul Sander 2025-02-03 21:28 8328
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetAll, selected, 0, ImGuiSelectionUserData_Invalid, ImGuiSelectionUserData_Invalid };
1193b4be Poul Sander 2025-02-03 21:28 8329
    ms->IO.Requests.resize(0);      // Can always clear previous requests
1193b4be Poul Sander 2025-02-03 21:28 8330
    ms->IO.Requests.push_back(req); // Add new request
1193b4be Poul Sander 2025-02-03 21:28 8331
}
1193b4be Poul Sander 2025-02-03 21:28 8332
1193b4be Poul Sander 2025-02-03 21:28 8333
void ImGui::MultiSelectAddSetRange(ImGuiMultiSelectTempData* ms, bool selected, int range_dir, ImGuiSelectionUserData first_item, ImGuiSelectionUserData last_item)
1193b4be Poul Sander 2025-02-03 21:28 8334
{
1193b4be Poul Sander 2025-02-03 21:28 8335
    // Merge contiguous spans into same request (unless NoRangeSelect is set which guarantees single-item ranges)
1193b4be Poul Sander 2025-02-03 21:28 8336
    if (ms->IO.Requests.Size > 0 && first_item == last_item && (ms->Flags & ImGuiMultiSelectFlags_NoRangeSelect) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8337
    {
1193b4be Poul Sander 2025-02-03 21:28 8338
        ImGuiSelectionRequest* prev = &ms->IO.Requests.Data[ms->IO.Requests.Size - 1];
1193b4be Poul Sander 2025-02-03 21:28 8339
        if (prev->Type == ImGuiSelectionRequestType_SetRange && prev->RangeLastItem == ms->LastSubmittedItem && prev->Selected == selected)
1193b4be Poul Sander 2025-02-03 21:28 8340
        {
1193b4be Poul Sander 2025-02-03 21:28 8341
            prev->RangeLastItem = last_item;
1193b4be Poul Sander 2025-02-03 21:28 8342
            return;
1193b4be Poul Sander 2025-02-03 21:28 8343
        }
1193b4be Poul Sander 2025-02-03 21:28 8344
    }
1193b4be Poul Sander 2025-02-03 21:28 8345
1193b4be Poul Sander 2025-02-03 21:28 8346
    ImGuiSelectionRequest req = { ImGuiSelectionRequestType_SetRange, selected, (ImS8)range_dir, (range_dir > 0) ? first_item : last_item, (range_dir > 0) ? last_item : first_item };
1193b4be Poul Sander 2025-02-03 21:28 8347
    ms->IO.Requests.push_back(req); // Add new request
1193b4be Poul Sander 2025-02-03 21:28 8348
}
1193b4be Poul Sander 2025-02-03 21:28 8349
1193b4be Poul Sander 2025-02-03 21:28 8350
void ImGui::DebugNodeMultiSelectState(ImGuiMultiSelectState* storage)
1193b4be Poul Sander 2025-02-03 21:28 8351
{
1193b4be Poul Sander 2025-02-03 21:28 8352
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
1193b4be Poul Sander 2025-02-03 21:28 8353
    const bool is_active = (storage->LastFrameActive >= GetFrameCount() - 2); // Note that fully clipped early out scrolling tables will appear as inactive here.
1193b4be Poul Sander 2025-02-03 21:28 8354
    if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
1193b4be Poul Sander 2025-02-03 21:28 8355
    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*");
1193b4be Poul Sander 2025-02-03 21:28 8356
    if (!is_active) { PopStyleColor(); }
1193b4be Poul Sander 2025-02-03 21:28 8357
    if (!open)
1193b4be Poul Sander 2025-02-03 21:28 8358
        return;
1193b4be Poul Sander 2025-02-03 21:28 8359
    Text("RangeSrcItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), RangeSelected = %d", storage->RangeSrcItem, storage->RangeSrcItem, storage->RangeSelected);
1193b4be Poul Sander 2025-02-03 21:28 8360
    Text("NavIdItem = %" IM_PRId64 " (0x%" IM_PRIX64 "), NavIdSelected = %d", storage->NavIdItem, storage->NavIdItem, storage->NavIdSelected);
1193b4be Poul Sander 2025-02-03 21:28 8361
    Text("LastSelectionSize = %d", storage->LastSelectionSize); // Provided by user
1193b4be Poul Sander 2025-02-03 21:28 8362
    TreePop();
1193b4be Poul Sander 2025-02-03 21:28 8363
#else
1193b4be Poul Sander 2025-02-03 21:28 8364
    IM_UNUSED(storage);
1193b4be Poul Sander 2025-02-03 21:28 8365
#endif
1193b4be Poul Sander 2025-02-03 21:28 8366
}
1193b4be Poul Sander 2025-02-03 21:28 8367
1193b4be Poul Sander 2025-02-03 21:28 8368
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8369
// [SECTION] Widgets: Multi-Select helpers
1193b4be Poul Sander 2025-02-03 21:28 8370
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8371
// - ImGuiSelectionBasicStorage
1193b4be Poul Sander 2025-02-03 21:28 8372
// - ImGuiSelectionExternalStorage
1193b4be Poul Sander 2025-02-03 21:28 8373
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8374
1193b4be Poul Sander 2025-02-03 21:28 8375
ImGuiSelectionBasicStorage::ImGuiSelectionBasicStorage()
1193b4be Poul Sander 2025-02-03 21:28 8376
{
1193b4be Poul Sander 2025-02-03 21:28 8377
    Size = 0;
1193b4be Poul Sander 2025-02-03 21:28 8378
    PreserveOrder = false;
1193b4be Poul Sander 2025-02-03 21:28 8379
    UserData = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8380
    AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage*, int idx) { return (ImGuiID)idx; };
1193b4be Poul Sander 2025-02-03 21:28 8381
    _SelectionOrder = 1; // Always >0
1193b4be Poul Sander 2025-02-03 21:28 8382
}
1193b4be Poul Sander 2025-02-03 21:28 8383
1193b4be Poul Sander 2025-02-03 21:28 8384
void ImGuiSelectionBasicStorage::Clear()
1193b4be Poul Sander 2025-02-03 21:28 8385
{
1193b4be Poul Sander 2025-02-03 21:28 8386
    Size = 0;
1193b4be Poul Sander 2025-02-03 21:28 8387
    _SelectionOrder = 1; // Always >0
1193b4be Poul Sander 2025-02-03 21:28 8388
    _Storage.Data.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 8389
}
1193b4be Poul Sander 2025-02-03 21:28 8390
1193b4be Poul Sander 2025-02-03 21:28 8391
void ImGuiSelectionBasicStorage::Swap(ImGuiSelectionBasicStorage& r)
1193b4be Poul Sander 2025-02-03 21:28 8392
{
1193b4be Poul Sander 2025-02-03 21:28 8393
    ImSwap(Size, r.Size);
1193b4be Poul Sander 2025-02-03 21:28 8394
    ImSwap(_SelectionOrder, r._SelectionOrder);
1193b4be Poul Sander 2025-02-03 21:28 8395
    _Storage.Data.swap(r._Storage.Data);
1193b4be Poul Sander 2025-02-03 21:28 8396
}
1193b4be Poul Sander 2025-02-03 21:28 8397
1193b4be Poul Sander 2025-02-03 21:28 8398
bool ImGuiSelectionBasicStorage::Contains(ImGuiID id) const
1193b4be Poul Sander 2025-02-03 21:28 8399
{
1193b4be Poul Sander 2025-02-03 21:28 8400
    return _Storage.GetInt(id, 0) != 0;
1193b4be Poul Sander 2025-02-03 21:28 8401
}
1193b4be Poul Sander 2025-02-03 21:28 8402
1193b4be Poul Sander 2025-02-03 21:28 8403
static int IMGUI_CDECL PairComparerByValueInt(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 8404
{
1193b4be Poul Sander 2025-02-03 21:28 8405
    int lhs_v = ((const ImGuiStoragePair*)lhs)->val_i;
1193b4be Poul Sander 2025-02-03 21:28 8406
    int rhs_v = ((const ImGuiStoragePair*)rhs)->val_i;
1193b4be Poul Sander 2025-02-03 21:28 8407
    return (lhs_v > rhs_v ? +1 : lhs_v < rhs_v ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8408
}
1193b4be Poul Sander 2025-02-03 21:28 8409
1193b4be Poul Sander 2025-02-03 21:28 8410
// GetNextSelectedItem() is an abstraction allowing us to change our underlying actual storage system without impacting user.
1193b4be Poul Sander 2025-02-03 21:28 8411
// (e.g. store unselected vs compact down, compact down on demand, use raw ImVector<ImGuiID> instead of ImGuiStorage...)
1193b4be Poul Sander 2025-02-03 21:28 8412
bool ImGuiSelectionBasicStorage::GetNextSelectedItem(void** opaque_it, ImGuiID* out_id)
1193b4be Poul Sander 2025-02-03 21:28 8413
{
1193b4be Poul Sander 2025-02-03 21:28 8414
    ImGuiStoragePair* it = (ImGuiStoragePair*)*opaque_it;
1193b4be Poul Sander 2025-02-03 21:28 8415
    ImGuiStoragePair* it_end = _Storage.Data.Data + _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8416
    if (PreserveOrder && it == NULL && it_end != NULL)
1193b4be Poul Sander 2025-02-03 21:28 8417
        ImQsort(_Storage.Data.Data, (size_t)_Storage.Data.Size, sizeof(ImGuiStoragePair), PairComparerByValueInt); // ~ImGuiStorage::BuildSortByValueInt()
1193b4be Poul Sander 2025-02-03 21:28 8418
    if (it == NULL)
1193b4be Poul Sander 2025-02-03 21:28 8419
        it = _Storage.Data.Data;
1193b4be Poul Sander 2025-02-03 21:28 8420
    IM_ASSERT(it >= _Storage.Data.Data && it <= it_end);
1193b4be Poul Sander 2025-02-03 21:28 8421
    if (it != it_end)
1193b4be Poul Sander 2025-02-03 21:28 8422
        while (it->val_i == 0 && it < it_end)
1193b4be Poul Sander 2025-02-03 21:28 8423
            it++;
1193b4be Poul Sander 2025-02-03 21:28 8424
    const bool has_more = (it != it_end);
1193b4be Poul Sander 2025-02-03 21:28 8425
    *opaque_it = has_more ? (void**)(it + 1) : (void**)(it);
1193b4be Poul Sander 2025-02-03 21:28 8426
    *out_id = has_more ? it->key : 0;
1193b4be Poul Sander 2025-02-03 21:28 8427
    if (PreserveOrder && !has_more)
1193b4be Poul Sander 2025-02-03 21:28 8428
        _Storage.BuildSortByKey();
1193b4be Poul Sander 2025-02-03 21:28 8429
    return has_more;
1193b4be Poul Sander 2025-02-03 21:28 8430
}
1193b4be Poul Sander 2025-02-03 21:28 8431
1193b4be Poul Sander 2025-02-03 21:28 8432
void ImGuiSelectionBasicStorage::SetItemSelected(ImGuiID id, bool selected)
1193b4be Poul Sander 2025-02-03 21:28 8433
{
1193b4be Poul Sander 2025-02-03 21:28 8434
    int* p_int = _Storage.GetIntRef(id, 0);
1193b4be Poul Sander 2025-02-03 21:28 8435
    if (selected && *p_int == 0) { *p_int = _SelectionOrder++; Size++; }
1193b4be Poul Sander 2025-02-03 21:28 8436
    else if (!selected && *p_int != 0) { *p_int = 0; Size--; }
1193b4be Poul Sander 2025-02-03 21:28 8437
}
1193b4be Poul Sander 2025-02-03 21:28 8438
1193b4be Poul Sander 2025-02-03 21:28 8439
// Optimized for batch edits (with same value of 'selected')
1193b4be Poul Sander 2025-02-03 21:28 8440
static void ImGuiSelectionBasicStorage_BatchSetItemSelected(ImGuiSelectionBasicStorage* selection, ImGuiID id, bool selected, int size_before_amends, int selection_order)
1193b4be Poul Sander 2025-02-03 21:28 8441
{
1193b4be Poul Sander 2025-02-03 21:28 8442
    ImGuiStorage* storage = &selection->_Storage;
1193b4be Poul Sander 2025-02-03 21:28 8443
    ImGuiStoragePair* it = ImLowerBound(storage->Data.Data, storage->Data.Data + size_before_amends, id);
1193b4be Poul Sander 2025-02-03 21:28 8444
    const bool is_contained = (it != storage->Data.Data + size_before_amends) && (it->key == id);
1193b4be Poul Sander 2025-02-03 21:28 8445
    if (selected == (is_contained && it->val_i != 0))
1193b4be Poul Sander 2025-02-03 21:28 8446
        return;
1193b4be Poul Sander 2025-02-03 21:28 8447
    if (selected && !is_contained)
1193b4be Poul Sander 2025-02-03 21:28 8448
        storage->Data.push_back(ImGuiStoragePair(id, selection_order)); // Push unsorted at end of vector, will be sorted in SelectionMultiAmendsFinish()
1193b4be Poul Sander 2025-02-03 21:28 8449
    else if (is_contained)
1193b4be Poul Sander 2025-02-03 21:28 8450
        it->val_i = selected ? selection_order : 0; // Modify in-place.
1193b4be Poul Sander 2025-02-03 21:28 8451
    selection->Size += selected ? +1 : -1;
1193b4be Poul Sander 2025-02-03 21:28 8452
}
1193b4be Poul Sander 2025-02-03 21:28 8453
1193b4be Poul Sander 2025-02-03 21:28 8454
static void ImGuiSelectionBasicStorage_BatchFinish(ImGuiSelectionBasicStorage* selection, bool selected, int size_before_amends)
1193b4be Poul Sander 2025-02-03 21:28 8455
{
1193b4be Poul Sander 2025-02-03 21:28 8456
    ImGuiStorage* storage = &selection->_Storage;
1193b4be Poul Sander 2025-02-03 21:28 8457
    if (selected && selection->Size != size_before_amends)
1193b4be Poul Sander 2025-02-03 21:28 8458
        storage->BuildSortByKey(); // When done selecting: sort everything
1193b4be Poul Sander 2025-02-03 21:28 8459
}
1193b4be Poul Sander 2025-02-03 21:28 8460
1193b4be Poul Sander 2025-02-03 21:28 8461
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8462
// - Enable 'Demo->Tools->Debug Log->Selection' to see selection requests as they happen.
1193b4be Poul Sander 2025-02-03 21:28 8463
// - Honoring SetRange requests requires that you can iterate/interpolate between RangeFirstItem and RangeLastItem.
1193b4be Poul Sander 2025-02-03 21:28 8464
//   - In this demo we often submit indices to SetNextItemSelectionUserData() + store the same indices in persistent selection.
1193b4be Poul Sander 2025-02-03 21:28 8465
//   - Your code may do differently. If you store pointers or objects ID in ImGuiSelectionUserData you may need to perform
1193b4be Poul Sander 2025-02-03 21:28 8466
//     a lookup in order to have some way to iterate/interpolate between two items.
1193b4be Poul Sander 2025-02-03 21:28 8467
// - A full-featured application is likely to allow search/filtering which is likely to lead to using indices
1193b4be Poul Sander 2025-02-03 21:28 8468
//   and constructing a view index <> object id/ptr data structure anyway.
1193b4be Poul Sander 2025-02-03 21:28 8469
// WHEN YOUR APPLICATION SETTLES ON A CHOICE, YOU WILL PROBABLY PREFER TO GET RID OF THIS UNNECESSARY 'ImGuiSelectionBasicStorage' INDIRECTION LOGIC.
1193b4be Poul Sander 2025-02-03 21:28 8470
// Notice that with the simplest adapter (using indices everywhere), all functions return their parameters.
1193b4be Poul Sander 2025-02-03 21:28 8471
// The most simple implementation (using indices everywhere) would look like:
1193b4be Poul Sander 2025-02-03 21:28 8472
//   for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8473
//   {
1193b4be Poul Sander 2025-02-03 21:28 8474
//      if (req.Type == ImGuiSelectionRequestType_SetAll)    { Clear(); if (req.Selected) { for (int n = 0; n < items_count; n++) { SetItemSelected(n, true); } }
1193b4be Poul Sander 2025-02-03 21:28 8475
//      if (req.Type == ImGuiSelectionRequestType_SetRange)  { for (int n = (int)ms_io->RangeFirstItem; n <= (int)ms_io->RangeLastItem; n++) { SetItemSelected(n, ms_io->Selected); } }
1193b4be Poul Sander 2025-02-03 21:28 8476
//   }
1193b4be Poul Sander 2025-02-03 21:28 8477
void ImGuiSelectionBasicStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
1193b4be Poul Sander 2025-02-03 21:28 8478
{
1193b4be Poul Sander 2025-02-03 21:28 8479
    // For convenience we obtain ItemsCount as passed to BeginMultiSelect(), which is optional.
1193b4be Poul Sander 2025-02-03 21:28 8480
    // It makes sense when using ImGuiSelectionBasicStorage to simply pass your items count to BeginMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8481
    // Other scheme may handle SetAll differently.
1193b4be Poul Sander 2025-02-03 21:28 8482
    IM_ASSERT(ms_io->ItemsCount != -1 && "Missing value for items_count in BeginMultiSelect() call!");
1193b4be Poul Sander 2025-02-03 21:28 8483
    IM_ASSERT(AdapterIndexToStorageId != NULL);
1193b4be Poul Sander 2025-02-03 21:28 8484
1193b4be Poul Sander 2025-02-03 21:28 8485
    // This is optimized/specialized to cope with very large selections (e.g. 100k+ items)
1193b4be Poul Sander 2025-02-03 21:28 8486
    // - A simpler version could call SetItemSelected() directly instead of ImGuiSelectionBasicStorage_BatchSetItemSelected() + ImGuiSelectionBasicStorage_BatchFinish().
1193b4be Poul Sander 2025-02-03 21:28 8487
    // - Optimized select can append unsorted, then sort in a second pass. Optimized unselect can clear in-place then compact in a second pass.
1193b4be Poul Sander 2025-02-03 21:28 8488
    // - 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.
1193b4be Poul Sander 2025-02-03 21:28 8489
    // - 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
4e9e2555 Poul Sander 2025-10-05 10:15 8490
    //   left and right: it affects coarse clipping + can emit multiple SetRange with 1 item each.
1193b4be Poul Sander 2025-02-03 21:28 8491
    // FIXME-OPT: For each block of consecutive SetRange request:
1193b4be Poul Sander 2025-02-03 21:28 8492
    // - add all requests to a sorted list, store ID, selected, offset in ImGuiStorage.
1193b4be Poul Sander 2025-02-03 21:28 8493
    // - rewrite sorted storage a single time.
1193b4be Poul Sander 2025-02-03 21:28 8494
    for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8495
    {
1193b4be Poul Sander 2025-02-03 21:28 8496
        if (req.Type == ImGuiSelectionRequestType_SetAll)
1193b4be Poul Sander 2025-02-03 21:28 8497
        {
1193b4be Poul Sander 2025-02-03 21:28 8498
            Clear();
1193b4be Poul Sander 2025-02-03 21:28 8499
            if (req.Selected)
1193b4be Poul Sander 2025-02-03 21:28 8500
            {
1193b4be Poul Sander 2025-02-03 21:28 8501
                _Storage.Data.reserve(ms_io->ItemsCount);
1193b4be Poul Sander 2025-02-03 21:28 8502
                const int size_before_amends = _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8503
                for (int idx = 0; idx < ms_io->ItemsCount; idx++, _SelectionOrder++)
1193b4be Poul Sander 2025-02-03 21:28 8504
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, _SelectionOrder);
1193b4be Poul Sander 2025-02-03 21:28 8505
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
1193b4be Poul Sander 2025-02-03 21:28 8506
            }
1193b4be Poul Sander 2025-02-03 21:28 8507
        }
1193b4be Poul Sander 2025-02-03 21:28 8508
        else if (req.Type == ImGuiSelectionRequestType_SetRange)
1193b4be Poul Sander 2025-02-03 21:28 8509
        {
1193b4be Poul Sander 2025-02-03 21:28 8510
            const int selection_changes = (int)req.RangeLastItem - (int)req.RangeFirstItem + 1;
1193b4be Poul Sander 2025-02-03 21:28 8511
            //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);
1193b4be Poul Sander 2025-02-03 21:28 8512
            if (selection_changes == 1 || (selection_changes < Size / 100))
1193b4be Poul Sander 2025-02-03 21:28 8513
            {
1193b4be Poul Sander 2025-02-03 21:28 8514
                // Multiple sorted insertion + copy likely to be faster.
1193b4be Poul Sander 2025-02-03 21:28 8515
                // Technically we could do a single copy with a little more work (sort sequential SetRange requests)
1193b4be Poul Sander 2025-02-03 21:28 8516
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8517
                    SetItemSelected(GetStorageIdFromIndex(idx), req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8518
            }
1193b4be Poul Sander 2025-02-03 21:28 8519
            else
1193b4be Poul Sander 2025-02-03 21:28 8520
            {
1193b4be Poul Sander 2025-02-03 21:28 8521
                // Append insertion + single sort likely be faster.
1193b4be Poul Sander 2025-02-03 21:28 8522
                // Use req.RangeDirection to set order field so that shift+clicking from 1 to 5 is different than shift+clicking from 5 to 1
1193b4be Poul Sander 2025-02-03 21:28 8523
                const int size_before_amends = _Storage.Data.Size;
1193b4be Poul Sander 2025-02-03 21:28 8524
                int selection_order = _SelectionOrder + ((req.RangeDirection < 0) ? selection_changes - 1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8525
                for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++, selection_order += req.RangeDirection)
1193b4be Poul Sander 2025-02-03 21:28 8526
                    ImGuiSelectionBasicStorage_BatchSetItemSelected(this, GetStorageIdFromIndex(idx), req.Selected, size_before_amends, selection_order);
1193b4be Poul Sander 2025-02-03 21:28 8527
                if (req.Selected)
1193b4be Poul Sander 2025-02-03 21:28 8528
                    _SelectionOrder += selection_changes;
1193b4be Poul Sander 2025-02-03 21:28 8529
                ImGuiSelectionBasicStorage_BatchFinish(this, req.Selected, size_before_amends);
1193b4be Poul Sander 2025-02-03 21:28 8530
            }
1193b4be Poul Sander 2025-02-03 21:28 8531
        }
1193b4be Poul Sander 2025-02-03 21:28 8532
    }
1193b4be Poul Sander 2025-02-03 21:28 8533
}
1193b4be Poul Sander 2025-02-03 21:28 8534
1193b4be Poul Sander 2025-02-03 21:28 8535
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8536
1193b4be Poul Sander 2025-02-03 21:28 8537
ImGuiSelectionExternalStorage::ImGuiSelectionExternalStorage()
1193b4be Poul Sander 2025-02-03 21:28 8538
{
1193b4be Poul Sander 2025-02-03 21:28 8539
    UserData = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8540
    AdapterSetItemSelected = NULL;
1193b4be Poul Sander 2025-02-03 21:28 8541
}
1193b4be Poul Sander 2025-02-03 21:28 8542
1193b4be Poul Sander 2025-02-03 21:28 8543
// Apply requests coming from BeginMultiSelect() and EndMultiSelect().
1193b4be Poul Sander 2025-02-03 21:28 8544
// We also pull 'ms_io->ItemsCount' as passed for BeginMultiSelect() for consistency with ImGuiSelectionBasicStorage
1193b4be Poul Sander 2025-02-03 21:28 8545
// This makes no assumption about underlying storage.
1193b4be Poul Sander 2025-02-03 21:28 8546
void ImGuiSelectionExternalStorage::ApplyRequests(ImGuiMultiSelectIO* ms_io)
1193b4be Poul Sander 2025-02-03 21:28 8547
{
1193b4be Poul Sander 2025-02-03 21:28 8548
    IM_ASSERT(AdapterSetItemSelected);
1193b4be Poul Sander 2025-02-03 21:28 8549
    for (ImGuiSelectionRequest& req : ms_io->Requests)
1193b4be Poul Sander 2025-02-03 21:28 8550
    {
1193b4be Poul Sander 2025-02-03 21:28 8551
        if (req.Type == ImGuiSelectionRequestType_SetAll)
1193b4be Poul Sander 2025-02-03 21:28 8552
            for (int idx = 0; idx < ms_io->ItemsCount; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8553
                AdapterSetItemSelected(this, idx, req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8554
        if (req.Type == ImGuiSelectionRequestType_SetRange)
1193b4be Poul Sander 2025-02-03 21:28 8555
            for (int idx = (int)req.RangeFirstItem; idx <= (int)req.RangeLastItem; idx++)
1193b4be Poul Sander 2025-02-03 21:28 8556
                AdapterSetItemSelected(this, idx, req.Selected);
1193b4be Poul Sander 2025-02-03 21:28 8557
    }
1193b4be Poul Sander 2025-02-03 21:28 8558
}
1193b4be Poul Sander 2025-02-03 21:28 8559
1193b4be Poul Sander 2025-02-03 21:28 8560
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8561
// [SECTION] Widgets: ListBox
1193b4be Poul Sander 2025-02-03 21:28 8562
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8563
// - BeginListBox()
1193b4be Poul Sander 2025-02-03 21:28 8564
// - EndListBox()
1193b4be Poul Sander 2025-02-03 21:28 8565
// - ListBox()
1193b4be Poul Sander 2025-02-03 21:28 8566
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8567
1193b4be Poul Sander 2025-02-03 21:28 8568
// This is essentially a thin wrapper to using BeginChild/EndChild with the ImGuiChildFlags_FrameStyle flag for stylistic changes + displaying a label.
4e9e2555 Poul Sander 2025-10-05 10:15 8569
// This handle some subtleties with capturing info from the label.
4e9e2555 Poul Sander 2025-10-05 10:15 8570
// If you don't need a label you can pretty much directly use ImGui::BeginChild() with ImGuiChildFlags_FrameStyle.
1193b4be Poul Sander 2025-02-03 21:28 8571
// Tip: To have a list filling the entire window width, use size.x = -FLT_MIN and pass an non-visible label e.g. "##empty"
4e9e2555 Poul Sander 2025-10-05 10:15 8572
// 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).
1193b4be Poul Sander 2025-02-03 21:28 8573
bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg)
1193b4be Poul Sander 2025-02-03 21:28 8574
{
1193b4be Poul Sander 2025-02-03 21:28 8575
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8576
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8577
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8578
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8579
1193b4be Poul Sander 2025-02-03 21:28 8580
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 8581
    const ImGuiID id = GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 8582
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 8583
1193b4be Poul Sander 2025-02-03 21:28 8584
    // Size default to hold ~7.25 items.
1193b4be Poul Sander 2025-02-03 21:28 8585
    // Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
1193b4be Poul Sander 2025-02-03 21:28 8586
    ImVec2 size = ImTrunc(CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.25f + style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 8587
    ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 8588
    ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 8589
    ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 8590
    g.NextItemData.ClearFlags();
1193b4be Poul Sander 2025-02-03 21:28 8591
1193b4be Poul Sander 2025-02-03 21:28 8592
    if (!IsRectVisible(bb.Min, bb.Max))
1193b4be Poul Sander 2025-02-03 21:28 8593
    {
1193b4be Poul Sander 2025-02-03 21:28 8594
        ItemSize(bb.GetSize(), style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8595
        ItemAdd(bb, 0, &frame_bb);
1193b4be Poul Sander 2025-02-03 21:28 8596
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 8597
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8598
    }
1193b4be Poul Sander 2025-02-03 21:28 8599
1193b4be Poul Sander 2025-02-03 21:28 8600
    // FIXME-OPT: We could omit the BeginGroup() if label_size.x == 0.0f but would need to omit the EndGroup() as well.
1193b4be Poul Sander 2025-02-03 21:28 8601
    BeginGroup();
1193b4be Poul Sander 2025-02-03 21:28 8602
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 8603
    {
1193b4be Poul Sander 2025-02-03 21:28 8604
        ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8605
        RenderText(label_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 8606
        window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size);
1193b4be Poul Sander 2025-02-03 21:28 8607
        AlignTextToFramePadding();
1193b4be Poul Sander 2025-02-03 21:28 8608
    }
1193b4be Poul Sander 2025-02-03 21:28 8609
1193b4be Poul Sander 2025-02-03 21:28 8610
    BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle);
1193b4be Poul Sander 2025-02-03 21:28 8611
    return true;
1193b4be Poul Sander 2025-02-03 21:28 8612
}
1193b4be Poul Sander 2025-02-03 21:28 8613
1193b4be Poul Sander 2025-02-03 21:28 8614
void ImGui::EndListBox()
1193b4be Poul Sander 2025-02-03 21:28 8615
{
1193b4be Poul Sander 2025-02-03 21:28 8616
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8617
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8618
    IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) && "Mismatched BeginListBox/EndListBox calls. Did you test the return value of BeginListBox?");
1193b4be Poul Sander 2025-02-03 21:28 8619
    IM_UNUSED(window);
1193b4be Poul Sander 2025-02-03 21:28 8620
1193b4be Poul Sander 2025-02-03 21:28 8621
    EndChild();
1193b4be Poul Sander 2025-02-03 21:28 8622
    EndGroup(); // This is only required to be able to do IsItemXXX query on the whole ListBox including label
1193b4be Poul Sander 2025-02-03 21:28 8623
}
1193b4be Poul Sander 2025-02-03 21:28 8624
1193b4be Poul Sander 2025-02-03 21:28 8625
bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
1193b4be Poul Sander 2025-02-03 21:28 8626
{
1193b4be Poul Sander 2025-02-03 21:28 8627
    const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
1193b4be Poul Sander 2025-02-03 21:28 8628
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 8629
}
1193b4be Poul Sander 2025-02-03 21:28 8630
1193b4be Poul Sander 2025-02-03 21:28 8631
// This is merely a helper around BeginListBox(), EndListBox().
1193b4be Poul Sander 2025-02-03 21:28 8632
// Considering using those directly to submit custom data or store selection differently.
1193b4be Poul Sander 2025-02-03 21:28 8633
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)
1193b4be Poul Sander 2025-02-03 21:28 8634
{
1193b4be Poul Sander 2025-02-03 21:28 8635
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8636
1193b4be Poul Sander 2025-02-03 21:28 8637
    // Calculate size from "height_in_items"
1193b4be Poul Sander 2025-02-03 21:28 8638
    if (height_in_items < 0)
1193b4be Poul Sander 2025-02-03 21:28 8639
        height_in_items = ImMin(items_count, 7);
1193b4be Poul Sander 2025-02-03 21:28 8640
    float height_in_items_f = height_in_items + 0.25f;
1193b4be Poul Sander 2025-02-03 21:28 8641
    ImVec2 size(0.0f, ImTrunc(GetTextLineHeightWithSpacing() * height_in_items_f + g.Style.FramePadding.y * 2.0f));
1193b4be Poul Sander 2025-02-03 21:28 8642
1193b4be Poul Sander 2025-02-03 21:28 8643
    if (!BeginListBox(label, size))
1193b4be Poul Sander 2025-02-03 21:28 8644
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8645
1193b4be Poul Sander 2025-02-03 21:28 8646
    // Assume all items have even height (= 1 line of text). If you need items of different height,
1193b4be Poul Sander 2025-02-03 21:28 8647
    // you can create a custom version of ListBox() in your code without using the clipper.
1193b4be Poul Sander 2025-02-03 21:28 8648
    bool value_changed = false;
1193b4be Poul Sander 2025-02-03 21:28 8649
    ImGuiListClipper clipper;
1193b4be Poul Sander 2025-02-03 21:28 8650
    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.
1193b4be Poul Sander 2025-02-03 21:28 8651
    clipper.IncludeItemByIndex(*current_item);
1193b4be Poul Sander 2025-02-03 21:28 8652
    while (clipper.Step())
1193b4be Poul Sander 2025-02-03 21:28 8653
        for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
1193b4be Poul Sander 2025-02-03 21:28 8654
        {
1193b4be Poul Sander 2025-02-03 21:28 8655
            const char* item_text = getter(user_data, i);
1193b4be Poul Sander 2025-02-03 21:28 8656
            if (item_text == NULL)
1193b4be Poul Sander 2025-02-03 21:28 8657
                item_text = "*Unknown item*";
1193b4be Poul Sander 2025-02-03 21:28 8658
1193b4be Poul Sander 2025-02-03 21:28 8659
            PushID(i);
1193b4be Poul Sander 2025-02-03 21:28 8660
            const bool item_selected = (i == *current_item);
1193b4be Poul Sander 2025-02-03 21:28 8661
            if (Selectable(item_text, item_selected))
1193b4be Poul Sander 2025-02-03 21:28 8662
            {
1193b4be Poul Sander 2025-02-03 21:28 8663
                *current_item = i;
1193b4be Poul Sander 2025-02-03 21:28 8664
                value_changed = true;
1193b4be Poul Sander 2025-02-03 21:28 8665
            }
1193b4be Poul Sander 2025-02-03 21:28 8666
            if (item_selected)
1193b4be Poul Sander 2025-02-03 21:28 8667
                SetItemDefaultFocus();
1193b4be Poul Sander 2025-02-03 21:28 8668
            PopID();
1193b4be Poul Sander 2025-02-03 21:28 8669
        }
1193b4be Poul Sander 2025-02-03 21:28 8670
    EndListBox();
1193b4be Poul Sander 2025-02-03 21:28 8671
1193b4be Poul Sander 2025-02-03 21:28 8672
    if (value_changed)
1193b4be Poul Sander 2025-02-03 21:28 8673
        MarkItemEdited(g.LastItemData.ID);
1193b4be Poul Sander 2025-02-03 21:28 8674
1193b4be Poul Sander 2025-02-03 21:28 8675
    return value_changed;
1193b4be Poul Sander 2025-02-03 21:28 8676
}
1193b4be Poul Sander 2025-02-03 21:28 8677
1193b4be Poul Sander 2025-02-03 21:28 8678
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8679
// [SECTION] Widgets: PlotLines, PlotHistogram
1193b4be Poul Sander 2025-02-03 21:28 8680
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8681
// - PlotEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8682
// - PlotLines()
1193b4be Poul Sander 2025-02-03 21:28 8683
// - PlotHistogram()
1193b4be Poul Sander 2025-02-03 21:28 8684
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8685
// Plot/Graph widgets are not very good.
1193b4be Poul Sander 2025-02-03 21:28 8686
// Consider writing your own, or using a third-party one, see:
1193b4be Poul Sander 2025-02-03 21:28 8687
// - ImPlot https://github.com/epezent/implot
1193b4be Poul Sander 2025-02-03 21:28 8688
// - others https://github.com/ocornut/imgui/wiki/Useful-Extensions
1193b4be Poul Sander 2025-02-03 21:28 8689
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8690
1193b4be Poul Sander 2025-02-03 21:28 8691
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)
1193b4be Poul Sander 2025-02-03 21:28 8692
{
1193b4be Poul Sander 2025-02-03 21:28 8693
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8694
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8695
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8696
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 8697
1193b4be Poul Sander 2025-02-03 21:28 8698
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 8699
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 8700
1193b4be Poul Sander 2025-02-03 21:28 8701
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 8702
    const ImVec2 frame_size = CalcItemSize(size_arg, CalcItemWidth(), label_size.y + style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 8703
1193b4be Poul Sander 2025-02-03 21:28 8704
    const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
1193b4be Poul Sander 2025-02-03 21:28 8705
    const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
1193b4be Poul Sander 2025-02-03 21:28 8706
    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));
1193b4be Poul Sander 2025-02-03 21:28 8707
    ItemSize(total_bb, style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 8708
    if (!ItemAdd(total_bb, id, &frame_bb, ImGuiItemFlags_NoNav))
1193b4be Poul Sander 2025-02-03 21:28 8709
        return -1;
1193b4be Poul Sander 2025-02-03 21:28 8710
    bool hovered;
1193b4be Poul Sander 2025-02-03 21:28 8711
    ButtonBehavior(frame_bb, id, &hovered, NULL);
1193b4be Poul Sander 2025-02-03 21:28 8712
1193b4be Poul Sander 2025-02-03 21:28 8713
    // Determine scale from values if not specified
1193b4be Poul Sander 2025-02-03 21:28 8714
    if (scale_min == FLT_MAX || scale_max == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8715
    {
1193b4be Poul Sander 2025-02-03 21:28 8716
        float v_min = FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 8717
        float v_max = -FLT_MAX;
1193b4be Poul Sander 2025-02-03 21:28 8718
        for (int i = 0; i < values_count; i++)
1193b4be Poul Sander 2025-02-03 21:28 8719
        {
1193b4be Poul Sander 2025-02-03 21:28 8720
            const float v = values_getter(data, i);
1193b4be Poul Sander 2025-02-03 21:28 8721
            if (v != v) // Ignore NaN values
1193b4be Poul Sander 2025-02-03 21:28 8722
                continue;
1193b4be Poul Sander 2025-02-03 21:28 8723
            v_min = ImMin(v_min, v);
1193b4be Poul Sander 2025-02-03 21:28 8724
            v_max = ImMax(v_max, v);
1193b4be Poul Sander 2025-02-03 21:28 8725
        }
1193b4be Poul Sander 2025-02-03 21:28 8726
        if (scale_min == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8727
            scale_min = v_min;
1193b4be Poul Sander 2025-02-03 21:28 8728
        if (scale_max == FLT_MAX)
1193b4be Poul Sander 2025-02-03 21:28 8729
            scale_max = v_max;
1193b4be Poul Sander 2025-02-03 21:28 8730
    }
1193b4be Poul Sander 2025-02-03 21:28 8731
1193b4be Poul Sander 2025-02-03 21:28 8732
    RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
1193b4be Poul Sander 2025-02-03 21:28 8733
1193b4be Poul Sander 2025-02-03 21:28 8734
    const int values_count_min = (plot_type == ImGuiPlotType_Lines) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 8735
    int idx_hovered = -1;
1193b4be Poul Sander 2025-02-03 21:28 8736
    if (values_count >= values_count_min)
1193b4be Poul Sander 2025-02-03 21:28 8737
    {
1193b4be Poul Sander 2025-02-03 21:28 8738
        int res_w = ImMin((int)frame_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8739
        int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
1193b4be Poul Sander 2025-02-03 21:28 8740
1193b4be Poul Sander 2025-02-03 21:28 8741
        // Tooltip on hover
1193b4be Poul Sander 2025-02-03 21:28 8742
        if (hovered && inner_bb.Contains(g.IO.MousePos))
1193b4be Poul Sander 2025-02-03 21:28 8743
        {
1193b4be Poul Sander 2025-02-03 21:28 8744
            const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
1193b4be Poul Sander 2025-02-03 21:28 8745
            const int v_idx = (int)(t * item_count);
1193b4be Poul Sander 2025-02-03 21:28 8746
            IM_ASSERT(v_idx >= 0 && v_idx < values_count);
1193b4be Poul Sander 2025-02-03 21:28 8747
1193b4be Poul Sander 2025-02-03 21:28 8748
            const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8749
            const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8750
            if (plot_type == ImGuiPlotType_Lines)
1193b4be Poul Sander 2025-02-03 21:28 8751
                SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
1193b4be Poul Sander 2025-02-03 21:28 8752
            else if (plot_type == ImGuiPlotType_Histogram)
1193b4be Poul Sander 2025-02-03 21:28 8753
                SetTooltip("%d: %8.4g", v_idx, v0);
1193b4be Poul Sander 2025-02-03 21:28 8754
            idx_hovered = v_idx;
1193b4be Poul Sander 2025-02-03 21:28 8755
        }
1193b4be Poul Sander 2025-02-03 21:28 8756
1193b4be Poul Sander 2025-02-03 21:28 8757
        const float t_step = 1.0f / (float)res_w;
1193b4be Poul Sander 2025-02-03 21:28 8758
        const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
1193b4be Poul Sander 2025-02-03 21:28 8759
1193b4be Poul Sander 2025-02-03 21:28 8760
        float v0 = values_getter(data, (0 + values_offset) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8761
        float t0 = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 8762
        ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) );                       // Point in the normalized space of our target rectangle
1193b4be Poul Sander 2025-02-03 21:28 8763
        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
1193b4be Poul Sander 2025-02-03 21:28 8764
1193b4be Poul Sander 2025-02-03 21:28 8765
        const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
1193b4be Poul Sander 2025-02-03 21:28 8766
        const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
1193b4be Poul Sander 2025-02-03 21:28 8767
1193b4be Poul Sander 2025-02-03 21:28 8768
        for (int n = 0; n < res_w; n++)
1193b4be Poul Sander 2025-02-03 21:28 8769
        {
1193b4be Poul Sander 2025-02-03 21:28 8770
            const float t1 = t0 + t_step;
1193b4be Poul Sander 2025-02-03 21:28 8771
            const int v1_idx = (int)(t0 * item_count + 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 8772
            IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
1193b4be Poul Sander 2025-02-03 21:28 8773
            const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
1193b4be Poul Sander 2025-02-03 21:28 8774
            const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
1193b4be Poul Sander 2025-02-03 21:28 8775
1193b4be Poul Sander 2025-02-03 21:28 8776
            // 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.
1193b4be Poul Sander 2025-02-03 21:28 8777
            ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
1193b4be Poul Sander 2025-02-03 21:28 8778
            ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
1193b4be Poul Sander 2025-02-03 21:28 8779
            if (plot_type == ImGuiPlotType_Lines)
1193b4be Poul Sander 2025-02-03 21:28 8780
            {
1193b4be Poul Sander 2025-02-03 21:28 8781
                window->DrawList->AddLine(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
1193b4be Poul Sander 2025-02-03 21:28 8782
            }
1193b4be Poul Sander 2025-02-03 21:28 8783
            else if (plot_type == ImGuiPlotType_Histogram)
1193b4be Poul Sander 2025-02-03 21:28 8784
            {
1193b4be Poul Sander 2025-02-03 21:28 8785
                if (pos1.x >= pos0.x + 2.0f)
1193b4be Poul Sander 2025-02-03 21:28 8786
                    pos1.x -= 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 8787
                window->DrawList->AddRectFilled(pos0, pos1, idx_hovered == v1_idx ? col_hovered : col_base);
1193b4be Poul Sander 2025-02-03 21:28 8788
            }
1193b4be Poul Sander 2025-02-03 21:28 8789
1193b4be Poul Sander 2025-02-03 21:28 8790
            t0 = t1;
1193b4be Poul Sander 2025-02-03 21:28 8791
            tp0 = tp1;
1193b4be Poul Sander 2025-02-03 21:28 8792
        }
1193b4be Poul Sander 2025-02-03 21:28 8793
    }
1193b4be Poul Sander 2025-02-03 21:28 8794
1193b4be Poul Sander 2025-02-03 21:28 8795
    // Text overlay
1193b4be Poul Sander 2025-02-03 21:28 8796
    if (overlay_text)
1193b4be Poul Sander 2025-02-03 21:28 8797
        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));
1193b4be Poul Sander 2025-02-03 21:28 8798
1193b4be Poul Sander 2025-02-03 21:28 8799
    if (label_size.x > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 8800
        RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
1193b4be Poul Sander 2025-02-03 21:28 8801
1193b4be Poul Sander 2025-02-03 21:28 8802
    // Return hovered index or -1 if none are hovered.
1193b4be Poul Sander 2025-02-03 21:28 8803
    // 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().
1193b4be Poul Sander 2025-02-03 21:28 8804
    return idx_hovered;
1193b4be Poul Sander 2025-02-03 21:28 8805
}
1193b4be Poul Sander 2025-02-03 21:28 8806
1193b4be Poul Sander 2025-02-03 21:28 8807
struct ImGuiPlotArrayGetterData
1193b4be Poul Sander 2025-02-03 21:28 8808
{
1193b4be Poul Sander 2025-02-03 21:28 8809
    const float* Values;
1193b4be Poul Sander 2025-02-03 21:28 8810
    int Stride;
1193b4be Poul Sander 2025-02-03 21:28 8811
1193b4be Poul Sander 2025-02-03 21:28 8812
    ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
1193b4be Poul Sander 2025-02-03 21:28 8813
};
1193b4be Poul Sander 2025-02-03 21:28 8814
1193b4be Poul Sander 2025-02-03 21:28 8815
static float Plot_ArrayGetter(void* data, int idx)
1193b4be Poul Sander 2025-02-03 21:28 8816
{
1193b4be Poul Sander 2025-02-03 21:28 8817
    ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
1193b4be Poul Sander 2025-02-03 21:28 8818
    const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
1193b4be Poul Sander 2025-02-03 21:28 8819
    return v;
1193b4be Poul Sander 2025-02-03 21:28 8820
}
1193b4be Poul Sander 2025-02-03 21:28 8821
1193b4be Poul Sander 2025-02-03 21:28 8822
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)
1193b4be Poul Sander 2025-02-03 21:28 8823
{
1193b4be Poul Sander 2025-02-03 21:28 8824
    ImGuiPlotArrayGetterData data(values, stride);
1193b4be Poul Sander 2025-02-03 21:28 8825
    PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
1193b4be Poul Sander 2025-02-03 21:28 8826
}
1193b4be Poul Sander 2025-02-03 21:28 8827
1193b4be Poul Sander 2025-02-03 21:28 8828
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)
1193b4be Poul Sander 2025-02-03 21:28 8829
{
1193b4be Poul Sander 2025-02-03 21:28 8830
    PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
1193b4be Poul Sander 2025-02-03 21:28 8831
}
1193b4be Poul Sander 2025-02-03 21:28 8832
1193b4be Poul Sander 2025-02-03 21:28 8833
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)
1193b4be Poul Sander 2025-02-03 21:28 8834
{
1193b4be Poul Sander 2025-02-03 21:28 8835
    ImGuiPlotArrayGetterData data(values, stride);
1193b4be Poul Sander 2025-02-03 21:28 8836
    PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
1193b4be Poul Sander 2025-02-03 21:28 8837
}
1193b4be Poul Sander 2025-02-03 21:28 8838
1193b4be Poul Sander 2025-02-03 21:28 8839
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)
1193b4be Poul Sander 2025-02-03 21:28 8840
{
1193b4be Poul Sander 2025-02-03 21:28 8841
    PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
1193b4be Poul Sander 2025-02-03 21:28 8842
}
1193b4be Poul Sander 2025-02-03 21:28 8843
1193b4be Poul Sander 2025-02-03 21:28 8844
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8845
// [SECTION] Widgets: Value helpers
1193b4be Poul Sander 2025-02-03 21:28 8846
// Those is not very useful, legacy API.
1193b4be Poul Sander 2025-02-03 21:28 8847
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8848
// - Value()
1193b4be Poul Sander 2025-02-03 21:28 8849
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8850
1193b4be Poul Sander 2025-02-03 21:28 8851
void ImGui::Value(const char* prefix, bool b)
1193b4be Poul Sander 2025-02-03 21:28 8852
{
1193b4be Poul Sander 2025-02-03 21:28 8853
    Text("%s: %s", prefix, (b ? "true" : "false"));
1193b4be Poul Sander 2025-02-03 21:28 8854
}
1193b4be Poul Sander 2025-02-03 21:28 8855
1193b4be Poul Sander 2025-02-03 21:28 8856
void ImGui::Value(const char* prefix, int v)
1193b4be Poul Sander 2025-02-03 21:28 8857
{
1193b4be Poul Sander 2025-02-03 21:28 8858
    Text("%s: %d", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8859
}
1193b4be Poul Sander 2025-02-03 21:28 8860
1193b4be Poul Sander 2025-02-03 21:28 8861
void ImGui::Value(const char* prefix, unsigned int v)
1193b4be Poul Sander 2025-02-03 21:28 8862
{
1193b4be Poul Sander 2025-02-03 21:28 8863
    Text("%s: %d", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8864
}
1193b4be Poul Sander 2025-02-03 21:28 8865
1193b4be Poul Sander 2025-02-03 21:28 8866
void ImGui::Value(const char* prefix, float v, const char* float_format)
1193b4be Poul Sander 2025-02-03 21:28 8867
{
1193b4be Poul Sander 2025-02-03 21:28 8868
    if (float_format)
1193b4be Poul Sander 2025-02-03 21:28 8869
    {
1193b4be Poul Sander 2025-02-03 21:28 8870
        char fmt[64];
1193b4be Poul Sander 2025-02-03 21:28 8871
        ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
1193b4be Poul Sander 2025-02-03 21:28 8872
        Text(fmt, prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8873
    }
1193b4be Poul Sander 2025-02-03 21:28 8874
    else
1193b4be Poul Sander 2025-02-03 21:28 8875
    {
1193b4be Poul Sander 2025-02-03 21:28 8876
        Text("%s: %.3f", prefix, v);
1193b4be Poul Sander 2025-02-03 21:28 8877
    }
1193b4be Poul Sander 2025-02-03 21:28 8878
}
1193b4be Poul Sander 2025-02-03 21:28 8879
1193b4be Poul Sander 2025-02-03 21:28 8880
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8881
// [SECTION] MenuItem, BeginMenu, EndMenu, etc.
1193b4be Poul Sander 2025-02-03 21:28 8882
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8883
// - ImGuiMenuColumns [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8884
// - BeginMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8885
// - EndMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8886
// - BeginMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8887
// - EndMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8888
// - BeginMenu()
1193b4be Poul Sander 2025-02-03 21:28 8889
// - EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 8890
// - MenuItemEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 8891
// - MenuItem()
1193b4be Poul Sander 2025-02-03 21:28 8892
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 8893
1193b4be Poul Sander 2025-02-03 21:28 8894
// Helpers for internal use
1193b4be Poul Sander 2025-02-03 21:28 8895
void ImGuiMenuColumns::Update(float spacing, bool window_reappearing)
1193b4be Poul Sander 2025-02-03 21:28 8896
{
1193b4be Poul Sander 2025-02-03 21:28 8897
    if (window_reappearing)
1193b4be Poul Sander 2025-02-03 21:28 8898
        memset(Widths, 0, sizeof(Widths));
1193b4be Poul Sander 2025-02-03 21:28 8899
    Spacing = (ImU16)spacing;
1193b4be Poul Sander 2025-02-03 21:28 8900
    CalcNextTotalWidth(true);
1193b4be Poul Sander 2025-02-03 21:28 8901
    memset(Widths, 0, sizeof(Widths));
1193b4be Poul Sander 2025-02-03 21:28 8902
    TotalWidth = NextTotalWidth;
1193b4be Poul Sander 2025-02-03 21:28 8903
    NextTotalWidth = 0;
1193b4be Poul Sander 2025-02-03 21:28 8904
}
1193b4be Poul Sander 2025-02-03 21:28 8905
1193b4be Poul Sander 2025-02-03 21:28 8906
void ImGuiMenuColumns::CalcNextTotalWidth(bool update_offsets)
1193b4be Poul Sander 2025-02-03 21:28 8907
{
1193b4be Poul Sander 2025-02-03 21:28 8908
    ImU16 offset = 0;
1193b4be Poul Sander 2025-02-03 21:28 8909
    bool want_spacing = false;
1193b4be Poul Sander 2025-02-03 21:28 8910
    for (int i = 0; i < IM_ARRAYSIZE(Widths); i++)
1193b4be Poul Sander 2025-02-03 21:28 8911
    {
1193b4be Poul Sander 2025-02-03 21:28 8912
        ImU16 width = Widths[i];
1193b4be Poul Sander 2025-02-03 21:28 8913
        if (want_spacing && width > 0)
1193b4be Poul Sander 2025-02-03 21:28 8914
            offset += Spacing;
1193b4be Poul Sander 2025-02-03 21:28 8915
        want_spacing |= (width > 0);
1193b4be Poul Sander 2025-02-03 21:28 8916
        if (update_offsets)
1193b4be Poul Sander 2025-02-03 21:28 8917
        {
1193b4be Poul Sander 2025-02-03 21:28 8918
            if (i == 1) { OffsetLabel = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8919
            if (i == 2) { OffsetShortcut = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8920
            if (i == 3) { OffsetMark = offset; }
1193b4be Poul Sander 2025-02-03 21:28 8921
        }
1193b4be Poul Sander 2025-02-03 21:28 8922
        offset += width;
1193b4be Poul Sander 2025-02-03 21:28 8923
    }
1193b4be Poul Sander 2025-02-03 21:28 8924
    NextTotalWidth = offset;
1193b4be Poul Sander 2025-02-03 21:28 8925
}
1193b4be Poul Sander 2025-02-03 21:28 8926
1193b4be Poul Sander 2025-02-03 21:28 8927
float ImGuiMenuColumns::DeclColumns(float w_icon, float w_label, float w_shortcut, float w_mark)
1193b4be Poul Sander 2025-02-03 21:28 8928
{
1193b4be Poul Sander 2025-02-03 21:28 8929
    Widths[0] = ImMax(Widths[0], (ImU16)w_icon);
1193b4be Poul Sander 2025-02-03 21:28 8930
    Widths[1] = ImMax(Widths[1], (ImU16)w_label);
1193b4be Poul Sander 2025-02-03 21:28 8931
    Widths[2] = ImMax(Widths[2], (ImU16)w_shortcut);
1193b4be Poul Sander 2025-02-03 21:28 8932
    Widths[3] = ImMax(Widths[3], (ImU16)w_mark);
1193b4be Poul Sander 2025-02-03 21:28 8933
    CalcNextTotalWidth(false);
1193b4be Poul Sander 2025-02-03 21:28 8934
    return (float)ImMax(TotalWidth, NextTotalWidth);
1193b4be Poul Sander 2025-02-03 21:28 8935
}
1193b4be Poul Sander 2025-02-03 21:28 8936
1193b4be Poul Sander 2025-02-03 21:28 8937
// FIXME: Provided a rectangle perhaps e.g. a BeginMenuBarEx() could be used anywhere..
1193b4be Poul Sander 2025-02-03 21:28 8938
// Currently the main responsibility of this function being to setup clip-rect + horizontal layout + menu navigation layer.
1193b4be Poul Sander 2025-02-03 21:28 8939
// 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.
1193b4be Poul Sander 2025-02-03 21:28 8940
// Then later the same system could be used for multiple menu-bars, scrollbars, side-bars.
1193b4be Poul Sander 2025-02-03 21:28 8941
bool ImGui::BeginMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8942
{
1193b4be Poul Sander 2025-02-03 21:28 8943
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8944
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8945
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8946
    if (!(window->Flags & ImGuiWindowFlags_MenuBar))
1193b4be Poul Sander 2025-02-03 21:28 8947
        return false;
1193b4be Poul Sander 2025-02-03 21:28 8948
1193b4be Poul Sander 2025-02-03 21:28 8949
    IM_ASSERT(!window->DC.MenuBarAppending);
1193b4be Poul Sander 2025-02-03 21:28 8950
    BeginGroup(); // Backup position on layer 0 // FIXME: Misleading to use a group for that backup/restore
4e9e2555 Poul Sander 2025-10-05 10:15 8951
    PushID("##MenuBar");
1193b4be Poul Sander 2025-02-03 21:28 8952
1193b4be Poul Sander 2025-02-03 21:28 8953
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 8954
    // 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.
4e9e2555 Poul Sander 2025-10-05 10:15 8955
    const float border_top = ImMax(IM_ROUND(window->WindowBorderSize * 0.5f - window->TitleBarHeight), 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 8956
    const float border_half = IM_ROUND(window->WindowBorderSize * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 8957
    ImRect bar_rect = window->MenuBarRect();
4e9e2555 Poul Sander 2025-10-05 10:15 8958
    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));
1193b4be Poul Sander 2025-02-03 21:28 8959
    clip_rect.ClipWith(window->OuterRectClipped);
1193b4be Poul Sander 2025-02-03 21:28 8960
    PushClipRect(clip_rect.Min, clip_rect.Max, false);
1193b4be Poul Sander 2025-02-03 21:28 8961
1193b4be Poul Sander 2025-02-03 21:28 8962
    // 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).
1193b4be Poul Sander 2025-02-03 21:28 8963
    window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
1193b4be Poul Sander 2025-02-03 21:28 8964
    window->DC.LayoutType = ImGuiLayoutType_Horizontal;
1193b4be Poul Sander 2025-02-03 21:28 8965
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 8966
    window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
1193b4be Poul Sander 2025-02-03 21:28 8967
    window->DC.MenuBarAppending = true;
1193b4be Poul Sander 2025-02-03 21:28 8968
    AlignTextToFramePadding();
1193b4be Poul Sander 2025-02-03 21:28 8969
    return true;
1193b4be Poul Sander 2025-02-03 21:28 8970
}
1193b4be Poul Sander 2025-02-03 21:28 8971
1193b4be Poul Sander 2025-02-03 21:28 8972
void ImGui::EndMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 8973
{
1193b4be Poul Sander 2025-02-03 21:28 8974
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 8975
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 8976
        return;
1193b4be Poul Sander 2025-02-03 21:28 8977
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 8978
4e9e2555 Poul Sander 2025-10-05 10:15 8979
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
4e9e2555 Poul Sander 2025-10-05 10:15 8980
    IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
4e9e2555 Poul Sander 2025-10-05 10:15 8981
    IM_ASSERT(window->DC.MenuBarAppending);
4e9e2555 Poul Sander 2025-10-05 10:15 8982
1193b4be Poul Sander 2025-02-03 21:28 8983
    // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
1193b4be Poul Sander 2025-02-03 21:28 8984
    if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8985
    {
1193b4be Poul Sander 2025-02-03 21:28 8986
        // Try to find out if the request is for one of our child menu
1193b4be Poul Sander 2025-02-03 21:28 8987
        ImGuiWindow* nav_earliest_child = g.NavWindow;
1193b4be Poul Sander 2025-02-03 21:28 8988
        while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 8989
            nav_earliest_child = nav_earliest_child->ParentWindow;
1193b4be Poul Sander 2025-02-03 21:28 8990
        if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
1193b4be Poul Sander 2025-02-03 21:28 8991
        {
1193b4be Poul Sander 2025-02-03 21:28 8992
            // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
1193b4be Poul Sander 2025-02-03 21:28 8993
            // 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)
1193b4be Poul Sander 2025-02-03 21:28 8994
            const ImGuiNavLayer layer = ImGuiNavLayer_Menu;
1193b4be Poul Sander 2025-02-03 21:28 8995
            IM_ASSERT(window->DC.NavLayersActiveMaskNext & (1 << layer)); // Sanity check (FIXME: Seems unnecessary)
1193b4be Poul Sander 2025-02-03 21:28 8996
            FocusWindow(window);
1193b4be Poul Sander 2025-02-03 21:28 8997
            SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
1193b4be Poul Sander 2025-02-03 21:28 8998
            // FIXME-NAV: How to deal with this when not using g.IO.ConfigNavCursorVisibleAuto?
1193b4be Poul Sander 2025-02-03 21:28 8999
            if (g.NavCursorVisible)
1193b4be Poul Sander 2025-02-03 21:28 9000
            {
1193b4be Poul Sander 2025-02-03 21:28 9001
                g.NavCursorVisible = false; // Hide nav cursor for the current frame so we don't see the intermediary selection. Will be set again
1193b4be Poul Sander 2025-02-03 21:28 9002
                g.NavCursorHideFrames = 2;
1193b4be Poul Sander 2025-02-03 21:28 9003
            }
1193b4be Poul Sander 2025-02-03 21:28 9004
            g.NavHighlightItemUnderNav = g.NavMousePosDirty = true;
1193b4be Poul Sander 2025-02-03 21:28 9005
            NavMoveRequestForward(g.NavMoveDir, g.NavMoveClipDir, g.NavMoveFlags, g.NavMoveScrollFlags); // Repeat
1193b4be Poul Sander 2025-02-03 21:28 9006
        }
1193b4be Poul Sander 2025-02-03 21:28 9007
    }
1193b4be Poul Sander 2025-02-03 21:28 9008
1193b4be Poul Sander 2025-02-03 21:28 9009
    PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 9010
    PopID();
4e9e2555 Poul Sander 2025-10-05 10:15 9011
    IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
1193b4be Poul Sander 2025-02-03 21:28 9012
    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.
1193b4be Poul Sander 2025-02-03 21:28 9013
1193b4be Poul Sander 2025-02-03 21:28 9014
    // FIXME: Extremely confusing, cleanup by (a) working on WorkRect stack system (b) not using a Group confusingly here.
1193b4be Poul Sander 2025-02-03 21:28 9015
    ImGuiGroupData& group_data = g.GroupStack.back();
1193b4be Poul Sander 2025-02-03 21:28 9016
    group_data.EmitItem = false;
1193b4be Poul Sander 2025-02-03 21:28 9017
    ImVec2 restore_cursor_max_pos = group_data.BackupCursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 9018
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, window->DC.CursorMaxPos.x - window->Scroll.x); // Convert ideal extents for scrolling layer equivalent.
1193b4be Poul Sander 2025-02-03 21:28 9019
    EndGroup(); // Restore position on layer 0 // FIXME: Misleading to use a group for that backup/restore
1193b4be Poul Sander 2025-02-03 21:28 9020
    window->DC.LayoutType = ImGuiLayoutType_Vertical;
1193b4be Poul Sander 2025-02-03 21:28 9021
    window->DC.IsSameLine = false;
1193b4be Poul Sander 2025-02-03 21:28 9022
    window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
1193b4be Poul Sander 2025-02-03 21:28 9023
    window->DC.MenuBarAppending = false;
1193b4be Poul Sander 2025-02-03 21:28 9024
    window->DC.CursorMaxPos = restore_cursor_max_pos;
1193b4be Poul Sander 2025-02-03 21:28 9025
}
1193b4be Poul Sander 2025-02-03 21:28 9026
1193b4be Poul Sander 2025-02-03 21:28 9027
// Important: calling order matters!
1193b4be Poul Sander 2025-02-03 21:28 9028
// FIXME: Somehow overlapping with docking tech.
1193b4be Poul Sander 2025-02-03 21:28 9029
// FIXME: The "rect-cut" aspect of this could be formalized into a lower-level helper (rect-cut: https://halt.software/dead-simple-layouts)
1193b4be Poul Sander 2025-02-03 21:28 9030
bool ImGui::BeginViewportSideBar(const char* name, ImGuiViewport* viewport_p, ImGuiDir dir, float axis_size, ImGuiWindowFlags window_flags)
1193b4be Poul Sander 2025-02-03 21:28 9031
{
1193b4be Poul Sander 2025-02-03 21:28 9032
    IM_ASSERT(dir != ImGuiDir_None);
1193b4be Poul Sander 2025-02-03 21:28 9033
1193b4be Poul Sander 2025-02-03 21:28 9034
    ImGuiWindow* bar_window = FindWindowByName(name);
1193b4be Poul Sander 2025-02-03 21:28 9035
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)(viewport_p ? viewport_p : GetMainViewport());
1193b4be Poul Sander 2025-02-03 21:28 9036
    if (bar_window == NULL || bar_window->BeginCount == 0)
1193b4be Poul Sander 2025-02-03 21:28 9037
    {
1193b4be Poul Sander 2025-02-03 21:28 9038
        // Calculate and set window size/position
1193b4be Poul Sander 2025-02-03 21:28 9039
        ImRect avail_rect = viewport->GetBuildWorkRect();
1193b4be Poul Sander 2025-02-03 21:28 9040
        ImGuiAxis axis = (dir == ImGuiDir_Up || dir == ImGuiDir_Down) ? ImGuiAxis_Y : ImGuiAxis_X;
1193b4be Poul Sander 2025-02-03 21:28 9041
        ImVec2 pos = avail_rect.Min;
1193b4be Poul Sander 2025-02-03 21:28 9042
        if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
1193b4be Poul Sander 2025-02-03 21:28 9043
            pos[axis] = avail_rect.Max[axis] - axis_size;
1193b4be Poul Sander 2025-02-03 21:28 9044
        ImVec2 size = avail_rect.GetSize();
1193b4be Poul Sander 2025-02-03 21:28 9045
        size[axis] = axis_size;
1193b4be Poul Sander 2025-02-03 21:28 9046
        SetNextWindowPos(pos);
1193b4be Poul Sander 2025-02-03 21:28 9047
        SetNextWindowSize(size);
1193b4be Poul Sander 2025-02-03 21:28 9048
1193b4be Poul Sander 2025-02-03 21:28 9049
        // Report our size into work area (for next frame) using actual window size
1193b4be Poul Sander 2025-02-03 21:28 9050
        if (dir == ImGuiDir_Up || dir == ImGuiDir_Left)
1193b4be Poul Sander 2025-02-03 21:28 9051
            viewport->BuildWorkInsetMin[axis] += axis_size;
1193b4be Poul Sander 2025-02-03 21:28 9052
        else if (dir == ImGuiDir_Down || dir == ImGuiDir_Right)
1193b4be Poul Sander 2025-02-03 21:28 9053
            viewport->BuildWorkInsetMax[axis] += axis_size;
1193b4be Poul Sander 2025-02-03 21:28 9054
    }
1193b4be Poul Sander 2025-02-03 21:28 9055
1193b4be Poul Sander 2025-02-03 21:28 9056
    window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
1193b4be Poul Sander 2025-02-03 21:28 9057
    SetNextWindowViewport(viewport->ID); // Enforce viewport so we don't create our own viewport when ImGuiConfigFlags_ViewportsNoMerge is set.
1193b4be Poul Sander 2025-02-03 21:28 9058
    PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9059
    PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0)); // Lift normal size constraint
1193b4be Poul Sander 2025-02-03 21:28 9060
    bool is_open = Begin(name, NULL, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 9061
    PopStyleVar(2);
1193b4be Poul Sander 2025-02-03 21:28 9062
1193b4be Poul Sander 2025-02-03 21:28 9063
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 9064
}
1193b4be Poul Sander 2025-02-03 21:28 9065
1193b4be Poul Sander 2025-02-03 21:28 9066
bool ImGui::BeginMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 9067
{
1193b4be Poul Sander 2025-02-03 21:28 9068
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9069
    ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
1193b4be Poul Sander 2025-02-03 21:28 9070
1193b4be Poul Sander 2025-02-03 21:28 9071
    // Notify of viewport change so GetFrameHeight() can be accurate in case of DPI change
1193b4be Poul Sander 2025-02-03 21:28 9072
    SetCurrentViewport(NULL, viewport);
1193b4be Poul Sander 2025-02-03 21:28 9073
1193b4be Poul Sander 2025-02-03 21:28 9074
    // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
1193b4be Poul Sander 2025-02-03 21:28 9075
    // FIXME: This could be generalized as an opt-in way to clamp window->DC.CursorStartPos to avoid SafeArea?
1193b4be Poul Sander 2025-02-03 21:28 9076
    // FIXME: Consider removing support for safe area down the line... it's messy. Nowadays consoles have support for TV calibration in OS settings.
1193b4be Poul Sander 2025-02-03 21:28 9077
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 9078
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
1193b4be Poul Sander 2025-02-03 21:28 9079
    float height = GetFrameHeight();
1193b4be Poul Sander 2025-02-03 21:28 9080
    bool is_open = BeginViewportSideBar("##MainMenuBar", viewport, ImGuiDir_Up, height, window_flags);
1193b4be Poul Sander 2025-02-03 21:28 9081
    g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 9082
    if (!is_open)
4e9e2555 Poul Sander 2025-10-05 10:15 9083
    {
1193b4be Poul Sander 2025-02-03 21:28 9084
        End();
4e9e2555 Poul Sander 2025-10-05 10:15 9085
        return false;
4e9e2555 Poul Sander 2025-10-05 10:15 9086
    }
4e9e2555 Poul Sander 2025-10-05 10:15 9087
4e9e2555 Poul Sander 2025-10-05 10:15 9088
    // Temporarily disable _NoSavedSettings, in the off-chance that tables or child windows submitted within the menu-bar may want to use settings. (#8356)
4e9e2555 Poul Sander 2025-10-05 10:15 9089
    g.CurrentWindow->Flags &= ~ImGuiWindowFlags_NoSavedSettings;
4e9e2555 Poul Sander 2025-10-05 10:15 9090
    BeginMenuBar();
1193b4be Poul Sander 2025-02-03 21:28 9091
    return is_open;
1193b4be Poul Sander 2025-02-03 21:28 9092
}
1193b4be Poul Sander 2025-02-03 21:28 9093
1193b4be Poul Sander 2025-02-03 21:28 9094
void ImGui::EndMainMenuBar()
1193b4be Poul Sander 2025-02-03 21:28 9095
{
4e9e2555 Poul Sander 2025-10-05 10:15 9096
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 9097
    if (!g.CurrentWindow->DC.MenuBarAppending)
4e9e2555 Poul Sander 2025-10-05 10:15 9098
    {
4e9e2555 Poul Sander 2025-10-05 10:15 9099
        IM_ASSERT_USER_ERROR(0, "Calling EndMainMenuBar() not from a menu-bar!"); // Not technically testing that it is the main menu bar
4e9e2555 Poul Sander 2025-10-05 10:15 9100
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 9101
    }
4e9e2555 Poul Sander 2025-10-05 10:15 9102
1193b4be Poul Sander 2025-02-03 21:28 9103
    EndMenuBar();
4e9e2555 Poul Sander 2025-10-05 10:15 9104
    g.CurrentWindow->Flags |= ImGuiWindowFlags_NoSavedSettings; // Restore _NoSavedSettings (#8356)
1193b4be Poul Sander 2025-02-03 21:28 9105
1193b4be Poul Sander 2025-02-03 21:28 9106
    // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
1193b4be Poul Sander 2025-02-03 21:28 9107
    // FIXME: With this strategy we won't be able to restore a NULL focus.
4e9e2555 Poul Sander 2025-10-05 10:15 9108
    if (g.CurrentWindow == g.NavWindow && g.NavLayer == ImGuiNavLayer_Main && !g.NavAnyRequest && g.ActiveId == 0)
1193b4be Poul Sander 2025-02-03 21:28 9109
        FocusTopMostWindowUnderOne(g.NavWindow, NULL, NULL, ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild);
1193b4be Poul Sander 2025-02-03 21:28 9110
1193b4be Poul Sander 2025-02-03 21:28 9111
    End();
1193b4be Poul Sander 2025-02-03 21:28 9112
}
1193b4be Poul Sander 2025-02-03 21:28 9113
1193b4be Poul Sander 2025-02-03 21:28 9114
static bool IsRootOfOpenMenuSet()
1193b4be Poul Sander 2025-02-03 21:28 9115
{
1193b4be Poul Sander 2025-02-03 21:28 9116
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9117
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9118
    if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 9119
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9120
1193b4be Poul Sander 2025-02-03 21:28 9121
    // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others
1193b4be Poul Sander 2025-02-03 21:28 9122
    // (e.g. inside menu bar vs loose menu items) based on parent ID.
1193b4be Poul Sander 2025-02-03 21:28 9123
    // This would however prevent the use of e.g. PushID() user code submitting menus.
1193b4be Poul Sander 2025-02-03 21:28 9124
    // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag,
1193b4be Poul Sander 2025-02-03 21:28 9125
    // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects.
1193b4be Poul Sander 2025-02-03 21:28 9126
    // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup
1193b4be Poul Sander 2025-02-03 21:28 9127
    // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu.
1193b4be Poul Sander 2025-02-03 21:28 9128
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 9129
    // 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
1193b4be Poul Sander 2025-02-03 21:28 9130
    // 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
1193b4be Poul Sander 2025-02-03 21:28 9131
    // it likely won't be a problem anyone runs into.
1193b4be Poul Sander 2025-02-03 21:28 9132
    const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size];
1193b4be Poul Sander 2025-02-03 21:28 9133
    if (window->DC.NavLayerCurrent != upper_popup->ParentNavLayer)
1193b4be Poul Sander 2025-02-03 21:28 9134
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9135
    return upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu) && ImGui::IsWindowChildOf(upper_popup->Window, window, true, false);
1193b4be Poul Sander 2025-02-03 21:28 9136
}
1193b4be Poul Sander 2025-02-03 21:28 9137
1193b4be Poul Sander 2025-02-03 21:28 9138
bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9139
{
1193b4be Poul Sander 2025-02-03 21:28 9140
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 9141
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9142
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9143
1193b4be Poul Sander 2025-02-03 21:28 9144
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9145
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 9146
    const ImGuiID id = window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 9147
    bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None);
1193b4be Poul Sander 2025-02-03 21:28 9148
1193b4be Poul Sander 2025-02-03 21:28 9149
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 9150
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 9151
    ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus;
1193b4be Poul Sander 2025-02-03 21:28 9152
    if (window->Flags & ImGuiWindowFlags_ChildMenu)
1193b4be Poul Sander 2025-02-03 21:28 9153
        window_flags |= ImGuiWindowFlags_ChildWindow;
1193b4be Poul Sander 2025-02-03 21:28 9154
1193b4be Poul Sander 2025-02-03 21:28 9155
    // If a menu with same the ID was already submitted, we will append to it, matching the behavior of Begin().
1193b4be Poul Sander 2025-02-03 21:28 9156
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 9157
    // If somehow this is ever becoming a problem we can switch to use e.g. ImGuiStorage mapping key to last frame used.
1193b4be Poul Sander 2025-02-03 21:28 9158
    if (g.MenusIdSubmittedThisFrame.contains(id))
1193b4be Poul Sander 2025-02-03 21:28 9159
    {
1193b4be Poul Sander 2025-02-03 21:28 9160
        if (menu_is_open)
4e9e2555 Poul Sander 2025-10-05 10:15 9161
            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)
1193b4be Poul Sander 2025-02-03 21:28 9162
        else
1193b4be Poul Sander 2025-02-03 21:28 9163
            g.NextWindowData.ClearFlags();          // we behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 9164
        return menu_is_open;
1193b4be Poul Sander 2025-02-03 21:28 9165
    }
1193b4be Poul Sander 2025-02-03 21:28 9166
1193b4be Poul Sander 2025-02-03 21:28 9167
    // Tag menu as used. Next time BeginMenu() with same ID is called it will append to existing menu
1193b4be Poul Sander 2025-02-03 21:28 9168
    g.MenusIdSubmittedThisFrame.push_back(id);
1193b4be Poul Sander 2025-02-03 21:28 9169
1193b4be Poul Sander 2025-02-03 21:28 9170
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 9171
1193b4be Poul Sander 2025-02-03 21:28 9172
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 9173
    // This is only done for items for the menu set and not the full parent window.
1193b4be Poul Sander 2025-02-03 21:28 9174
    const bool menuset_is_open = IsRootOfOpenMenuSet();
1193b4be Poul Sander 2025-02-03 21:28 9175
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9176
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
1193b4be Poul Sander 2025-02-03 21:28 9177
1193b4be Poul Sander 2025-02-03 21:28 9178
    // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
1193b4be Poul Sander 2025-02-03 21:28 9179
    // However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
1193b4be Poul Sander 2025-02-03 21:28 9180
    // e.g. Menus tend to overlap each other horizontally to amplify relative Z-ordering.
1193b4be Poul Sander 2025-02-03 21:28 9181
    ImVec2 popup_pos, pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9182
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 9183
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9184
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9185
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
1193b4be Poul Sander 2025-02-03 21:28 9186
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 9187
1193b4be Poul Sander 2025-02-03 21:28 9188
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
1193b4be Poul Sander 2025-02-03 21:28 9189
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SelectOnClick | ImGuiSelectableFlags_NoAutoClosePopups;
1193b4be Poul Sander 2025-02-03 21:28 9190
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 9191
    {
4e9e2555 Poul Sander 2025-10-05 10:15 9192
        // Menu inside a horizontal menu bar
1193b4be Poul Sander 2025-02-03 21:28 9193
        // Selectable extend their highlight by half ItemSpacing in each direction.
1193b4be Poul Sander 2025-02-03 21:28 9194
        // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
1193b4be Poul Sander 2025-02-03 21:28 9195
        popup_pos = ImVec2(pos.x - 1.0f - IM_TRUNC(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight);
1193b4be Poul Sander 2025-02-03 21:28 9196
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9197
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 9198
        float w = label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 9199
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 9200
        pressed = Selectable("", menu_is_open, selectable_flags, ImVec2(w, label_size.y));
4e9e2555 Poul Sander 2025-10-05 10:15 9201
        LogSetNextTextDecoration("[", "]");
1193b4be Poul Sander 2025-02-03 21:28 9202
        RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 9203
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 9204
        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().
1193b4be Poul Sander 2025-02-03 21:28 9205
    }
1193b4be Poul Sander 2025-02-03 21:28 9206
    else
1193b4be Poul Sander 2025-02-03 21:28 9207
    {
1193b4be Poul Sander 2025-02-03 21:28 9208
        // Menu inside a regular/vertical menu
1193b4be Poul Sander 2025-02-03 21:28 9209
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
4e9e2555 Poul Sander 2025-10-05 10:15 9210
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.)
1193b4be Poul Sander 2025-02-03 21:28 9211
        popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
1193b4be Poul Sander 2025-02-03 21:28 9212
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9213
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
1193b4be Poul Sander 2025-02-03 21:28 9214
        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, 0.0f, checkmark_w); // Feedback to next frame
1193b4be Poul Sander 2025-02-03 21:28 9215
        float extra_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
1193b4be Poul Sander 2025-02-03 21:28 9216
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 9217
        pressed = Selectable("", menu_is_open, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
4e9e2555 Poul Sander 2025-10-05 10:15 9218
        LogSetNextTextDecoration("", ">");
1193b4be Poul Sander 2025-02-03 21:28 9219
        RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 9220
        if (icon_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9221
            RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
1193b4be Poul Sander 2025-02-03 21:28 9222
        RenderArrow(window->DrawList, pos + ImVec2(offsets->OffsetMark + extra_w + g.FontSize * 0.30f, 0.0f), GetColorU32(ImGuiCol_Text), ImGuiDir_Right);
1193b4be Poul Sander 2025-02-03 21:28 9223
    }
1193b4be Poul Sander 2025-02-03 21:28 9224
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9225
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9226
1193b4be Poul Sander 2025-02-03 21:28 9227
    const bool hovered = (g.HoveredId == id) && enabled && !g.NavHighlightItemUnderNav;
1193b4be Poul Sander 2025-02-03 21:28 9228
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9229
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 9230
1193b4be Poul Sander 2025-02-03 21:28 9231
    bool want_open = false;
1193b4be Poul Sander 2025-02-03 21:28 9232
    bool want_open_nav_init = false;
1193b4be Poul Sander 2025-02-03 21:28 9233
    bool want_close = false;
1193b4be Poul Sander 2025-02-03 21:28 9234
    if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
1193b4be Poul Sander 2025-02-03 21:28 9235
    {
1193b4be Poul Sander 2025-02-03 21:28 9236
        // Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
1193b4be Poul Sander 2025-02-03 21:28 9237
        // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
1193b4be Poul Sander 2025-02-03 21:28 9238
        bool moving_toward_child_menu = false;
1193b4be Poul Sander 2025-02-03 21:28 9239
        ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
1193b4be Poul Sander 2025-02-03 21:28 9240
        ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
1193b4be Poul Sander 2025-02-03 21:28 9241
        if (g.HoveredWindow == window && child_menu_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 9242
        {
1193b4be Poul Sander 2025-02-03 21:28 9243
            const float ref_unit = g.FontSize; // FIXME-DPI
1193b4be Poul Sander 2025-02-03 21:28 9244
            const float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 9245
            const ImRect next_window_rect = child_menu_window->Rect();
1193b4be Poul Sander 2025-02-03 21:28 9246
            ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
1193b4be Poul Sander 2025-02-03 21:28 9247
            ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
1193b4be Poul Sander 2025-02-03 21:28 9248
            ImVec2 tc = (child_dir > 0.0f) ? next_window_rect.GetBL() : next_window_rect.GetBR();
1193b4be Poul Sander 2025-02-03 21:28 9249
            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.
1193b4be Poul Sander 2025-02-03 21:28 9250
            ta.x += child_dir * -0.5f;
1193b4be Poul Sander 2025-02-03 21:28 9251
            tb.x += child_dir * ref_unit;
1193b4be Poul Sander 2025-02-03 21:28 9252
            tc.x += child_dir * ref_unit;
1193b4be Poul Sander 2025-02-03 21:28 9253
            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
1193b4be Poul Sander 2025-02-03 21:28 9254
            tc.y = ta.y + ImMin((tc.y + pad_farmost_h) - ta.y, +ref_unit * 8.0f);
1193b4be Poul Sander 2025-02-03 21:28 9255
            moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 9256
            //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
1193b4be Poul Sander 2025-02-03 21:28 9257
        }
1193b4be Poul Sander 2025-02-03 21:28 9258
1193b4be Poul Sander 2025-02-03 21:28 9259
        // 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)
1193b4be Poul Sander 2025-02-03 21:28 9260
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 9261
        // (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.)
1193b4be Poul Sander 2025-02-03 21:28 9262
        if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavHighlightItemUnderNav && g.ActiveId == 0)
1193b4be Poul Sander 2025-02-03 21:28 9263
            want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 9264
1193b4be Poul Sander 2025-02-03 21:28 9265
        // Open
1193b4be Poul Sander 2025-02-03 21:28 9266
        // (note: at this point 'hovered' actually includes the NavDisableMouseHover == false test)
1193b4be Poul Sander 2025-02-03 21:28 9267
        if (!menu_is_open && pressed) // Click/activate to open
1193b4be Poul Sander 2025-02-03 21:28 9268
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9269
        else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
1193b4be Poul Sander 2025-02-03 21:28 9270
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9271
        else if (!menu_is_open && hovered && g.HoveredIdTimer >= 0.30f && g.MouseStationaryTimer >= 0.30f) // Hover to open (timer fallback)
1193b4be Poul Sander 2025-02-03 21:28 9272
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9273
        if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
1193b4be Poul Sander 2025-02-03 21:28 9274
        {
1193b4be Poul Sander 2025-02-03 21:28 9275
            want_open = want_open_nav_init = true;
1193b4be Poul Sander 2025-02-03 21:28 9276
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 9277
            SetNavCursorVisibleAfterMove();
1193b4be Poul Sander 2025-02-03 21:28 9278
        }
1193b4be Poul Sander 2025-02-03 21:28 9279
    }
1193b4be Poul Sander 2025-02-03 21:28 9280
    else
1193b4be Poul Sander 2025-02-03 21:28 9281
    {
1193b4be Poul Sander 2025-02-03 21:28 9282
        // Menu bar
1193b4be Poul Sander 2025-02-03 21:28 9283
        if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
1193b4be Poul Sander 2025-02-03 21:28 9284
        {
1193b4be Poul Sander 2025-02-03 21:28 9285
            want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 9286
            want_open = menu_is_open = false;
1193b4be Poul Sander 2025-02-03 21:28 9287
        }
1193b4be Poul Sander 2025-02-03 21:28 9288
        else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
1193b4be Poul Sander 2025-02-03 21:28 9289
        {
1193b4be Poul Sander 2025-02-03 21:28 9290
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9291
        }
1193b4be Poul Sander 2025-02-03 21:28 9292
        else if (g.NavId == id && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
1193b4be Poul Sander 2025-02-03 21:28 9293
        {
1193b4be Poul Sander 2025-02-03 21:28 9294
            want_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9295
            NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 9296
        }
1193b4be Poul Sander 2025-02-03 21:28 9297
    }
1193b4be Poul Sander 2025-02-03 21:28 9298
1193b4be Poul Sander 2025-02-03 21:28 9299
    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.. }'
1193b4be Poul Sander 2025-02-03 21:28 9300
        want_close = true;
1193b4be Poul Sander 2025-02-03 21:28 9301
    if (want_close && IsPopupOpen(id, ImGuiPopupFlags_None))
1193b4be Poul Sander 2025-02-03 21:28 9302
        ClosePopupToLevel(g.BeginPopupStack.Size, true);
1193b4be Poul Sander 2025-02-03 21:28 9303
1193b4be Poul Sander 2025-02-03 21:28 9304
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Openable | (menu_is_open ? ImGuiItemStatusFlags_Opened : 0));
1193b4be Poul Sander 2025-02-03 21:28 9305
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 9306
1193b4be Poul Sander 2025-02-03 21:28 9307
    if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size)
1193b4be Poul Sander 2025-02-03 21:28 9308
    {
1193b4be Poul Sander 2025-02-03 21:28 9309
        // 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.
1193b4be Poul Sander 2025-02-03 21:28 9310
        OpenPopup(label);
1193b4be Poul Sander 2025-02-03 21:28 9311
    }
1193b4be Poul Sander 2025-02-03 21:28 9312
    else if (want_open)
1193b4be Poul Sander 2025-02-03 21:28 9313
    {
1193b4be Poul Sander 2025-02-03 21:28 9314
        menu_is_open = true;
1193b4be Poul Sander 2025-02-03 21:28 9315
        OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0));
1193b4be Poul Sander 2025-02-03 21:28 9316
    }
1193b4be Poul Sander 2025-02-03 21:28 9317
1193b4be Poul Sander 2025-02-03 21:28 9318
    if (menu_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9319
    {
1193b4be Poul Sander 2025-02-03 21:28 9320
        ImGuiLastItemData last_item_in_parent = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 9321
        SetNextWindowPos(popup_pos, ImGuiCond_Always);                  // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
1193b4be Poul Sander 2025-02-03 21:28 9322
        PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
4e9e2555 Poul Sander 2025-10-05 10:15 9323
        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)
1193b4be Poul Sander 2025-02-03 21:28 9324
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 9325
        if (menu_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9326
        {
1193b4be Poul Sander 2025-02-03 21:28 9327
            // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do:
1193b4be Poul Sander 2025-02-03 21:28 9328
            // Perform an init request in the case the popup was already open (via a previous mouse hover)
1193b4be Poul Sander 2025-02-03 21:28 9329
            if (want_open && want_open_nav_init && !g.NavInitRequest)
1193b4be Poul Sander 2025-02-03 21:28 9330
            {
1193b4be Poul Sander 2025-02-03 21:28 9331
                FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal);
1193b4be Poul Sander 2025-02-03 21:28 9332
                NavInitWindow(g.CurrentWindow, false);
1193b4be Poul Sander 2025-02-03 21:28 9333
            }
1193b4be Poul Sander 2025-02-03 21:28 9334
1193b4be Poul Sander 2025-02-03 21:28 9335
            // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 9336
            // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
1193b4be Poul Sander 2025-02-03 21:28 9337
            g.LastItemData = last_item_in_parent;
1193b4be Poul Sander 2025-02-03 21:28 9338
            if (g.HoveredWindow == window)
1193b4be Poul Sander 2025-02-03 21:28 9339
                g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 9340
        }
1193b4be Poul Sander 2025-02-03 21:28 9341
    }
1193b4be Poul Sander 2025-02-03 21:28 9342
    else
1193b4be Poul Sander 2025-02-03 21:28 9343
    {
1193b4be Poul Sander 2025-02-03 21:28 9344
        g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
1193b4be Poul Sander 2025-02-03 21:28 9345
    }
1193b4be Poul Sander 2025-02-03 21:28 9346
1193b4be Poul Sander 2025-02-03 21:28 9347
    return menu_is_open;
1193b4be Poul Sander 2025-02-03 21:28 9348
}
1193b4be Poul Sander 2025-02-03 21:28 9349
1193b4be Poul Sander 2025-02-03 21:28 9350
bool ImGui::BeginMenu(const char* label, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9351
{
1193b4be Poul Sander 2025-02-03 21:28 9352
    return BeginMenuEx(label, NULL, enabled);
1193b4be Poul Sander 2025-02-03 21:28 9353
}
1193b4be Poul Sander 2025-02-03 21:28 9354
1193b4be Poul Sander 2025-02-03 21:28 9355
void ImGui::EndMenu()
1193b4be Poul Sander 2025-02-03 21:28 9356
{
1193b4be Poul Sander 2025-02-03 21:28 9357
    // Nav: When a left move request our menu failed, close ourselves.
1193b4be Poul Sander 2025-02-03 21:28 9358
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9359
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9360
    IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup);  // Mismatched BeginMenu()/EndMenu() calls
1193b4be Poul Sander 2025-02-03 21:28 9361
    ImGuiWindow* parent_window = window->ParentWindow;  // Should always be != NULL is we passed assert.
1193b4be Poul Sander 2025-02-03 21:28 9362
    if (window->BeginCount == window->BeginCountPreviousFrame)
1193b4be Poul Sander 2025-02-03 21:28 9363
        if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
1193b4be Poul Sander 2025-02-03 21:28 9364
            if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
1193b4be Poul Sander 2025-02-03 21:28 9365
            {
1193b4be Poul Sander 2025-02-03 21:28 9366
                ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
1193b4be Poul Sander 2025-02-03 21:28 9367
                NavMoveRequestCancel();
1193b4be Poul Sander 2025-02-03 21:28 9368
            }
1193b4be Poul Sander 2025-02-03 21:28 9369
1193b4be Poul Sander 2025-02-03 21:28 9370
    EndPopup();
1193b4be Poul Sander 2025-02-03 21:28 9371
}
1193b4be Poul Sander 2025-02-03 21:28 9372
1193b4be Poul Sander 2025-02-03 21:28 9373
bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut, bool selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9374
{
1193b4be Poul Sander 2025-02-03 21:28 9375
    ImGuiWindow* window = GetCurrentWindow();
1193b4be Poul Sander 2025-02-03 21:28 9376
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9377
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9378
1193b4be Poul Sander 2025-02-03 21:28 9379
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9380
    ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 9381
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9382
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 9383
1193b4be Poul Sander 2025-02-03 21:28 9384
    // See BeginMenuEx() for comments about this.
1193b4be Poul Sander 2025-02-03 21:28 9385
    const bool menuset_is_open = IsRootOfOpenMenuSet();
1193b4be Poul Sander 2025-02-03 21:28 9386
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9387
        PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
1193b4be Poul Sander 2025-02-03 21:28 9388
1193b4be Poul Sander 2025-02-03 21:28 9389
    // We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
1193b4be Poul Sander 2025-02-03 21:28 9390
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 9391
    bool pressed;
1193b4be Poul Sander 2025-02-03 21:28 9392
    PushID(label);
1193b4be Poul Sander 2025-02-03 21:28 9393
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9394
        BeginDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9395
1193b4be Poul Sander 2025-02-03 21:28 9396
    // We use ImGuiSelectableFlags_NoSetKeyOwner to allow down on one menu item, move, up on another.
1193b4be Poul Sander 2025-02-03 21:28 9397
    const ImGuiSelectableFlags selectable_flags = ImGuiSelectableFlags_SelectOnRelease | ImGuiSelectableFlags_NoSetKeyOwner | ImGuiSelectableFlags_SetNavIdOnHover;
1193b4be Poul Sander 2025-02-03 21:28 9398
    const ImGuiMenuColumns* offsets = &window->DC.MenuColumns;
1193b4be Poul Sander 2025-02-03 21:28 9399
    if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
1193b4be Poul Sander 2025-02-03 21:28 9400
    {
1193b4be Poul Sander 2025-02-03 21:28 9401
        // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
1193b4be Poul Sander 2025-02-03 21:28 9402
        // Note that in this situation: we don't render the shortcut, we render a highlight instead of the selected tick mark.
1193b4be Poul Sander 2025-02-03 21:28 9403
        float w = label_size.x;
1193b4be Poul Sander 2025-02-03 21:28 9404
        window->DC.CursorPos.x += IM_TRUNC(style.ItemSpacing.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9405
        ImVec2 text_pos(window->DC.CursorPos.x + offsets->OffsetLabel, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
1193b4be Poul Sander 2025-02-03 21:28 9406
        PushStyleVarX(ImGuiStyleVar_ItemSpacing, style.ItemSpacing.x * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 9407
        pressed = Selectable("", selected, selectable_flags, ImVec2(w, 0.0f));
1193b4be Poul Sander 2025-02-03 21:28 9408
        PopStyleVar();
1193b4be Poul Sander 2025-02-03 21:28 9409
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
1193b4be Poul Sander 2025-02-03 21:28 9410
            RenderText(text_pos, label);
1193b4be Poul Sander 2025-02-03 21:28 9411
        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().
1193b4be Poul Sander 2025-02-03 21:28 9412
    }
1193b4be Poul Sander 2025-02-03 21:28 9413
    else
1193b4be Poul Sander 2025-02-03 21:28 9414
    {
1193b4be Poul Sander 2025-02-03 21:28 9415
        // Menu item inside a vertical menu
1193b4be Poul Sander 2025-02-03 21:28 9416
        // (In a typical menu window where all items are BeginMenu() or MenuItem() calls, extra_w will always be 0.0f.
4e9e2555 Poul Sander 2025-10-05 10:15 9417
        //  Only when they are other items sticking out we're going to add spacing, yet only register minimum width into the layout system.)
1193b4be Poul Sander 2025-02-03 21:28 9418
        float icon_w = (icon && icon[0]) ? CalcTextSize(icon, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9419
        float shortcut_w = (shortcut && shortcut[0]) ? CalcTextSize(shortcut, NULL).x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9420
        float checkmark_w = IM_TRUNC(g.FontSize * 1.20f);
1193b4be Poul Sander 2025-02-03 21:28 9421
        float min_w = window->DC.MenuColumns.DeclColumns(icon_w, label_size.x, shortcut_w, checkmark_w); // Feedback for next frame
1193b4be Poul Sander 2025-02-03 21:28 9422
        float stretch_w = ImMax(0.0f, GetContentRegionAvail().x - min_w);
1193b4be Poul Sander 2025-02-03 21:28 9423
        pressed = Selectable("", false, selectable_flags | ImGuiSelectableFlags_SpanAvailWidth, ImVec2(min_w, label_size.y));
1193b4be Poul Sander 2025-02-03 21:28 9424
        if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible)
1193b4be Poul Sander 2025-02-03 21:28 9425
        {
1193b4be Poul Sander 2025-02-03 21:28 9426
            RenderText(pos + ImVec2(offsets->OffsetLabel, 0.0f), label);
1193b4be Poul Sander 2025-02-03 21:28 9427
            if (icon_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9428
                RenderText(pos + ImVec2(offsets->OffsetIcon, 0.0f), icon);
1193b4be Poul Sander 2025-02-03 21:28 9429
            if (shortcut_w > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9430
            {
1193b4be Poul Sander 2025-02-03 21:28 9431
                PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
4e9e2555 Poul Sander 2025-10-05 10:15 9432
                LogSetNextTextDecoration("(", ")");
1193b4be Poul Sander 2025-02-03 21:28 9433
                RenderText(pos + ImVec2(offsets->OffsetShortcut + stretch_w, 0.0f), shortcut, NULL, false);
1193b4be Poul Sander 2025-02-03 21:28 9434
                PopStyleColor();
1193b4be Poul Sander 2025-02-03 21:28 9435
            }
1193b4be Poul Sander 2025-02-03 21:28 9436
            if (selected)
1193b4be Poul Sander 2025-02-03 21:28 9437
                RenderCheckMark(window->DrawList, pos + ImVec2(offsets->OffsetMark + stretch_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(ImGuiCol_Text), g.FontSize * 0.866f);
1193b4be Poul Sander 2025-02-03 21:28 9438
        }
1193b4be Poul Sander 2025-02-03 21:28 9439
    }
1193b4be Poul Sander 2025-02-03 21:28 9440
    IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | ImGuiItemStatusFlags_Checkable | (selected ? ImGuiItemStatusFlags_Checked : 0));
1193b4be Poul Sander 2025-02-03 21:28 9441
    if (!enabled)
1193b4be Poul Sander 2025-02-03 21:28 9442
        EndDisabled();
1193b4be Poul Sander 2025-02-03 21:28 9443
    PopID();
1193b4be Poul Sander 2025-02-03 21:28 9444
    if (menuset_is_open)
1193b4be Poul Sander 2025-02-03 21:28 9445
        PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 9446
1193b4be Poul Sander 2025-02-03 21:28 9447
    return pressed;
1193b4be Poul Sander 2025-02-03 21:28 9448
}
1193b4be Poul Sander 2025-02-03 21:28 9449
1193b4be Poul Sander 2025-02-03 21:28 9450
bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9451
{
1193b4be Poul Sander 2025-02-03 21:28 9452
    return MenuItemEx(label, NULL, shortcut, selected, enabled);
1193b4be Poul Sander 2025-02-03 21:28 9453
}
1193b4be Poul Sander 2025-02-03 21:28 9454
1193b4be Poul Sander 2025-02-03 21:28 9455
bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
1193b4be Poul Sander 2025-02-03 21:28 9456
{
1193b4be Poul Sander 2025-02-03 21:28 9457
    if (MenuItemEx(label, NULL, shortcut, p_selected ? *p_selected : false, enabled))
1193b4be Poul Sander 2025-02-03 21:28 9458
    {
1193b4be Poul Sander 2025-02-03 21:28 9459
        if (p_selected)
1193b4be Poul Sander 2025-02-03 21:28 9460
            *p_selected = !*p_selected;
1193b4be Poul Sander 2025-02-03 21:28 9461
        return true;
1193b4be Poul Sander 2025-02-03 21:28 9462
    }
1193b4be Poul Sander 2025-02-03 21:28 9463
    return false;
1193b4be Poul Sander 2025-02-03 21:28 9464
}
1193b4be Poul Sander 2025-02-03 21:28 9465
1193b4be Poul Sander 2025-02-03 21:28 9466
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9467
// [SECTION] Widgets: BeginTabBar, EndTabBar, etc.
1193b4be Poul Sander 2025-02-03 21:28 9468
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9469
// - BeginTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9470
// - BeginTabBarEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9471
// - EndTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9472
// - TabBarLayout() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9473
// - TabBarCalcTabID() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9474
// - TabBarCalcMaxTabWidth() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9475
// - TabBarFindTabById() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9476
// - TabBarFindTabByOrder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9477
// - TabBarFindMostRecentlySelectedTabForActiveWindow() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9478
// - TabBarGetCurrentTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9479
// - TabBarGetTabName() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9480
// - TabBarAddTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9481
// - TabBarRemoveTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9482
// - TabBarCloseTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9483
// - TabBarScrollClamp() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9484
// - TabBarScrollToTab() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9485
// - TabBarQueueFocus() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9486
// - TabBarQueueReorder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9487
// - TabBarProcessReorderFromMousePos() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9488
// - TabBarProcessReorder() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9489
// - TabBarScrollingButtons() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9490
// - TabBarTabListPopupButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 9491
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 9492
1193b4be Poul Sander 2025-02-03 21:28 9493
struct ImGuiTabBarSection
1193b4be Poul Sander 2025-02-03 21:28 9494
{
1193b4be Poul Sander 2025-02-03 21:28 9495
    int                 TabCount;               // Number of tabs in this section.
1193b4be Poul Sander 2025-02-03 21:28 9496
    float               Width;                  // Sum of width of tabs in this section (after shrinking down)
4e9e2555 Poul Sander 2025-10-05 10:15 9497
    float               WidthAfterShrinkMinWidth;
1193b4be Poul Sander 2025-02-03 21:28 9498
    float               Spacing;                // Horizontal spacing at the end of the section.
1193b4be Poul Sander 2025-02-03 21:28 9499
1193b4be Poul Sander 2025-02-03 21:28 9500
    ImGuiTabBarSection() { memset(this, 0, sizeof(*this)); }
1193b4be Poul Sander 2025-02-03 21:28 9501
};
1193b4be Poul Sander 2025-02-03 21:28 9502
1193b4be Poul Sander 2025-02-03 21:28 9503
namespace ImGui
1193b4be Poul Sander 2025-02-03 21:28 9504
{
1193b4be Poul Sander 2025-02-03 21:28 9505
    static void             TabBarLayout(ImGuiTabBar* tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9506
    static ImU32            TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window);
1193b4be Poul Sander 2025-02-03 21:28 9507
    static float            TabBarCalcMaxTabWidth();
1193b4be Poul Sander 2025-02-03 21:28 9508
    static float            TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling);
1193b4be Poul Sander 2025-02-03 21:28 9509
    static void             TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections);
1193b4be Poul Sander 2025-02-03 21:28 9510
    static ImGuiTabItem*    TabBarScrollingButtons(ImGuiTabBar* tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9511
    static ImGuiTabItem*    TabBarTabListPopupButton(ImGuiTabBar* tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9512
}
1193b4be Poul Sander 2025-02-03 21:28 9513
1193b4be Poul Sander 2025-02-03 21:28 9514
ImGuiTabBar::ImGuiTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9515
{
1193b4be Poul Sander 2025-02-03 21:28 9516
    memset(this, 0, sizeof(*this));
1193b4be Poul Sander 2025-02-03 21:28 9517
    CurrFrameVisible = PrevFrameVisible = -1;
1193b4be Poul Sander 2025-02-03 21:28 9518
    LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9519
}
1193b4be Poul Sander 2025-02-03 21:28 9520
1193b4be Poul Sander 2025-02-03 21:28 9521
static inline int TabItemGetSectionIdx(const ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 9522
{
1193b4be Poul Sander 2025-02-03 21:28 9523
    return (tab->Flags & ImGuiTabItemFlags_Leading) ? 0 : (tab->Flags & ImGuiTabItemFlags_Trailing) ? 2 : 1;
1193b4be Poul Sander 2025-02-03 21:28 9524
}
1193b4be Poul Sander 2025-02-03 21:28 9525
1193b4be Poul Sander 2025-02-03 21:28 9526
static int IMGUI_CDECL TabItemComparerBySection(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 9527
{
1193b4be Poul Sander 2025-02-03 21:28 9528
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 9529
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 9530
    const int a_section = TabItemGetSectionIdx(a);
1193b4be Poul Sander 2025-02-03 21:28 9531
    const int b_section = TabItemGetSectionIdx(b);
1193b4be Poul Sander 2025-02-03 21:28 9532
    if (a_section != b_section)
1193b4be Poul Sander 2025-02-03 21:28 9533
        return a_section - b_section;
1193b4be Poul Sander 2025-02-03 21:28 9534
    return (int)(a->IndexDuringLayout - b->IndexDuringLayout);
1193b4be Poul Sander 2025-02-03 21:28 9535
}
1193b4be Poul Sander 2025-02-03 21:28 9536
1193b4be Poul Sander 2025-02-03 21:28 9537
static int IMGUI_CDECL TabItemComparerByBeginOrder(const void* lhs, const void* rhs)
1193b4be Poul Sander 2025-02-03 21:28 9538
{
1193b4be Poul Sander 2025-02-03 21:28 9539
    const ImGuiTabItem* a = (const ImGuiTabItem*)lhs;
1193b4be Poul Sander 2025-02-03 21:28 9540
    const ImGuiTabItem* b = (const ImGuiTabItem*)rhs;
1193b4be Poul Sander 2025-02-03 21:28 9541
    return (int)(a->BeginOrder - b->BeginOrder);
1193b4be Poul Sander 2025-02-03 21:28 9542
}
1193b4be Poul Sander 2025-02-03 21:28 9543
1193b4be Poul Sander 2025-02-03 21:28 9544
static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiPtrOrIndex& ref)
1193b4be Poul Sander 2025-02-03 21:28 9545
{
1193b4be Poul Sander 2025-02-03 21:28 9546
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9547
    return ref.Ptr ? (ImGuiTabBar*)ref.Ptr : g.TabBars.GetByIndex(ref.Index);
1193b4be Poul Sander 2025-02-03 21:28 9548
}
1193b4be Poul Sander 2025-02-03 21:28 9549
1193b4be Poul Sander 2025-02-03 21:28 9550
static ImGuiPtrOrIndex GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9551
{
1193b4be Poul Sander 2025-02-03 21:28 9552
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9553
    if (g.TabBars.Contains(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9554
        return ImGuiPtrOrIndex(g.TabBars.GetIndex(tab_bar));
1193b4be Poul Sander 2025-02-03 21:28 9555
    return ImGuiPtrOrIndex(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9556
}
1193b4be Poul Sander 2025-02-03 21:28 9557
4e9e2555 Poul Sander 2025-10-05 10:15 9558
ImGuiTabBar* ImGui::TabBarFindByID(ImGuiID id)
4e9e2555 Poul Sander 2025-10-05 10:15 9559
{
4e9e2555 Poul Sander 2025-10-05 10:15 9560
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 9561
    return g.TabBars.GetByKey(id);
4e9e2555 Poul Sander 2025-10-05 10:15 9562
}
4e9e2555 Poul Sander 2025-10-05 10:15 9563
4e9e2555 Poul Sander 2025-10-05 10:15 9564
// Remove TabBar data (currently only used by TestEngine)
4e9e2555 Poul Sander 2025-10-05 10:15 9565
void    ImGui::TabBarRemove(ImGuiTabBar* tab_bar)
4e9e2555 Poul Sander 2025-10-05 10:15 9566
{
4e9e2555 Poul Sander 2025-10-05 10:15 9567
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 9568
    g.TabBars.Remove(tab_bar->ID, tab_bar);
4e9e2555 Poul Sander 2025-10-05 10:15 9569
}
4e9e2555 Poul Sander 2025-10-05 10:15 9570
1193b4be Poul Sander 2025-02-03 21:28 9571
bool    ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9572
{
1193b4be Poul Sander 2025-02-03 21:28 9573
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9574
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9575
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9576
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9577
1193b4be Poul Sander 2025-02-03 21:28 9578
    ImGuiID id = window->GetID(str_id);
1193b4be Poul Sander 2025-02-03 21:28 9579
    ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id);
1193b4be Poul Sander 2025-02-03 21:28 9580
    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);
1193b4be Poul Sander 2025-02-03 21:28 9581
    tab_bar->ID = id;
4e9e2555 Poul Sander 2025-10-05 10:15 9582
    tab_bar->SeparatorMinX = tab_bar_bb.Min.x - IM_TRUNC(window->WindowPadding.x * 0.5f);
4e9e2555 Poul Sander 2025-10-05 10:15 9583
    tab_bar->SeparatorMaxX = tab_bar_bb.Max.x + IM_TRUNC(window->WindowPadding.x * 0.5f);
1193b4be Poul Sander 2025-02-03 21:28 9584
    //if (g.NavWindow && IsWindowChildOf(g.NavWindow, window, false, false))
1193b4be Poul Sander 2025-02-03 21:28 9585
    flags |= ImGuiTabBarFlags_IsFocused;
1193b4be Poul Sander 2025-02-03 21:28 9586
    return BeginTabBarEx(tab_bar, tab_bar_bb, flags);
1193b4be Poul Sander 2025-02-03 21:28 9587
}
1193b4be Poul Sander 2025-02-03 21:28 9588
1193b4be Poul Sander 2025-02-03 21:28 9589
bool    ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 9590
{
1193b4be Poul Sander 2025-02-03 21:28 9591
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9592
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9593
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9594
        return false;
1193b4be Poul Sander 2025-02-03 21:28 9595
1193b4be Poul Sander 2025-02-03 21:28 9596
    IM_ASSERT(tab_bar->ID != 0);
1193b4be Poul Sander 2025-02-03 21:28 9597
    if ((flags & ImGuiTabBarFlags_DockNode) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9598
        PushOverrideID(tab_bar->ID);
1193b4be Poul Sander 2025-02-03 21:28 9599
1193b4be Poul Sander 2025-02-03 21:28 9600
    // Add to stack
1193b4be Poul Sander 2025-02-03 21:28 9601
    g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar));
1193b4be Poul Sander 2025-02-03 21:28 9602
    g.CurrentTabBar = tab_bar;
1193b4be Poul Sander 2025-02-03 21:28 9603
    tab_bar->Window = window;
1193b4be Poul Sander 2025-02-03 21:28 9604
1193b4be Poul Sander 2025-02-03 21:28 9605
    // Append with multiple BeginTabBar()/EndTabBar() pairs.
1193b4be Poul Sander 2025-02-03 21:28 9606
    tab_bar->BackupCursorPos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9607
    if (tab_bar->CurrFrameVisible == g.FrameCount)
1193b4be Poul Sander 2025-02-03 21:28 9608
    {
1193b4be Poul Sander 2025-02-03 21:28 9609
        window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
1193b4be Poul Sander 2025-02-03 21:28 9610
        tab_bar->BeginCount++;
1193b4be Poul Sander 2025-02-03 21:28 9611
        return true;
1193b4be Poul Sander 2025-02-03 21:28 9612
    }
1193b4be Poul Sander 2025-02-03 21:28 9613
1193b4be Poul Sander 2025-02-03 21:28 9614
    // Ensure correct ordering when toggling ImGuiTabBarFlags_Reorderable flag, or when a new tab was added while being not reorderable
1193b4be Poul Sander 2025-02-03 21:28 9615
    if ((flags & ImGuiTabBarFlags_Reorderable) != (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (tab_bar->TabsAddedNew && !(flags & ImGuiTabBarFlags_Reorderable)))
1193b4be Poul Sander 2025-02-03 21:28 9616
        if ((flags & ImGuiTabBarFlags_DockNode) == 0) // FIXME: TabBar with DockNode can now be hybrid
1193b4be Poul Sander 2025-02-03 21:28 9617
            ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByBeginOrder);
1193b4be Poul Sander 2025-02-03 21:28 9618
    tab_bar->TabsAddedNew = false;
1193b4be Poul Sander 2025-02-03 21:28 9619
1193b4be Poul Sander 2025-02-03 21:28 9620
    // Flags
1193b4be Poul Sander 2025-02-03 21:28 9621
    if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9622
        flags |= ImGuiTabBarFlags_FittingPolicyDefault_;
1193b4be Poul Sander 2025-02-03 21:28 9623
1193b4be Poul Sander 2025-02-03 21:28 9624
    tab_bar->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 9625
    tab_bar->BarRect = tab_bar_bb;
1193b4be Poul Sander 2025-02-03 21:28 9626
    tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab()
1193b4be Poul Sander 2025-02-03 21:28 9627
    tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible;
1193b4be Poul Sander 2025-02-03 21:28 9628
    tab_bar->CurrFrameVisible = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 9629
    tab_bar->PrevTabsContentsHeight = tab_bar->CurrTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9630
    tab_bar->CurrTabsContentsHeight = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9631
    tab_bar->ItemSpacingY = g.Style.ItemSpacing.y;
1193b4be Poul Sander 2025-02-03 21:28 9632
    tab_bar->FramePadding = g.Style.FramePadding;
1193b4be Poul Sander 2025-02-03 21:28 9633
    tab_bar->TabsActiveCount = 0;
1193b4be Poul Sander 2025-02-03 21:28 9634
    tab_bar->LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9635
    tab_bar->BeginCount = 1;
1193b4be Poul Sander 2025-02-03 21:28 9636
1193b4be Poul Sander 2025-02-03 21:28 9637
    // Set cursor pos in a way which only be used in the off-chance the user erroneously submits item before BeginTabItem(): items will overlap
1193b4be Poul Sander 2025-02-03 21:28 9638
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.y + tab_bar->ItemSpacingY);
1193b4be Poul Sander 2025-02-03 21:28 9639
1193b4be Poul Sander 2025-02-03 21:28 9640
    // Draw separator
1193b4be Poul Sander 2025-02-03 21:28 9641
    // (it would be misleading to draw this in EndTabBar() suggesting that it may be drawn over tabs, as tab bar are appendable)
1193b4be Poul Sander 2025-02-03 21:28 9642
    const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabSelected : ImGuiCol_TabDimmedSelected);
1193b4be Poul Sander 2025-02-03 21:28 9643
    if (g.Style.TabBarBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9644
    {
1193b4be Poul Sander 2025-02-03 21:28 9645
        const float y = tab_bar->BarRect.Max.y;
1193b4be Poul Sander 2025-02-03 21:28 9646
        window->DrawList->AddRectFilled(ImVec2(tab_bar->SeparatorMinX, y - g.Style.TabBarBorderSize), ImVec2(tab_bar->SeparatorMaxX, y), col);
1193b4be Poul Sander 2025-02-03 21:28 9647
    }
1193b4be Poul Sander 2025-02-03 21:28 9648
    return true;
1193b4be Poul Sander 2025-02-03 21:28 9649
}
1193b4be Poul Sander 2025-02-03 21:28 9650
1193b4be Poul Sander 2025-02-03 21:28 9651
void    ImGui::EndTabBar()
1193b4be Poul Sander 2025-02-03 21:28 9652
{
1193b4be Poul Sander 2025-02-03 21:28 9653
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9654
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9655
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 9656
        return;
1193b4be Poul Sander 2025-02-03 21:28 9657
1193b4be Poul Sander 2025-02-03 21:28 9658
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 9659
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 9660
    {
1193b4be Poul Sander 2025-02-03 21:28 9661
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Mismatched BeginTabBar()/EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 9662
        return;
1193b4be Poul Sander 2025-02-03 21:28 9663
    }
1193b4be Poul Sander 2025-02-03 21:28 9664
1193b4be Poul Sander 2025-02-03 21:28 9665
    // Fallback in case no TabItem have been submitted
1193b4be Poul Sander 2025-02-03 21:28 9666
    if (tab_bar->WantLayout)
1193b4be Poul Sander 2025-02-03 21:28 9667
        TabBarLayout(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9668
1193b4be Poul Sander 2025-02-03 21:28 9669
    // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed().
1193b4be Poul Sander 2025-02-03 21:28 9670
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 9671
    if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing)
1193b4be Poul Sander 2025-02-03 21:28 9672
    {
1193b4be Poul Sander 2025-02-03 21:28 9673
        tab_bar->CurrTabsContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, tab_bar->CurrTabsContentsHeight);
1193b4be Poul Sander 2025-02-03 21:28 9674
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->CurrTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9675
    }
1193b4be Poul Sander 2025-02-03 21:28 9676
    else
1193b4be Poul Sander 2025-02-03 21:28 9677
    {
1193b4be Poul Sander 2025-02-03 21:28 9678
        window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->PrevTabsContentsHeight;
1193b4be Poul Sander 2025-02-03 21:28 9679
    }
1193b4be Poul Sander 2025-02-03 21:28 9680
    if (tab_bar->BeginCount > 1)
1193b4be Poul Sander 2025-02-03 21:28 9681
        window->DC.CursorPos = tab_bar->BackupCursorPos;
1193b4be Poul Sander 2025-02-03 21:28 9682
1193b4be Poul Sander 2025-02-03 21:28 9683
    tab_bar->LastTabItemIdx = -1;
1193b4be Poul Sander 2025-02-03 21:28 9684
    if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9685
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 9686
1193b4be Poul Sander 2025-02-03 21:28 9687
    g.CurrentTabBarStack.pop_back();
1193b4be Poul Sander 2025-02-03 21:28 9688
    g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back());
1193b4be Poul Sander 2025-02-03 21:28 9689
}
1193b4be Poul Sander 2025-02-03 21:28 9690
1193b4be Poul Sander 2025-02-03 21:28 9691
// Scrolling happens only in the central section (leading/trailing sections are not scrolling)
1193b4be Poul Sander 2025-02-03 21:28 9692
static float TabBarCalcScrollableWidth(ImGuiTabBar* tab_bar, ImGuiTabBarSection* sections)
1193b4be Poul Sander 2025-02-03 21:28 9693
{
1193b4be Poul Sander 2025-02-03 21:28 9694
    return tab_bar->BarRect.GetWidth() - sections[0].Width - sections[2].Width - sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9695
}
1193b4be Poul Sander 2025-02-03 21:28 9696
1193b4be Poul Sander 2025-02-03 21:28 9697
// This is called only once a frame before by the first call to ItemTab()
1193b4be Poul Sander 2025-02-03 21:28 9698
// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions.
1193b4be Poul Sander 2025-02-03 21:28 9699
static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9700
{
1193b4be Poul Sander 2025-02-03 21:28 9701
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9702
    tab_bar->WantLayout = false;
1193b4be Poul Sander 2025-02-03 21:28 9703
4e9e2555 Poul Sander 2025-10-05 10:15 9704
    // Track selected tab when resizing our parent down
4e9e2555 Poul Sander 2025-10-05 10:15 9705
    const bool scroll_to_selected_tab = (tab_bar->BarRectPrevWidth > tab_bar->BarRect.GetWidth());
4e9e2555 Poul Sander 2025-10-05 10:15 9706
    tab_bar->BarRectPrevWidth = tab_bar->BarRect.GetWidth();
4e9e2555 Poul Sander 2025-10-05 10:15 9707
1193b4be Poul Sander 2025-02-03 21:28 9708
    // Garbage collect by compacting list
1193b4be Poul Sander 2025-02-03 21:28 9709
    // Detect if we need to sort out tab list (e.g. in rare case where a tab changed section)
1193b4be Poul Sander 2025-02-03 21:28 9710
    int tab_dst_n = 0;
1193b4be Poul Sander 2025-02-03 21:28 9711
    bool need_sort_by_section = false;
1193b4be Poul Sander 2025-02-03 21:28 9712
    ImGuiTabBarSection sections[3]; // Layout sections: Leading, Central, Trailing
1193b4be Poul Sander 2025-02-03 21:28 9713
    for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++)
1193b4be Poul Sander 2025-02-03 21:28 9714
    {
1193b4be Poul Sander 2025-02-03 21:28 9715
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n];
1193b4be Poul Sander 2025-02-03 21:28 9716
        if (tab->LastFrameVisible < tab_bar->PrevFrameVisible || tab->WantClose)
1193b4be Poul Sander 2025-02-03 21:28 9717
        {
1193b4be Poul Sander 2025-02-03 21:28 9718
            // Remove tab
1193b4be Poul Sander 2025-02-03 21:28 9719
            if (tab_bar->VisibleTabId == tab->ID) { tab_bar->VisibleTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9720
            if (tab_bar->SelectedTabId == tab->ID) { tab_bar->SelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9721
            if (tab_bar->NextSelectedTabId == tab->ID) { tab_bar->NextSelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 9722
            continue;
1193b4be Poul Sander 2025-02-03 21:28 9723
        }
1193b4be Poul Sander 2025-02-03 21:28 9724
        if (tab_dst_n != tab_src_n)
1193b4be Poul Sander 2025-02-03 21:28 9725
            tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n];
1193b4be Poul Sander 2025-02-03 21:28 9726
1193b4be Poul Sander 2025-02-03 21:28 9727
        tab = &tab_bar->Tabs[tab_dst_n];
1193b4be Poul Sander 2025-02-03 21:28 9728
        tab->IndexDuringLayout = (ImS16)tab_dst_n;
1193b4be Poul Sander 2025-02-03 21:28 9729
1193b4be Poul Sander 2025-02-03 21:28 9730
        // We will need sorting if tabs have changed section (e.g. moved from one of Leading/Central/Trailing to another)
1193b4be Poul Sander 2025-02-03 21:28 9731
        int curr_tab_section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9732
        if (tab_dst_n > 0)
1193b4be Poul Sander 2025-02-03 21:28 9733
        {
1193b4be Poul Sander 2025-02-03 21:28 9734
            ImGuiTabItem* prev_tab = &tab_bar->Tabs[tab_dst_n - 1];
1193b4be Poul Sander 2025-02-03 21:28 9735
            int prev_tab_section_n = TabItemGetSectionIdx(prev_tab);
1193b4be Poul Sander 2025-02-03 21:28 9736
            if (curr_tab_section_n == 0 && prev_tab_section_n != 0)
1193b4be Poul Sander 2025-02-03 21:28 9737
                need_sort_by_section = true;
1193b4be Poul Sander 2025-02-03 21:28 9738
            if (prev_tab_section_n == 2 && curr_tab_section_n != 2)
1193b4be Poul Sander 2025-02-03 21:28 9739
                need_sort_by_section = true;
1193b4be Poul Sander 2025-02-03 21:28 9740
        }
1193b4be Poul Sander 2025-02-03 21:28 9741
1193b4be Poul Sander 2025-02-03 21:28 9742
        sections[curr_tab_section_n].TabCount++;
1193b4be Poul Sander 2025-02-03 21:28 9743
        tab_dst_n++;
1193b4be Poul Sander 2025-02-03 21:28 9744
    }
1193b4be Poul Sander 2025-02-03 21:28 9745
    if (tab_bar->Tabs.Size != tab_dst_n)
1193b4be Poul Sander 2025-02-03 21:28 9746
        tab_bar->Tabs.resize(tab_dst_n);
1193b4be Poul Sander 2025-02-03 21:28 9747
1193b4be Poul Sander 2025-02-03 21:28 9748
    if (need_sort_by_section)
1193b4be Poul Sander 2025-02-03 21:28 9749
        ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerBySection);
1193b4be Poul Sander 2025-02-03 21:28 9750
1193b4be Poul Sander 2025-02-03 21:28 9751
    // Calculate spacing between sections
1193b4be Poul Sander 2025-02-03 21:28 9752
    sections[0].Spacing = sections[0].TabCount > 0 && (sections[1].TabCount + sections[2].TabCount) > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9753
    sections[1].Spacing = sections[1].TabCount > 0 && sections[2].TabCount > 0 ? g.Style.ItemInnerSpacing.x : 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9754
1193b4be Poul Sander 2025-02-03 21:28 9755
    // Setup next selected tab
1193b4be Poul Sander 2025-02-03 21:28 9756
    ImGuiID scroll_to_tab_id = 0;
1193b4be Poul Sander 2025-02-03 21:28 9757
    if (tab_bar->NextSelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9758
    {
1193b4be Poul Sander 2025-02-03 21:28 9759
        tab_bar->SelectedTabId = tab_bar->NextSelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9760
        tab_bar->NextSelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9761
        scroll_to_tab_id = tab_bar->SelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9762
    }
1193b4be Poul Sander 2025-02-03 21:28 9763
1193b4be Poul Sander 2025-02-03 21:28 9764
    // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot).
1193b4be Poul Sander 2025-02-03 21:28 9765
    if (tab_bar->ReorderRequestTabId != 0)
1193b4be Poul Sander 2025-02-03 21:28 9766
    {
1193b4be Poul Sander 2025-02-03 21:28 9767
        if (TabBarProcessReorder(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9768
            if (tab_bar->ReorderRequestTabId == tab_bar->SelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9769
                scroll_to_tab_id = tab_bar->ReorderRequestTabId;
1193b4be Poul Sander 2025-02-03 21:28 9770
        tab_bar->ReorderRequestTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9771
    }
1193b4be Poul Sander 2025-02-03 21:28 9772
1193b4be Poul Sander 2025-02-03 21:28 9773
    // Tab List Popup (will alter tab_bar->BarRect and therefore the available width!)
1193b4be Poul Sander 2025-02-03 21:28 9774
    const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_TabListPopupButton) != 0;
1193b4be Poul Sander 2025-02-03 21:28 9775
    if (tab_list_popup_button)
1193b4be Poul Sander 2025-02-03 21:28 9776
        if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Min.x!
1193b4be Poul Sander 2025-02-03 21:28 9777
            scroll_to_tab_id = tab_bar->SelectedTabId = tab_to_select->ID;
1193b4be Poul Sander 2025-02-03 21:28 9778
1193b4be Poul Sander 2025-02-03 21:28 9779
    // Leading/Trailing tabs will be shrink only if central one aren't visible anymore, so layout the shrink data as: leading, trailing, central
1193b4be Poul Sander 2025-02-03 21:28 9780
    // (whereas our tabs are stored as: leading, central, trailing)
1193b4be Poul Sander 2025-02-03 21:28 9781
    int shrink_buffer_indexes[3] = { 0, sections[0].TabCount + sections[2].TabCount, sections[0].TabCount };
1193b4be Poul Sander 2025-02-03 21:28 9782
    g.ShrinkWidthBuffer.resize(tab_bar->Tabs.Size);
1193b4be Poul Sander 2025-02-03 21:28 9783
4e9e2555 Poul Sander 2025-10-05 10:15 9784
    // Minimum shrink width
4e9e2555 Poul Sander 2025-10-05 10:15 9785
    const float shrink_min_width = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed) ? g.Style.TabMinWidthShrink : 1.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 9786
1193b4be Poul Sander 2025-02-03 21:28 9787
    // Compute ideal tabs widths + store them into shrink buffer
1193b4be Poul Sander 2025-02-03 21:28 9788
    ImGuiTabItem* most_recently_selected_tab = NULL;
1193b4be Poul Sander 2025-02-03 21:28 9789
    int curr_section_n = -1;
1193b4be Poul Sander 2025-02-03 21:28 9790
    bool found_selected_tab_id = false;
1193b4be Poul Sander 2025-02-03 21:28 9791
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9792
    {
1193b4be Poul Sander 2025-02-03 21:28 9793
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9794
        IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible);
1193b4be Poul Sander 2025-02-03 21:28 9795
1193b4be Poul Sander 2025-02-03 21:28 9796
        if ((most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) && !(tab->Flags & ImGuiTabItemFlags_Button))
1193b4be Poul Sander 2025-02-03 21:28 9797
            most_recently_selected_tab = tab;
1193b4be Poul Sander 2025-02-03 21:28 9798
        if (tab->ID == tab_bar->SelectedTabId)
1193b4be Poul Sander 2025-02-03 21:28 9799
            found_selected_tab_id = true;
1193b4be Poul Sander 2025-02-03 21:28 9800
        if (scroll_to_tab_id == 0 && g.NavJustMovedToId == tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 9801
            scroll_to_tab_id = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9802
1193b4be Poul Sander 2025-02-03 21:28 9803
        // Refresh tab width immediately, otherwise changes of style e.g. style.FramePadding.x would noticeably lag in the tab bar.
1193b4be Poul Sander 2025-02-03 21:28 9804
        // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
1193b4be Poul Sander 2025-02-03 21:28 9805
        // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
1193b4be Poul Sander 2025-02-03 21:28 9806
        const char* tab_name = TabBarGetTabName(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 9807
        const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
1193b4be Poul Sander 2025-02-03 21:28 9808
        tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
4e9e2555 Poul Sander 2025-10-05 10:15 9809
        if ((tab->Flags & ImGuiTabItemFlags_Button) == 0)
4e9e2555 Poul Sander 2025-10-05 10:15 9810
            tab->ContentWidth = ImMax(tab->ContentWidth, g.Style.TabMinWidthBase);
1193b4be Poul Sander 2025-02-03 21:28 9811
1193b4be Poul Sander 2025-02-03 21:28 9812
        int section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9813
        ImGuiTabBarSection* section = &sections[section_n];
1193b4be Poul Sander 2025-02-03 21:28 9814
        section->Width += tab->ContentWidth + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
4e9e2555 Poul Sander 2025-10-05 10:15 9815
        section->WidthAfterShrinkMinWidth += ImMin(tab->ContentWidth, shrink_min_width) + (section_n == curr_section_n ? g.Style.ItemInnerSpacing.x : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9816
        curr_section_n = section_n;
1193b4be Poul Sander 2025-02-03 21:28 9817
1193b4be Poul Sander 2025-02-03 21:28 9818
        // Store data so we can build an array sorted by width if we need to shrink tabs down
1193b4be Poul Sander 2025-02-03 21:28 9819
        IM_MSVC_WARNING_SUPPRESS(6385);
1193b4be Poul Sander 2025-02-03 21:28 9820
        ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
1193b4be Poul Sander 2025-02-03 21:28 9821
        shrink_width_item->Index = tab_n;
1193b4be Poul Sander 2025-02-03 21:28 9822
        shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
1193b4be Poul Sander 2025-02-03 21:28 9823
        tab->Width = ImMax(tab->ContentWidth, 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 9824
    }
1193b4be Poul Sander 2025-02-03 21:28 9825
1193b4be Poul Sander 2025-02-03 21:28 9826
    // Compute total ideal width (used for e.g. auto-resizing a window)
4e9e2555 Poul Sander 2025-10-05 10:15 9827
    float width_all_tabs_after_min_width_shrink = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9828
    tab_bar->WidthAllTabsIdeal = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9829
    for (int section_n = 0; section_n < 3; section_n++)
4e9e2555 Poul Sander 2025-10-05 10:15 9830
    {
1193b4be Poul Sander 2025-02-03 21:28 9831
        tab_bar->WidthAllTabsIdeal += sections[section_n].Width + sections[section_n].Spacing;
4e9e2555 Poul Sander 2025-10-05 10:15 9832
        width_all_tabs_after_min_width_shrink += sections[section_n].WidthAfterShrinkMinWidth + sections[section_n].Spacing;
4e9e2555 Poul Sander 2025-10-05 10:15 9833
    }
1193b4be Poul Sander 2025-02-03 21:28 9834
1193b4be Poul Sander 2025-02-03 21:28 9835
    // Horizontal scrolling buttons
4e9e2555 Poul Sander 2025-10-05 10:15 9836
    // Important: note that TabBarScrollButtons() will alter BarRect.Max.x.
4e9e2555 Poul Sander 2025-10-05 10:15 9837
    const bool can_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
4e9e2555 Poul Sander 2025-10-05 10:15 9838
    const float width_all_tabs_to_use_for_scroll = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll) ? tab_bar->WidthAllTabs : width_all_tabs_after_min_width_shrink;
4e9e2555 Poul Sander 2025-10-05 10:15 9839
    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);
4e9e2555 Poul Sander 2025-10-05 10:15 9840
    if (tab_bar->ScrollButtonEnabled)
1193b4be Poul Sander 2025-02-03 21:28 9841
        if (ImGuiTabItem* scroll_and_select_tab = TabBarScrollingButtons(tab_bar))
1193b4be Poul Sander 2025-02-03 21:28 9842
        {
1193b4be Poul Sander 2025-02-03 21:28 9843
            scroll_to_tab_id = scroll_and_select_tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9844
            if ((scroll_and_select_tab->Flags & ImGuiTabItemFlags_Button) == 0)
1193b4be Poul Sander 2025-02-03 21:28 9845
                tab_bar->SelectedTabId = scroll_to_tab_id;
1193b4be Poul Sander 2025-02-03 21:28 9846
        }
4e9e2555 Poul Sander 2025-10-05 10:15 9847
    if (scroll_to_tab_id == 0 && scroll_to_selected_tab)
4e9e2555 Poul Sander 2025-10-05 10:15 9848
        scroll_to_tab_id = tab_bar->SelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9849
1193b4be Poul Sander 2025-02-03 21:28 9850
    // Shrink widths if full tabs don't fit in their allocated space
1193b4be Poul Sander 2025-02-03 21:28 9851
    float section_0_w = sections[0].Width + sections[0].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9852
    float section_1_w = sections[1].Width + sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9853
    float section_2_w = sections[2].Width + sections[2].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9854
    bool central_section_is_visible = (section_0_w + section_2_w) < tab_bar->BarRect.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 9855
    float width_excess;
1193b4be Poul Sander 2025-02-03 21:28 9856
    if (central_section_is_visible)
1193b4be Poul Sander 2025-02-03 21:28 9857
        width_excess = ImMax(section_1_w - (tab_bar->BarRect.GetWidth() - section_0_w - section_2_w), 0.0f); // Excess used to shrink central section
1193b4be Poul Sander 2025-02-03 21:28 9858
    else
1193b4be Poul Sander 2025-02-03 21:28 9859
        width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
1193b4be Poul Sander 2025-02-03 21:28 9860
1193b4be Poul Sander 2025-02-03 21:28 9861
    // With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
4e9e2555 Poul Sander 2025-10-05 10:15 9862
    const bool can_shrink = (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyShrink) || (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyMixed);
4e9e2555 Poul Sander 2025-10-05 10:15 9863
    if (width_excess >= 1.0f && (can_shrink || !central_section_is_visible))
1193b4be Poul Sander 2025-02-03 21:28 9864
    {
1193b4be Poul Sander 2025-02-03 21:28 9865
        int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
1193b4be Poul Sander 2025-02-03 21:28 9866
        int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
4e9e2555 Poul Sander 2025-10-05 10:15 9867
        ShrinkWidths(g.ShrinkWidthBuffer.Data + shrink_data_offset, shrink_data_count, width_excess, shrink_min_width);
1193b4be Poul Sander 2025-02-03 21:28 9868
1193b4be Poul Sander 2025-02-03 21:28 9869
        // Apply shrunk values into tabs and sections
1193b4be Poul Sander 2025-02-03 21:28 9870
        for (int tab_n = shrink_data_offset; tab_n < shrink_data_offset + shrink_data_count; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9871
        {
1193b4be Poul Sander 2025-02-03 21:28 9872
            ImGuiTabItem* tab = &tab_bar->Tabs[g.ShrinkWidthBuffer[tab_n].Index];
1193b4be Poul Sander 2025-02-03 21:28 9873
            float shrinked_width = IM_TRUNC(g.ShrinkWidthBuffer[tab_n].Width);
1193b4be Poul Sander 2025-02-03 21:28 9874
            if (shrinked_width < 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9875
                continue;
1193b4be Poul Sander 2025-02-03 21:28 9876
1193b4be Poul Sander 2025-02-03 21:28 9877
            shrinked_width = ImMax(1.0f, shrinked_width);
1193b4be Poul Sander 2025-02-03 21:28 9878
            int section_n = TabItemGetSectionIdx(tab);
1193b4be Poul Sander 2025-02-03 21:28 9879
            sections[section_n].Width -= (tab->Width - shrinked_width);
1193b4be Poul Sander 2025-02-03 21:28 9880
            tab->Width = shrinked_width;
1193b4be Poul Sander 2025-02-03 21:28 9881
        }
1193b4be Poul Sander 2025-02-03 21:28 9882
    }
1193b4be Poul Sander 2025-02-03 21:28 9883
1193b4be Poul Sander 2025-02-03 21:28 9884
    // Layout all active tabs
1193b4be Poul Sander 2025-02-03 21:28 9885
    int section_tab_index = 0;
1193b4be Poul Sander 2025-02-03 21:28 9886
    float tab_offset = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9887
    tab_bar->WidthAllTabs = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9888
    for (int section_n = 0; section_n < 3; section_n++)
1193b4be Poul Sander 2025-02-03 21:28 9889
    {
1193b4be Poul Sander 2025-02-03 21:28 9890
        ImGuiTabBarSection* section = &sections[section_n];
1193b4be Poul Sander 2025-02-03 21:28 9891
        if (section_n == 2)
1193b4be Poul Sander 2025-02-03 21:28 9892
            tab_offset = ImMin(ImMax(0.0f, tab_bar->BarRect.GetWidth() - section->Width), tab_offset);
1193b4be Poul Sander 2025-02-03 21:28 9893
1193b4be Poul Sander 2025-02-03 21:28 9894
        for (int tab_n = 0; tab_n < section->TabCount; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 9895
        {
1193b4be Poul Sander 2025-02-03 21:28 9896
            ImGuiTabItem* tab = &tab_bar->Tabs[section_tab_index + tab_n];
1193b4be Poul Sander 2025-02-03 21:28 9897
            tab->Offset = tab_offset;
1193b4be Poul Sander 2025-02-03 21:28 9898
            tab->NameOffset = -1;
1193b4be Poul Sander 2025-02-03 21:28 9899
            tab_offset += tab->Width + (tab_n < section->TabCount - 1 ? g.Style.ItemInnerSpacing.x : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9900
        }
1193b4be Poul Sander 2025-02-03 21:28 9901
        tab_bar->WidthAllTabs += ImMax(section->Width + section->Spacing, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 9902
        tab_offset += section->Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9903
        section_tab_index += section->TabCount;
1193b4be Poul Sander 2025-02-03 21:28 9904
    }
1193b4be Poul Sander 2025-02-03 21:28 9905
1193b4be Poul Sander 2025-02-03 21:28 9906
    // Clear name buffers
1193b4be Poul Sander 2025-02-03 21:28 9907
    tab_bar->TabsNames.Buf.resize(0);
1193b4be Poul Sander 2025-02-03 21:28 9908
1193b4be Poul Sander 2025-02-03 21:28 9909
    // If we have lost the selected tab, select the next most recently active one
4e9e2555 Poul Sander 2025-10-05 10:15 9910
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
4e9e2555 Poul Sander 2025-10-05 10:15 9911
    if (found_selected_tab_id == false && !tab_bar_appearing)
1193b4be Poul Sander 2025-02-03 21:28 9912
        tab_bar->SelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 9913
    if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL)
1193b4be Poul Sander 2025-02-03 21:28 9914
        scroll_to_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 9915
1193b4be Poul Sander 2025-02-03 21:28 9916
    // Lock in visible tab
1193b4be Poul Sander 2025-02-03 21:28 9917
    tab_bar->VisibleTabId = tab_bar->SelectedTabId;
1193b4be Poul Sander 2025-02-03 21:28 9918
    tab_bar->VisibleTabWasSubmitted = false;
1193b4be Poul Sander 2025-02-03 21:28 9919
1193b4be Poul Sander 2025-02-03 21:28 9920
    // CTRL+TAB can override visible tab temporarily
1193b4be Poul Sander 2025-02-03 21:28 9921
    if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 9922
        tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
1193b4be Poul Sander 2025-02-03 21:28 9923
1193b4be Poul Sander 2025-02-03 21:28 9924
    // Apply request requests
1193b4be Poul Sander 2025-02-03 21:28 9925
    if (scroll_to_tab_id != 0)
1193b4be Poul Sander 2025-02-03 21:28 9926
        TabBarScrollToTab(tab_bar, scroll_to_tab_id, sections);
4e9e2555 Poul Sander 2025-10-05 10:15 9927
    else if (tab_bar->ScrollButtonEnabled && IsMouseHoveringRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, true) && IsWindowContentHoverable(g.CurrentWindow))
1193b4be Poul Sander 2025-02-03 21:28 9928
    {
1193b4be Poul Sander 2025-02-03 21:28 9929
        const float wheel = g.IO.MouseWheelRequestAxisSwap ? g.IO.MouseWheel : g.IO.MouseWheelH;
1193b4be Poul Sander 2025-02-03 21:28 9930
        const ImGuiKey wheel_key = g.IO.MouseWheelRequestAxisSwap ? ImGuiKey_MouseWheelY : ImGuiKey_MouseWheelX;
1193b4be Poul Sander 2025-02-03 21:28 9931
        if (TestKeyOwner(wheel_key, tab_bar->ID) && wheel != 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 9932
        {
1193b4be Poul Sander 2025-02-03 21:28 9933
            const float scroll_step = wheel * TabBarCalcScrollableWidth(tab_bar, sections) / 3.0f;
1193b4be Poul Sander 2025-02-03 21:28 9934
            tab_bar->ScrollingTargetDistToVisibility = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9935
            tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget - scroll_step);
1193b4be Poul Sander 2025-02-03 21:28 9936
        }
1193b4be Poul Sander 2025-02-03 21:28 9937
        SetKeyOwner(wheel_key, tab_bar->ID);
1193b4be Poul Sander 2025-02-03 21:28 9938
    }
1193b4be Poul Sander 2025-02-03 21:28 9939
1193b4be Poul Sander 2025-02-03 21:28 9940
    // Update scrolling
1193b4be Poul Sander 2025-02-03 21:28 9941
    tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim);
1193b4be Poul Sander 2025-02-03 21:28 9942
    tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget);
1193b4be Poul Sander 2025-02-03 21:28 9943
    if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget)
1193b4be Poul Sander 2025-02-03 21:28 9944
    {
1193b4be Poul Sander 2025-02-03 21:28 9945
        // Scrolling speed adjust itself so we can always reach our target in 1/3 seconds.
1193b4be Poul Sander 2025-02-03 21:28 9946
        // Teleport if we are aiming far off the visible line
1193b4be Poul Sander 2025-02-03 21:28 9947
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, 70.0f * g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 9948
        tab_bar->ScrollingSpeed = ImMax(tab_bar->ScrollingSpeed, ImFabs(tab_bar->ScrollingTarget - tab_bar->ScrollingAnim) / 0.3f);
1193b4be Poul Sander 2025-02-03 21:28 9949
        const bool teleport = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) || (tab_bar->ScrollingTargetDistToVisibility > 10.0f * g.FontSize);
1193b4be Poul Sander 2025-02-03 21:28 9950
        tab_bar->ScrollingAnim = teleport ? tab_bar->ScrollingTarget : ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, g.IO.DeltaTime * tab_bar->ScrollingSpeed);
1193b4be Poul Sander 2025-02-03 21:28 9951
    }
1193b4be Poul Sander 2025-02-03 21:28 9952
    else
1193b4be Poul Sander 2025-02-03 21:28 9953
    {
1193b4be Poul Sander 2025-02-03 21:28 9954
        tab_bar->ScrollingSpeed = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 9955
    }
1193b4be Poul Sander 2025-02-03 21:28 9956
    tab_bar->ScrollingRectMinX = tab_bar->BarRect.Min.x + sections[0].Width + sections[0].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9957
    tab_bar->ScrollingRectMaxX = tab_bar->BarRect.Max.x - sections[2].Width - sections[1].Spacing;
1193b4be Poul Sander 2025-02-03 21:28 9958
1193b4be Poul Sander 2025-02-03 21:28 9959
    // Actual layout in host window (we don't do it in BeginTabBar() so as not to waste an extra frame)
1193b4be Poul Sander 2025-02-03 21:28 9960
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9961
    window->DC.CursorPos = tab_bar->BarRect.Min;
1193b4be Poul Sander 2025-02-03 21:28 9962
    ItemSize(ImVec2(tab_bar->WidthAllTabs, tab_bar->BarRect.GetHeight()), tab_bar->FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 9963
    window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, tab_bar->BarRect.Min.x + tab_bar->WidthAllTabsIdeal);
1193b4be Poul Sander 2025-02-03 21:28 9964
}
1193b4be Poul Sander 2025-02-03 21:28 9965
1193b4be Poul Sander 2025-02-03 21:28 9966
// Dockable windows uses Name/ID in the global namespace. Non-dockable items use the ID stack.
1193b4be Poul Sander 2025-02-03 21:28 9967
static ImU32   ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label, ImGuiWindow* docked_window)
1193b4be Poul Sander 2025-02-03 21:28 9968
{
1193b4be Poul Sander 2025-02-03 21:28 9969
    if (docked_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 9970
    {
1193b4be Poul Sander 2025-02-03 21:28 9971
        IM_UNUSED(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 9972
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 9973
        ImGuiID id = docked_window->TabId;
1193b4be Poul Sander 2025-02-03 21:28 9974
        KeepAliveID(id);
1193b4be Poul Sander 2025-02-03 21:28 9975
        return id;
1193b4be Poul Sander 2025-02-03 21:28 9976
    }
1193b4be Poul Sander 2025-02-03 21:28 9977
    else
1193b4be Poul Sander 2025-02-03 21:28 9978
    {
1193b4be Poul Sander 2025-02-03 21:28 9979
        ImGuiWindow* window = GImGui->CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 9980
        return window->GetID(label);
1193b4be Poul Sander 2025-02-03 21:28 9981
    }
1193b4be Poul Sander 2025-02-03 21:28 9982
}
1193b4be Poul Sander 2025-02-03 21:28 9983
1193b4be Poul Sander 2025-02-03 21:28 9984
static float ImGui::TabBarCalcMaxTabWidth()
1193b4be Poul Sander 2025-02-03 21:28 9985
{
1193b4be Poul Sander 2025-02-03 21:28 9986
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 9987
    return g.FontSize * 20.0f;
1193b4be Poul Sander 2025-02-03 21:28 9988
}
1193b4be Poul Sander 2025-02-03 21:28 9989
1193b4be Poul Sander 2025-02-03 21:28 9990
ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id)
1193b4be Poul Sander 2025-02-03 21:28 9991
{
1193b4be Poul Sander 2025-02-03 21:28 9992
    if (tab_id != 0)
1193b4be Poul Sander 2025-02-03 21:28 9993
        for (int n = 0; n < tab_bar->Tabs.Size; n++)
1193b4be Poul Sander 2025-02-03 21:28 9994
            if (tab_bar->Tabs[n].ID == tab_id)
1193b4be Poul Sander 2025-02-03 21:28 9995
                return &tab_bar->Tabs[n];
1193b4be Poul Sander 2025-02-03 21:28 9996
    return NULL;
1193b4be Poul Sander 2025-02-03 21:28 9997
}
1193b4be Poul Sander 2025-02-03 21:28 9998
1193b4be Poul Sander 2025-02-03 21:28 9999
// Order = visible order, not submission order! (which is tab->BeginOrder)
1193b4be Poul Sander 2025-02-03 21:28 10000
ImGuiTabItem* ImGui::TabBarFindTabByOrder(ImGuiTabBar* tab_bar, int order)
1193b4be Poul Sander 2025-02-03 21:28 10001
{
1193b4be Poul Sander 2025-02-03 21:28 10002
    if (order < 0 || order >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 10003
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 10004
    return &tab_bar->Tabs[order];
1193b4be Poul Sander 2025-02-03 21:28 10005
}
1193b4be Poul Sander 2025-02-03 21:28 10006
1193b4be Poul Sander 2025-02-03 21:28 10007
// FIXME: See references to #2304 in TODO.txt
1193b4be Poul Sander 2025-02-03 21:28 10008
ImGuiTabItem* ImGui::TabBarFindMostRecentlySelectedTabForActiveWindow(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10009
{
1193b4be Poul Sander 2025-02-03 21:28 10010
    ImGuiTabItem* most_recently_selected_tab = NULL;
1193b4be Poul Sander 2025-02-03 21:28 10011
    for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 10012
    {
1193b4be Poul Sander 2025-02-03 21:28 10013
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 10014
        if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected)
1193b4be Poul Sander 2025-02-03 21:28 10015
            if (tab->Window && tab->Window->WasActive)
1193b4be Poul Sander 2025-02-03 21:28 10016
                most_recently_selected_tab = tab;
1193b4be Poul Sander 2025-02-03 21:28 10017
    }
1193b4be Poul Sander 2025-02-03 21:28 10018
    return most_recently_selected_tab;
1193b4be Poul Sander 2025-02-03 21:28 10019
}
1193b4be Poul Sander 2025-02-03 21:28 10020
1193b4be Poul Sander 2025-02-03 21:28 10021
ImGuiTabItem* ImGui::TabBarGetCurrentTab(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10022
{
1193b4be Poul Sander 2025-02-03 21:28 10023
    if (tab_bar->LastTabItemIdx < 0 || tab_bar->LastTabItemIdx >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 10024
        return NULL;
1193b4be Poul Sander 2025-02-03 21:28 10025
    return &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 10026
}
1193b4be Poul Sander 2025-02-03 21:28 10027
1193b4be Poul Sander 2025-02-03 21:28 10028
const char* ImGui::TabBarGetTabName(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 10029
{
1193b4be Poul Sander 2025-02-03 21:28 10030
    if (tab->Window)
1193b4be Poul Sander 2025-02-03 21:28 10031
        return tab->Window->Name;
1193b4be Poul Sander 2025-02-03 21:28 10032
    if (tab->NameOffset == -1)
1193b4be Poul Sander 2025-02-03 21:28 10033
        return "N/A";
1193b4be Poul Sander 2025-02-03 21:28 10034
    IM_ASSERT(tab->NameOffset < tab_bar->TabsNames.Buf.Size);
1193b4be Poul Sander 2025-02-03 21:28 10035
    return tab_bar->TabsNames.Buf.Data + tab->NameOffset;
1193b4be Poul Sander 2025-02-03 21:28 10036
}
1193b4be Poul Sander 2025-02-03 21:28 10037
1193b4be Poul Sander 2025-02-03 21:28 10038
// The purpose of this call is to register tab in advance so we can control their order at the time they appear.
1193b4be Poul Sander 2025-02-03 21:28 10039
// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function.
1193b4be Poul Sander 2025-02-03 21:28 10040
void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiTabItemFlags tab_flags, ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 10041
{
1193b4be Poul Sander 2025-02-03 21:28 10042
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10043
    IM_ASSERT(TabBarFindTabByID(tab_bar, window->TabId) == NULL);
1193b4be Poul Sander 2025-02-03 21:28 10044
    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)
1193b4be Poul Sander 2025-02-03 21:28 10045
1193b4be Poul Sander 2025-02-03 21:28 10046
    if (!window->HasCloseButton)
1193b4be Poul Sander 2025-02-03 21:28 10047
        tab_flags |= ImGuiTabItemFlags_NoCloseButton;       // Set _NoCloseButton immediately because it will be used for first-frame width calculation.
1193b4be Poul Sander 2025-02-03 21:28 10048
1193b4be Poul Sander 2025-02-03 21:28 10049
    ImGuiTabItem new_tab;
1193b4be Poul Sander 2025-02-03 21:28 10050
    new_tab.ID = window->TabId;
1193b4be Poul Sander 2025-02-03 21:28 10051
    new_tab.Flags = tab_flags;
1193b4be Poul Sander 2025-02-03 21:28 10052
    new_tab.LastFrameVisible = tab_bar->CurrFrameVisible;   // Required so BeginTabBar() doesn't ditch the tab
1193b4be Poul Sander 2025-02-03 21:28 10053
    if (new_tab.LastFrameVisible == -1)
1193b4be Poul Sander 2025-02-03 21:28 10054
        new_tab.LastFrameVisible = g.FrameCount - 1;
1193b4be Poul Sander 2025-02-03 21:28 10055
    new_tab.Window = window;                                // Required so tab bar layout can compute the tab width before tab submission
1193b4be Poul Sander 2025-02-03 21:28 10056
    tab_bar->Tabs.push_back(new_tab);
1193b4be Poul Sander 2025-02-03 21:28 10057
}
1193b4be Poul Sander 2025-02-03 21:28 10058
1193b4be Poul Sander 2025-02-03 21:28 10059
// The *TabId fields are already set by the docking system _before_ the actual TabItem was created, so we clear them regardless.
1193b4be Poul Sander 2025-02-03 21:28 10060
void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
1193b4be Poul Sander 2025-02-03 21:28 10061
{
1193b4be Poul Sander 2025-02-03 21:28 10062
    if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
1193b4be Poul Sander 2025-02-03 21:28 10063
        tab_bar->Tabs.erase(tab);
1193b4be Poul Sander 2025-02-03 21:28 10064
    if (tab_bar->VisibleTabId == tab_id)      { tab_bar->VisibleTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 10065
    if (tab_bar->SelectedTabId == tab_id)     { tab_bar->SelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 10066
    if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; }
1193b4be Poul Sander 2025-02-03 21:28 10067
}
1193b4be Poul Sander 2025-02-03 21:28 10068
1193b4be Poul Sander 2025-02-03 21:28 10069
// Called on manual closure attempt
1193b4be Poul Sander 2025-02-03 21:28 10070
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 10071
{
1193b4be Poul Sander 2025-02-03 21:28 10072
    if (tab->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 10073
        return; // A button appended with TabItemButton().
1193b4be Poul Sander 2025-02-03 21:28 10074
1193b4be Poul Sander 2025-02-03 21:28 10075
    if ((tab->Flags & (ImGuiTabItemFlags_UnsavedDocument | ImGuiTabItemFlags_NoAssumedClosure)) == 0)
1193b4be Poul Sander 2025-02-03 21:28 10076
    {
1193b4be Poul Sander 2025-02-03 21:28 10077
        // This will remove a frame of lag for selecting another tab on closure.
1193b4be Poul Sander 2025-02-03 21:28 10078
        // 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
1193b4be Poul Sander 2025-02-03 21:28 10079
        tab->WantClose = true;
1193b4be Poul Sander 2025-02-03 21:28 10080
        if (tab_bar->VisibleTabId == tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 10081
        {
1193b4be Poul Sander 2025-02-03 21:28 10082
            tab->LastFrameVisible = -1;
1193b4be Poul Sander 2025-02-03 21:28 10083
            tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0;
1193b4be Poul Sander 2025-02-03 21:28 10084
        }
1193b4be Poul Sander 2025-02-03 21:28 10085
    }
1193b4be Poul Sander 2025-02-03 21:28 10086
    else
1193b4be Poul Sander 2025-02-03 21:28 10087
    {
1193b4be Poul Sander 2025-02-03 21:28 10088
        // Actually select before expecting closure attempt (on an UnsavedDocument tab user is expect to e.g. show a popup)
1193b4be Poul Sander 2025-02-03 21:28 10089
        if (tab_bar->VisibleTabId != tab->ID)
1193b4be Poul Sander 2025-02-03 21:28 10090
            TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10091
    }
1193b4be Poul Sander 2025-02-03 21:28 10092
}
1193b4be Poul Sander 2025-02-03 21:28 10093
1193b4be Poul Sander 2025-02-03 21:28 10094
static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling)
1193b4be Poul Sander 2025-02-03 21:28 10095
{
1193b4be Poul Sander 2025-02-03 21:28 10096
    scrolling = ImMin(scrolling, tab_bar->WidthAllTabs - tab_bar->BarRect.GetWidth());
1193b4be Poul Sander 2025-02-03 21:28 10097
    return ImMax(scrolling, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10098
}
1193b4be Poul Sander 2025-02-03 21:28 10099
1193b4be Poul Sander 2025-02-03 21:28 10100
// Note: we may scroll to tab that are not selected! e.g. using keyboard arrow keys
1193b4be Poul Sander 2025-02-03 21:28 10101
static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiID tab_id, ImGuiTabBarSection* sections)
1193b4be Poul Sander 2025-02-03 21:28 10102
{
1193b4be Poul Sander 2025-02-03 21:28 10103
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id);
1193b4be Poul Sander 2025-02-03 21:28 10104
    if (tab == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10105
        return;
1193b4be Poul Sander 2025-02-03 21:28 10106
    if (tab->Flags & ImGuiTabItemFlags_SectionMask_)
1193b4be Poul Sander 2025-02-03 21:28 10107
        return;
1193b4be Poul Sander 2025-02-03 21:28 10108
1193b4be Poul Sander 2025-02-03 21:28 10109
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10110
    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)
1193b4be Poul Sander 2025-02-03 21:28 10111
    int order = TabBarGetTabOrder(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10112
1193b4be Poul Sander 2025-02-03 21:28 10113
    // Scrolling happens only in the central section (leading/trailing sections are not scrolling)
1193b4be Poul Sander 2025-02-03 21:28 10114
    float scrollable_width = TabBarCalcScrollableWidth(tab_bar, sections);
1193b4be Poul Sander 2025-02-03 21:28 10115
1193b4be Poul Sander 2025-02-03 21:28 10116
    // We make all tabs positions all relative Sections[0].Width to make code simpler
1193b4be Poul Sander 2025-02-03 21:28 10117
    float tab_x1 = tab->Offset - sections[0].Width + (order > sections[0].TabCount - 1 ? -margin : 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10118
    float tab_x2 = tab->Offset - sections[0].Width + tab->Width + (order + 1 < tab_bar->Tabs.Size - sections[2].TabCount ? margin : 1.0f);
1193b4be Poul Sander 2025-02-03 21:28 10119
    tab_bar->ScrollingTargetDistToVisibility = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 10120
    if (tab_bar->ScrollingTarget > tab_x1 || (tab_x2 - tab_x1 >= scrollable_width))
1193b4be Poul Sander 2025-02-03 21:28 10121
    {
1193b4be Poul Sander 2025-02-03 21:28 10122
        // Scroll to the left
1193b4be Poul Sander 2025-02-03 21:28 10123
        tab_bar->ScrollingTargetDistToVisibility = ImMax(tab_bar->ScrollingAnim - tab_x2, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10124
        tab_bar->ScrollingTarget = tab_x1;
1193b4be Poul Sander 2025-02-03 21:28 10125
    }
1193b4be Poul Sander 2025-02-03 21:28 10126
    else if (tab_bar->ScrollingTarget < tab_x2 - scrollable_width)
1193b4be Poul Sander 2025-02-03 21:28 10127
    {
1193b4be Poul Sander 2025-02-03 21:28 10128
        // Scroll to the right
1193b4be Poul Sander 2025-02-03 21:28 10129
        tab_bar->ScrollingTargetDistToVisibility = ImMax((tab_x1 - scrollable_width) - tab_bar->ScrollingAnim, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10130
        tab_bar->ScrollingTarget = tab_x2 - scrollable_width;
1193b4be Poul Sander 2025-02-03 21:28 10131
    }
1193b4be Poul Sander 2025-02-03 21:28 10132
}
1193b4be Poul Sander 2025-02-03 21:28 10133
1193b4be Poul Sander 2025-02-03 21:28 10134
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
1193b4be Poul Sander 2025-02-03 21:28 10135
{
1193b4be Poul Sander 2025-02-03 21:28 10136
    tab_bar->NextSelectedTabId = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 10137
}
1193b4be Poul Sander 2025-02-03 21:28 10138
1193b4be Poul Sander 2025-02-03 21:28 10139
void ImGui::TabBarQueueFocus(ImGuiTabBar* tab_bar, const char* tab_name)
1193b4be Poul Sander 2025-02-03 21:28 10140
{
1193b4be Poul Sander 2025-02-03 21:28 10141
    IM_ASSERT((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0); // Only supported for manual/explicit tab bars
1193b4be Poul Sander 2025-02-03 21:28 10142
    ImGuiID tab_id = TabBarCalcTabID(tab_bar, tab_name, NULL);
1193b4be Poul Sander 2025-02-03 21:28 10143
    tab_bar->NextSelectedTabId = tab_id;
1193b4be Poul Sander 2025-02-03 21:28 10144
}
1193b4be Poul Sander 2025-02-03 21:28 10145
1193b4be Poul Sander 2025-02-03 21:28 10146
void ImGui::TabBarQueueReorder(ImGuiTabBar* tab_bar, ImGuiTabItem* tab, int offset)
1193b4be Poul Sander 2025-02-03 21:28 10147
{
1193b4be Poul Sander 2025-02-03 21:28 10148
    IM_ASSERT(offset != 0);
1193b4be Poul Sander 2025-02-03 21:28 10149
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
1193b4be Poul Sander 2025-02-03 21:28 10150
    tab_bar->ReorderRequestTabId = tab->ID;
1193b4be Poul Sander 2025-02-03 21:28 10151
    tab_bar->ReorderRequestOffset = (ImS16)offset;
1193b4be Poul Sander 2025-02-03 21:28 10152
}
1193b4be Poul Sander 2025-02-03 21:28 10153
1193b4be Poul Sander 2025-02-03 21:28 10154
void ImGui::TabBarQueueReorderFromMousePos(ImGuiTabBar* tab_bar, ImGuiTabItem* src_tab, ImVec2 mouse_pos)
1193b4be Poul Sander 2025-02-03 21:28 10155
{
1193b4be Poul Sander 2025-02-03 21:28 10156
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10157
    IM_ASSERT(tab_bar->ReorderRequestTabId == 0);
1193b4be Poul Sander 2025-02-03 21:28 10158
    if ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) == 0)
1193b4be Poul Sander 2025-02-03 21:28 10159
        return;
1193b4be Poul Sander 2025-02-03 21:28 10160
1193b4be Poul Sander 2025-02-03 21:28 10161
    const bool is_central_section = (src_tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
1193b4be Poul Sander 2025-02-03 21:28 10162
    const float bar_offset = tab_bar->BarRect.Min.x - (is_central_section ? tab_bar->ScrollingTarget : 0);
1193b4be Poul Sander 2025-02-03 21:28 10163
1193b4be Poul Sander 2025-02-03 21:28 10164
    // Count number of contiguous tabs we are crossing over
1193b4be Poul Sander 2025-02-03 21:28 10165
    const int dir = (bar_offset + src_tab->Offset) > mouse_pos.x ? -1 : +1;
1193b4be Poul Sander 2025-02-03 21:28 10166
    const int src_idx = tab_bar->Tabs.index_from_ptr(src_tab);
1193b4be Poul Sander 2025-02-03 21:28 10167
    int dst_idx = src_idx;
1193b4be Poul Sander 2025-02-03 21:28 10168
    for (int i = src_idx; i >= 0 && i < tab_bar->Tabs.Size; i += dir)
1193b4be Poul Sander 2025-02-03 21:28 10169
    {
1193b4be Poul Sander 2025-02-03 21:28 10170
        // Reordered tabs must share the same section
1193b4be Poul Sander 2025-02-03 21:28 10171
        const ImGuiTabItem* dst_tab = &tab_bar->Tabs[i];
1193b4be Poul Sander 2025-02-03 21:28 10172
        if (dst_tab->Flags & ImGuiTabItemFlags_NoReorder)
1193b4be Poul Sander 2025-02-03 21:28 10173
            break;
1193b4be Poul Sander 2025-02-03 21:28 10174
        if ((dst_tab->Flags & ImGuiTabItemFlags_SectionMask_) != (src_tab->Flags & ImGuiTabItemFlags_SectionMask_))
1193b4be Poul Sander 2025-02-03 21:28 10175
            break;
1193b4be Poul Sander 2025-02-03 21:28 10176
        dst_idx = i;
1193b4be Poul Sander 2025-02-03 21:28 10177
1193b4be Poul Sander 2025-02-03 21:28 10178
        // Include spacing after tab, so when mouse cursor is between tabs we would not continue checking further tabs that are not hovered.
1193b4be Poul Sander 2025-02-03 21:28 10179
        const float x1 = bar_offset + dst_tab->Offset - g.Style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 10180
        const float x2 = bar_offset + dst_tab->Offset + dst_tab->Width + g.Style.ItemInnerSpacing.x;
1193b4be Poul Sander 2025-02-03 21:28 10181
        //GetForegroundDrawList()->AddRect(ImVec2(x1, tab_bar->BarRect.Min.y), ImVec2(x2, tab_bar->BarRect.Max.y), IM_COL32(255, 0, 0, 255));
1193b4be Poul Sander 2025-02-03 21:28 10182
        if ((dir < 0 && mouse_pos.x > x1) || (dir > 0 && mouse_pos.x < x2))
1193b4be Poul Sander 2025-02-03 21:28 10183
            break;
1193b4be Poul Sander 2025-02-03 21:28 10184
    }
1193b4be Poul Sander 2025-02-03 21:28 10185
1193b4be Poul Sander 2025-02-03 21:28 10186
    if (dst_idx != src_idx)
1193b4be Poul Sander 2025-02-03 21:28 10187
        TabBarQueueReorder(tab_bar, src_tab, dst_idx - src_idx);
1193b4be Poul Sander 2025-02-03 21:28 10188
}
1193b4be Poul Sander 2025-02-03 21:28 10189
1193b4be Poul Sander 2025-02-03 21:28 10190
bool ImGui::TabBarProcessReorder(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10191
{
1193b4be Poul Sander 2025-02-03 21:28 10192
    ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId);
1193b4be Poul Sander 2025-02-03 21:28 10193
    if (tab1 == NULL || (tab1->Flags & ImGuiTabItemFlags_NoReorder))
1193b4be Poul Sander 2025-02-03 21:28 10194
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10195
1193b4be Poul Sander 2025-02-03 21:28 10196
    //IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); // <- this may happen when using debug tools
1193b4be Poul Sander 2025-02-03 21:28 10197
    int tab2_order = TabBarGetTabOrder(tab_bar, tab1) + tab_bar->ReorderRequestOffset;
1193b4be Poul Sander 2025-02-03 21:28 10198
    if (tab2_order < 0 || tab2_order >= tab_bar->Tabs.Size)
1193b4be Poul Sander 2025-02-03 21:28 10199
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10200
1193b4be Poul Sander 2025-02-03 21:28 10201
    // Reordered tabs must share the same section
1193b4be Poul Sander 2025-02-03 21:28 10202
    // (Note: TabBarQueueReorderFromMousePos() also has a similar test but since we allow direct calls to TabBarQueueReorder() we do it here too)
1193b4be Poul Sander 2025-02-03 21:28 10203
    ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order];
1193b4be Poul Sander 2025-02-03 21:28 10204
    if (tab2->Flags & ImGuiTabItemFlags_NoReorder)
1193b4be Poul Sander 2025-02-03 21:28 10205
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10206
    if ((tab1->Flags & ImGuiTabItemFlags_SectionMask_) != (tab2->Flags & ImGuiTabItemFlags_SectionMask_))
1193b4be Poul Sander 2025-02-03 21:28 10207
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10208
1193b4be Poul Sander 2025-02-03 21:28 10209
    ImGuiTabItem item_tmp = *tab1;
1193b4be Poul Sander 2025-02-03 21:28 10210
    ImGuiTabItem* src_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 + 1 : tab2;
1193b4be Poul Sander 2025-02-03 21:28 10211
    ImGuiTabItem* dst_tab = (tab_bar->ReorderRequestOffset > 0) ? tab1 : tab2 + 1;
1193b4be Poul Sander 2025-02-03 21:28 10212
    const int move_count = (tab_bar->ReorderRequestOffset > 0) ? tab_bar->ReorderRequestOffset : -tab_bar->ReorderRequestOffset;
1193b4be Poul Sander 2025-02-03 21:28 10213
    memmove(dst_tab, src_tab, move_count * sizeof(ImGuiTabItem));
1193b4be Poul Sander 2025-02-03 21:28 10214
    *tab2 = item_tmp;
1193b4be Poul Sander 2025-02-03 21:28 10215
1193b4be Poul Sander 2025-02-03 21:28 10216
    if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings)
1193b4be Poul Sander 2025-02-03 21:28 10217
        MarkIniSettingsDirty();
1193b4be Poul Sander 2025-02-03 21:28 10218
    return true;
1193b4be Poul Sander 2025-02-03 21:28 10219
}
1193b4be Poul Sander 2025-02-03 21:28 10220
1193b4be Poul Sander 2025-02-03 21:28 10221
static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10222
{
1193b4be Poul Sander 2025-02-03 21:28 10223
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10224
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10225
1193b4be Poul Sander 2025-02-03 21:28 10226
    const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 10227
    const float scrolling_buttons_width = arrow_button_size.x * 2.0f;
1193b4be Poul Sander 2025-02-03 21:28 10228
1193b4be Poul Sander 2025-02-03 21:28 10229
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10230
    //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));
1193b4be Poul Sander 2025-02-03 21:28 10231
1193b4be Poul Sander 2025-02-03 21:28 10232
    int select_dir = 0;
1193b4be Poul Sander 2025-02-03 21:28 10233
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
1193b4be Poul Sander 2025-02-03 21:28 10234
    arrow_col.w *= 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 10235
1193b4be Poul Sander 2025-02-03 21:28 10236
    PushStyleColor(ImGuiCol_Text, arrow_col);
1193b4be Poul Sander 2025-02-03 21:28 10237
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
4e9e2555 Poul Sander 2025-10-05 10:15 10238
    PushItemFlag(ImGuiItemFlags_ButtonRepeat | ImGuiItemFlags_NoNav, true);
1193b4be Poul Sander 2025-02-03 21:28 10239
    const float backup_repeat_delay = g.IO.KeyRepeatDelay;
1193b4be Poul Sander 2025-02-03 21:28 10240
    const float backup_repeat_rate = g.IO.KeyRepeatRate;
1193b4be Poul Sander 2025-02-03 21:28 10241
    g.IO.KeyRepeatDelay = 0.250f;
1193b4be Poul Sander 2025-02-03 21:28 10242
    g.IO.KeyRepeatRate = 0.200f;
1193b4be Poul Sander 2025-02-03 21:28 10243
    float x = ImMax(tab_bar->BarRect.Min.x, tab_bar->BarRect.Max.x - scrolling_buttons_width);
1193b4be Poul Sander 2025-02-03 21:28 10244
    window->DC.CursorPos = ImVec2(x, tab_bar->BarRect.Min.y);
1193b4be Poul Sander 2025-02-03 21:28 10245
    if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
1193b4be Poul Sander 2025-02-03 21:28 10246
        select_dir = -1;
1193b4be Poul Sander 2025-02-03 21:28 10247
    window->DC.CursorPos = ImVec2(x + arrow_button_size.x, tab_bar->BarRect.Min.y);
1193b4be Poul Sander 2025-02-03 21:28 10248
    if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick))
1193b4be Poul Sander 2025-02-03 21:28 10249
        select_dir = +1;
1193b4be Poul Sander 2025-02-03 21:28 10250
    PopItemFlag();
1193b4be Poul Sander 2025-02-03 21:28 10251
    PopStyleColor(2);
1193b4be Poul Sander 2025-02-03 21:28 10252
    g.IO.KeyRepeatRate = backup_repeat_rate;
1193b4be Poul Sander 2025-02-03 21:28 10253
    g.IO.KeyRepeatDelay = backup_repeat_delay;
1193b4be Poul Sander 2025-02-03 21:28 10254
1193b4be Poul Sander 2025-02-03 21:28 10255
    ImGuiTabItem* tab_to_scroll_to = NULL;
1193b4be Poul Sander 2025-02-03 21:28 10256
    if (select_dir != 0)
1193b4be Poul Sander 2025-02-03 21:28 10257
        if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
1193b4be Poul Sander 2025-02-03 21:28 10258
        {
1193b4be Poul Sander 2025-02-03 21:28 10259
            int selected_order = TabBarGetTabOrder(tab_bar, tab_item);
1193b4be Poul Sander 2025-02-03 21:28 10260
            int target_order = selected_order + select_dir;
1193b4be Poul Sander 2025-02-03 21:28 10261
1193b4be Poul Sander 2025-02-03 21:28 10262
            // Skip tab item buttons until another tab item is found or end is reached
1193b4be Poul Sander 2025-02-03 21:28 10263
            while (tab_to_scroll_to == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10264
            {
1193b4be Poul Sander 2025-02-03 21:28 10265
                // If we are at the end of the list, still scroll to make our tab visible
1193b4be Poul Sander 2025-02-03 21:28 10266
                tab_to_scroll_to = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order];
1193b4be Poul Sander 2025-02-03 21:28 10267
1193b4be Poul Sander 2025-02-03 21:28 10268
                // Cross through buttons
1193b4be Poul Sander 2025-02-03 21:28 10269
                // (even if first/last item is a button, return it so we can update the scroll)
1193b4be Poul Sander 2025-02-03 21:28 10270
                if (tab_to_scroll_to->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 10271
                {
1193b4be Poul Sander 2025-02-03 21:28 10272
                    target_order += select_dir;
1193b4be Poul Sander 2025-02-03 21:28 10273
                    selected_order += select_dir;
1193b4be Poul Sander 2025-02-03 21:28 10274
                    tab_to_scroll_to = (target_order < 0 || target_order >= tab_bar->Tabs.Size) ? tab_to_scroll_to : NULL;
1193b4be Poul Sander 2025-02-03 21:28 10275
                }
1193b4be Poul Sander 2025-02-03 21:28 10276
            }
1193b4be Poul Sander 2025-02-03 21:28 10277
        }
1193b4be Poul Sander 2025-02-03 21:28 10278
    window->DC.CursorPos = backup_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10279
    tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10280
1193b4be Poul Sander 2025-02-03 21:28 10281
    return tab_to_scroll_to;
1193b4be Poul Sander 2025-02-03 21:28 10282
}
1193b4be Poul Sander 2025-02-03 21:28 10283
1193b4be Poul Sander 2025-02-03 21:28 10284
static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10285
{
1193b4be Poul Sander 2025-02-03 21:28 10286
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10287
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10288
1193b4be Poul Sander 2025-02-03 21:28 10289
    // We use g.Style.FramePadding.y to match the square ArrowButton size
1193b4be Poul Sander 2025-02-03 21:28 10290
    const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y;
1193b4be Poul Sander 2025-02-03 21:28 10291
    const ImVec2 backup_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10292
    window->DC.CursorPos = ImVec2(tab_bar->BarRect.Min.x - g.Style.FramePadding.y, tab_bar->BarRect.Min.y);
1193b4be Poul Sander 2025-02-03 21:28 10293
    tab_bar->BarRect.Min.x += tab_list_popup_button_width;
1193b4be Poul Sander 2025-02-03 21:28 10294
1193b4be Poul Sander 2025-02-03 21:28 10295
    ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text];
1193b4be Poul Sander 2025-02-03 21:28 10296
    arrow_col.w *= 0.5f;
1193b4be Poul Sander 2025-02-03 21:28 10297
    PushStyleColor(ImGuiCol_Text, arrow_col);
1193b4be Poul Sander 2025-02-03 21:28 10298
    PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
1193b4be Poul Sander 2025-02-03 21:28 10299
    bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_HeightLargest);
1193b4be Poul Sander 2025-02-03 21:28 10300
    PopStyleColor(2);
1193b4be Poul Sander 2025-02-03 21:28 10301
1193b4be Poul Sander 2025-02-03 21:28 10302
    ImGuiTabItem* tab_to_select = NULL;
1193b4be Poul Sander 2025-02-03 21:28 10303
    if (open)
1193b4be Poul Sander 2025-02-03 21:28 10304
    {
1193b4be Poul Sander 2025-02-03 21:28 10305
        for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
1193b4be Poul Sander 2025-02-03 21:28 10306
        {
1193b4be Poul Sander 2025-02-03 21:28 10307
            ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
1193b4be Poul Sander 2025-02-03 21:28 10308
            if (tab->Flags & ImGuiTabItemFlags_Button)
1193b4be Poul Sander 2025-02-03 21:28 10309
                continue;
1193b4be Poul Sander 2025-02-03 21:28 10310
1193b4be Poul Sander 2025-02-03 21:28 10311
            const char* tab_name = TabBarGetTabName(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10312
            if (Selectable(tab_name, tab_bar->SelectedTabId == tab->ID))
1193b4be Poul Sander 2025-02-03 21:28 10313
                tab_to_select = tab;
1193b4be Poul Sander 2025-02-03 21:28 10314
        }
1193b4be Poul Sander 2025-02-03 21:28 10315
        EndCombo();
1193b4be Poul Sander 2025-02-03 21:28 10316
    }
1193b4be Poul Sander 2025-02-03 21:28 10317
1193b4be Poul Sander 2025-02-03 21:28 10318
    window->DC.CursorPos = backup_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10319
    return tab_to_select;
1193b4be Poul Sander 2025-02-03 21:28 10320
}
1193b4be Poul Sander 2025-02-03 21:28 10321
1193b4be Poul Sander 2025-02-03 21:28 10322
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 10323
// [SECTION] Widgets: BeginTabItem, EndTabItem, etc.
1193b4be Poul Sander 2025-02-03 21:28 10324
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 10325
// - BeginTabItem()
1193b4be Poul Sander 2025-02-03 21:28 10326
// - EndTabItem()
1193b4be Poul Sander 2025-02-03 21:28 10327
// - TabItemButton()
1193b4be Poul Sander 2025-02-03 21:28 10328
// - TabItemEx() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 10329
// - SetTabItemClosed()
1193b4be Poul Sander 2025-02-03 21:28 10330
// - TabItemCalcSize() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 10331
// - TabItemBackground() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 10332
// - TabItemLabelAndCloseButton() [Internal]
1193b4be Poul Sander 2025-02-03 21:28 10333
//-------------------------------------------------------------------------
1193b4be Poul Sander 2025-02-03 21:28 10334
1193b4be Poul Sander 2025-02-03 21:28 10335
bool    ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 10336
{
1193b4be Poul Sander 2025-02-03 21:28 10337
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10338
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10339
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 10340
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10341
1193b4be Poul Sander 2025-02-03 21:28 10342
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 10343
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10344
    {
1193b4be Poul Sander 2025-02-03 21:28 10345
        IM_ASSERT_USER_ERROR(tab_bar, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 10346
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10347
    }
4e9e2555 Poul Sander 2025-10-05 10:15 10348
    IM_ASSERT((flags & ImGuiTabItemFlags_Button) == 0); // BeginTabItem() Can't be used with button flags, use TabItemButton() instead!
1193b4be Poul Sander 2025-02-03 21:28 10349
1193b4be Poul Sander 2025-02-03 21:28 10350
    bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL);
1193b4be Poul Sander 2025-02-03 21:28 10351
    if (ret && !(flags & ImGuiTabItemFlags_NoPushId))
1193b4be Poul Sander 2025-02-03 21:28 10352
    {
1193b4be Poul Sander 2025-02-03 21:28 10353
        ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 10354
        PushOverrideID(tab->ID); // We already hashed 'label' so push into the ID stack directly instead of doing another hash through PushID(label)
1193b4be Poul Sander 2025-02-03 21:28 10355
    }
1193b4be Poul Sander 2025-02-03 21:28 10356
    return ret;
1193b4be Poul Sander 2025-02-03 21:28 10357
}
1193b4be Poul Sander 2025-02-03 21:28 10358
1193b4be Poul Sander 2025-02-03 21:28 10359
void    ImGui::EndTabItem()
1193b4be Poul Sander 2025-02-03 21:28 10360
{
1193b4be Poul Sander 2025-02-03 21:28 10361
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10362
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10363
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 10364
        return;
1193b4be Poul Sander 2025-02-03 21:28 10365
1193b4be Poul Sander 2025-02-03 21:28 10366
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 10367
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10368
    {
1193b4be Poul Sander 2025-02-03 21:28 10369
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 10370
        return;
1193b4be Poul Sander 2025-02-03 21:28 10371
    }
1193b4be Poul Sander 2025-02-03 21:28 10372
    IM_ASSERT(tab_bar->LastTabItemIdx >= 0);
1193b4be Poul Sander 2025-02-03 21:28 10373
    ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx];
1193b4be Poul Sander 2025-02-03 21:28 10374
    if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))
1193b4be Poul Sander 2025-02-03 21:28 10375
        PopID();
1193b4be Poul Sander 2025-02-03 21:28 10376
}
1193b4be Poul Sander 2025-02-03 21:28 10377
1193b4be Poul Sander 2025-02-03 21:28 10378
bool    ImGui::TabItemButton(const char* label, ImGuiTabItemFlags flags)
1193b4be Poul Sander 2025-02-03 21:28 10379
{
1193b4be Poul Sander 2025-02-03 21:28 10380
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10381
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10382
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 10383
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10384
1193b4be Poul Sander 2025-02-03 21:28 10385
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 10386
    if (tab_bar == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10387
    {
1193b4be Poul Sander 2025-02-03 21:28 10388
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
1193b4be Poul Sander 2025-02-03 21:28 10389
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10390
    }
1193b4be Poul Sander 2025-02-03 21:28 10391
    return TabItemEx(tab_bar, label, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder, NULL);
1193b4be Poul Sander 2025-02-03 21:28 10392
}
1193b4be Poul Sander 2025-02-03 21:28 10393
4e9e2555 Poul Sander 2025-10-05 10:15 10394
void    ImGui::TabItemSpacing(const char* str_id, ImGuiTabItemFlags flags, float width)
4e9e2555 Poul Sander 2025-10-05 10:15 10395
{
4e9e2555 Poul Sander 2025-10-05 10:15 10396
    ImGuiContext& g = *GImGui;
4e9e2555 Poul Sander 2025-10-05 10:15 10397
    ImGuiWindow* window = g.CurrentWindow;
4e9e2555 Poul Sander 2025-10-05 10:15 10398
    if (window->SkipItems)
4e9e2555 Poul Sander 2025-10-05 10:15 10399
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 10400
4e9e2555 Poul Sander 2025-10-05 10:15 10401
    ImGuiTabBar* tab_bar = g.CurrentTabBar;
4e9e2555 Poul Sander 2025-10-05 10:15 10402
    if (tab_bar == NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 10403
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10404
        IM_ASSERT_USER_ERROR(tab_bar != NULL, "Needs to be called between BeginTabBar() and EndTabBar()!");
4e9e2555 Poul Sander 2025-10-05 10:15 10405
        return;
4e9e2555 Poul Sander 2025-10-05 10:15 10406
    }
4e9e2555 Poul Sander 2025-10-05 10:15 10407
    SetNextItemWidth(width);
4e9e2555 Poul Sander 2025-10-05 10:15 10408
    TabItemEx(tab_bar, str_id, NULL, flags | ImGuiTabItemFlags_Button | ImGuiTabItemFlags_NoReorder | ImGuiTabItemFlags_Invisible, NULL);
4e9e2555 Poul Sander 2025-10-05 10:15 10409
}
4e9e2555 Poul Sander 2025-10-05 10:15 10410
1193b4be Poul Sander 2025-02-03 21:28 10411
bool    ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window)
1193b4be Poul Sander 2025-02-03 21:28 10412
{
1193b4be Poul Sander 2025-02-03 21:28 10413
    // Layout whole tab bar if not already done
1193b4be Poul Sander 2025-02-03 21:28 10414
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10415
    if (tab_bar->WantLayout)
1193b4be Poul Sander 2025-02-03 21:28 10416
    {
1193b4be Poul Sander 2025-02-03 21:28 10417
        ImGuiNextItemData backup_next_item_data = g.NextItemData;
1193b4be Poul Sander 2025-02-03 21:28 10418
        TabBarLayout(tab_bar);
1193b4be Poul Sander 2025-02-03 21:28 10419
        g.NextItemData = backup_next_item_data;
1193b4be Poul Sander 2025-02-03 21:28 10420
    }
1193b4be Poul Sander 2025-02-03 21:28 10421
    ImGuiWindow* window = g.CurrentWindow;
1193b4be Poul Sander 2025-02-03 21:28 10422
    if (window->SkipItems)
1193b4be Poul Sander 2025-02-03 21:28 10423
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10424
1193b4be Poul Sander 2025-02-03 21:28 10425
    const ImGuiStyle& style = g.Style;
1193b4be Poul Sander 2025-02-03 21:28 10426
    const ImGuiID id = TabBarCalcTabID(tab_bar, label, docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10427
1193b4be Poul Sander 2025-02-03 21:28 10428
    // If the user called us with *p_open == false, we early out and don't render.
1193b4be Poul Sander 2025-02-03 21:28 10429
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 10430
    IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
1193b4be Poul Sander 2025-02-03 21:28 10431
    if (p_open && !*p_open)
1193b4be Poul Sander 2025-02-03 21:28 10432
    {
1193b4be Poul Sander 2025-02-03 21:28 10433
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 10434
        return false;
1193b4be Poul Sander 2025-02-03 21:28 10435
    }
1193b4be Poul Sander 2025-02-03 21:28 10436
1193b4be Poul Sander 2025-02-03 21:28 10437
    IM_ASSERT(!p_open || !(flags & ImGuiTabItemFlags_Button));
1193b4be Poul Sander 2025-02-03 21:28 10438
    IM_ASSERT((flags & (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)) != (ImGuiTabItemFlags_Leading | ImGuiTabItemFlags_Trailing)); // Can't use both Leading and Trailing
1193b4be Poul Sander 2025-02-03 21:28 10439
1193b4be Poul Sander 2025-02-03 21:28 10440
    // Store into ImGuiTabItemFlags_NoCloseButton, also honor ImGuiTabItemFlags_NoCloseButton passed by user (although not documented)
1193b4be Poul Sander 2025-02-03 21:28 10441
    if (flags & ImGuiTabItemFlags_NoCloseButton)
1193b4be Poul Sander 2025-02-03 21:28 10442
        p_open = NULL;
1193b4be Poul Sander 2025-02-03 21:28 10443
    else if (p_open == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10444
        flags |= ImGuiTabItemFlags_NoCloseButton;
1193b4be Poul Sander 2025-02-03 21:28 10445
1193b4be Poul Sander 2025-02-03 21:28 10446
    // Acquire tab data
1193b4be Poul Sander 2025-02-03 21:28 10447
    ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id);
1193b4be Poul Sander 2025-02-03 21:28 10448
    bool tab_is_new = false;
1193b4be Poul Sander 2025-02-03 21:28 10449
    if (tab == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10450
    {
1193b4be Poul Sander 2025-02-03 21:28 10451
        tab_bar->Tabs.push_back(ImGuiTabItem());
1193b4be Poul Sander 2025-02-03 21:28 10452
        tab = &tab_bar->Tabs.back();
1193b4be Poul Sander 2025-02-03 21:28 10453
        tab->ID = id;
1193b4be Poul Sander 2025-02-03 21:28 10454
        tab_bar->TabsAddedNew = tab_is_new = true;
1193b4be Poul Sander 2025-02-03 21:28 10455
    }
1193b4be Poul Sander 2025-02-03 21:28 10456
    tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
1193b4be Poul Sander 2025-02-03 21:28 10457
1193b4be Poul Sander 2025-02-03 21:28 10458
    // Calculate tab contents size
1193b4be Poul Sander 2025-02-03 21:28 10459
    ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
1193b4be Poul Sander 2025-02-03 21:28 10460
    tab->RequestedWidth = -1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10461
    if (g.NextItemData.HasFlags & ImGuiNextItemDataFlags_HasWidth)
1193b4be Poul Sander 2025-02-03 21:28 10462
        size.x = tab->RequestedWidth = g.NextItemData.Width;
1193b4be Poul Sander 2025-02-03 21:28 10463
    if (tab_is_new)
1193b4be Poul Sander 2025-02-03 21:28 10464
        tab->Width = ImMax(1.0f, size.x);
1193b4be Poul Sander 2025-02-03 21:28 10465
    tab->ContentWidth = size.x;
1193b4be Poul Sander 2025-02-03 21:28 10466
    tab->BeginOrder = tab_bar->TabsActiveCount++;
1193b4be Poul Sander 2025-02-03 21:28 10467
1193b4be Poul Sander 2025-02-03 21:28 10468
    const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 10469
    const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
1193b4be Poul Sander 2025-02-03 21:28 10470
    const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
1193b4be Poul Sander 2025-02-03 21:28 10471
    const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
1193b4be Poul Sander 2025-02-03 21:28 10472
    const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
1193b4be Poul Sander 2025-02-03 21:28 10473
    tab->LastFrameVisible = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 10474
    tab->Flags = flags;
1193b4be Poul Sander 2025-02-03 21:28 10475
    tab->Window = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10476
1193b4be Poul Sander 2025-02-03 21:28 10477
    // Append name _WITH_ the zero-terminator
1193b4be Poul Sander 2025-02-03 21:28 10478
    // (regular tabs are permitted in a DockNode tab bar, but window tabs not permitted in a non-DockNode tab bar)
1193b4be Poul Sander 2025-02-03 21:28 10479
    if (docked_window != NULL)
1193b4be Poul Sander 2025-02-03 21:28 10480
    {
1193b4be Poul Sander 2025-02-03 21:28 10481
        IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 10482
        tab->NameOffset = -1;
1193b4be Poul Sander 2025-02-03 21:28 10483
    }
1193b4be Poul Sander 2025-02-03 21:28 10484
    else
1193b4be Poul Sander 2025-02-03 21:28 10485
    {
1193b4be Poul Sander 2025-02-03 21:28 10486
        tab->NameOffset = (ImS32)tab_bar->TabsNames.size();
4e9e2555 Poul Sander 2025-10-05 10:15 10487
        tab_bar->TabsNames.append(label, label + ImStrlen(label) + 1);
1193b4be Poul Sander 2025-02-03 21:28 10488
    }
1193b4be Poul Sander 2025-02-03 21:28 10489
1193b4be Poul Sander 2025-02-03 21:28 10490
    // Update selected tab
1193b4be Poul Sander 2025-02-03 21:28 10491
    if (!is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10492
    {
1193b4be Poul Sander 2025-02-03 21:28 10493
        if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0)
1193b4be Poul Sander 2025-02-03 21:28 10494
            if (!tab_bar_appearing || tab_bar->SelectedTabId == 0)
1193b4be Poul Sander 2025-02-03 21:28 10495
                TabBarQueueFocus(tab_bar, tab); // New tabs gets activated
1193b4be Poul Sander 2025-02-03 21:28 10496
        if ((flags & ImGuiTabItemFlags_SetSelected) && (tab_bar->SelectedTabId != id)) // _SetSelected can only be passed on explicit tab bar
1193b4be Poul Sander 2025-02-03 21:28 10497
            TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10498
    }
1193b4be Poul Sander 2025-02-03 21:28 10499
1193b4be Poul Sander 2025-02-03 21:28 10500
    // Lock visibility
1193b4be Poul Sander 2025-02-03 21:28 10501
    // (Note: tab_contents_visible != tab_selected... because CTRL+TAB operations may preview some tabs without selecting them!)
1193b4be Poul Sander 2025-02-03 21:28 10502
    bool tab_contents_visible = (tab_bar->VisibleTabId == id);
1193b4be Poul Sander 2025-02-03 21:28 10503
    if (tab_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10504
        tab_bar->VisibleTabWasSubmitted = true;
1193b4be Poul Sander 2025-02-03 21:28 10505
1193b4be Poul Sander 2025-02-03 21:28 10506
    // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches
1193b4be Poul Sander 2025-02-03 21:28 10507
    if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL)
1193b4be Poul Sander 2025-02-03 21:28 10508
        if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs))
1193b4be Poul Sander 2025-02-03 21:28 10509
            tab_contents_visible = true;
1193b4be Poul Sander 2025-02-03 21:28 10510
1193b4be Poul Sander 2025-02-03 21:28 10511
    // Note that tab_is_new is not necessarily the same as tab_appearing! When a tab bar stops being submitted
1193b4be Poul Sander 2025-02-03 21:28 10512
    // and then gets submitted again, the tabs will have 'tab_appearing=true' but 'tab_is_new=false'.
1193b4be Poul Sander 2025-02-03 21:28 10513
    if (tab_appearing && (!tab_bar_appearing || tab_is_new))
1193b4be Poul Sander 2025-02-03 21:28 10514
    {
1193b4be Poul Sander 2025-02-03 21:28 10515
        ItemAdd(ImRect(), id, NULL, ImGuiItemFlags_NoNav);
1193b4be Poul Sander 2025-02-03 21:28 10516
        if (is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10517
            return false;
1193b4be Poul Sander 2025-02-03 21:28 10518
        return tab_contents_visible;
1193b4be Poul Sander 2025-02-03 21:28 10519
    }
1193b4be Poul Sander 2025-02-03 21:28 10520
1193b4be Poul Sander 2025-02-03 21:28 10521
    if (tab_bar->SelectedTabId == id)
1193b4be Poul Sander 2025-02-03 21:28 10522
        tab->LastFrameSelected = g.FrameCount;
1193b4be Poul Sander 2025-02-03 21:28 10523
1193b4be Poul Sander 2025-02-03 21:28 10524
    // Backup current layout position
1193b4be Poul Sander 2025-02-03 21:28 10525
    const ImVec2 backup_main_cursor_pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10526
1193b4be Poul Sander 2025-02-03 21:28 10527
    // Layout
1193b4be Poul Sander 2025-02-03 21:28 10528
    const bool is_central_section = (tab->Flags & ImGuiTabItemFlags_SectionMask_) == 0;
1193b4be Poul Sander 2025-02-03 21:28 10529
    size.x = tab->Width;
1193b4be Poul Sander 2025-02-03 21:28 10530
    if (is_central_section)
1193b4be Poul Sander 2025-02-03 21:28 10531
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(IM_TRUNC(tab->Offset - tab_bar->ScrollingAnim), 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10532
    else
1193b4be Poul Sander 2025-02-03 21:28 10533
        window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2(tab->Offset, 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10534
    ImVec2 pos = window->DC.CursorPos;
1193b4be Poul Sander 2025-02-03 21:28 10535
    ImRect bb(pos, pos + size);
1193b4be Poul Sander 2025-02-03 21:28 10536
1193b4be Poul Sander 2025-02-03 21:28 10537
    // 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)
1193b4be Poul Sander 2025-02-03 21:28 10538
    const bool want_clip_rect = is_central_section && (bb.Min.x < tab_bar->ScrollingRectMinX || bb.Max.x > tab_bar->ScrollingRectMaxX);
1193b4be Poul Sander 2025-02-03 21:28 10539
    if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10540
        PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->ScrollingRectMinX), bb.Min.y - 1), ImVec2(tab_bar->ScrollingRectMaxX, bb.Max.y), true);
1193b4be Poul Sander 2025-02-03 21:28 10541
1193b4be Poul Sander 2025-02-03 21:28 10542
    ImVec2 backup_cursor_max_pos = window->DC.CursorMaxPos;
1193b4be Poul Sander 2025-02-03 21:28 10543
    ItemSize(bb.GetSize(), style.FramePadding.y);
1193b4be Poul Sander 2025-02-03 21:28 10544
    window->DC.CursorMaxPos = backup_cursor_max_pos;
1193b4be Poul Sander 2025-02-03 21:28 10545
1193b4be Poul Sander 2025-02-03 21:28 10546
    if (!ItemAdd(bb, id))
1193b4be Poul Sander 2025-02-03 21:28 10547
    {
1193b4be Poul Sander 2025-02-03 21:28 10548
        if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10549
            PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 10550
        window->DC.CursorPos = backup_main_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10551
        return tab_contents_visible;
1193b4be Poul Sander 2025-02-03 21:28 10552
    }
1193b4be Poul Sander 2025-02-03 21:28 10553
1193b4be Poul Sander 2025-02-03 21:28 10554
    // Click to Select a tab
1193b4be Poul Sander 2025-02-03 21:28 10555
    ImGuiButtonFlags button_flags = ((is_tab_button ? ImGuiButtonFlags_PressedOnClickRelease : ImGuiButtonFlags_PressedOnClick) | ImGuiButtonFlags_AllowOverlap);
1193b4be Poul Sander 2025-02-03 21:28 10556
    if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) // FIXME: May be an opt-in property of the payload to disable this
1193b4be Poul Sander 2025-02-03 21:28 10557
        button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
4e9e2555 Poul Sander 2025-10-05 10:15 10558
    bool hovered, held, pressed;
4e9e2555 Poul Sander 2025-10-05 10:15 10559
    if (flags & ImGuiTabItemFlags_Invisible)
4e9e2555 Poul Sander 2025-10-05 10:15 10560
        hovered = held = pressed = false;
4e9e2555 Poul Sander 2025-10-05 10:15 10561
    else
4e9e2555 Poul Sander 2025-10-05 10:15 10562
        pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
1193b4be Poul Sander 2025-02-03 21:28 10563
    if (pressed && !is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10564
        TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10565
1193b4be Poul Sander 2025-02-03 21:28 10566
    // Transfer active id window so the active id is not owned by the dock host (as StartMouseMovingWindow()
1193b4be Poul Sander 2025-02-03 21:28 10567
    // will only do it on the drag). This allows FocusWindow() to be more conservative in how it clears active id.
1193b4be Poul Sander 2025-02-03 21:28 10568
    if (held && docked_window && g.ActiveId == id && g.ActiveIdIsJustActivated)
1193b4be Poul Sander 2025-02-03 21:28 10569
        g.ActiveIdWindow = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10570
1193b4be Poul Sander 2025-02-03 21:28 10571
    // Drag and drop a single floating window node moves it
1193b4be Poul Sander 2025-02-03 21:28 10572
    ImGuiDockNode* node = docked_window ? docked_window->DockNode : NULL;
1193b4be Poul Sander 2025-02-03 21:28 10573
    const bool single_floating_window_node = node && node->IsFloatingNode() && (node->Windows.Size == 1);
1193b4be Poul Sander 2025-02-03 21:28 10574
    if (held && single_floating_window_node && IsMouseDragging(0, 0.0f))
1193b4be Poul Sander 2025-02-03 21:28 10575
    {
1193b4be Poul Sander 2025-02-03 21:28 10576
        // Move
1193b4be Poul Sander 2025-02-03 21:28 10577
        StartMouseMovingWindow(docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10578
    }
1193b4be Poul Sander 2025-02-03 21:28 10579
    else if (held && !tab_appearing && IsMouseDragging(0))
1193b4be Poul Sander 2025-02-03 21:28 10580
    {
1193b4be Poul Sander 2025-02-03 21:28 10581
        // Drag and drop: re-order tabs
1193b4be Poul Sander 2025-02-03 21:28 10582
        int drag_dir = 0;
1193b4be Poul Sander 2025-02-03 21:28 10583
        float drag_distance_from_edge_x = 0.0f;
1193b4be Poul Sander 2025-02-03 21:28 10584
        if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (docked_window != NULL)))
1193b4be Poul Sander 2025-02-03 21:28 10585
        {
1193b4be Poul Sander 2025-02-03 21:28 10586
            // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x
1193b4be Poul Sander 2025-02-03 21:28 10587
            if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x)
1193b4be Poul Sander 2025-02-03 21:28 10588
            {
1193b4be Poul Sander 2025-02-03 21:28 10589
                drag_dir = -1;
1193b4be Poul Sander 2025-02-03 21:28 10590
                drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x;
1193b4be Poul Sander 2025-02-03 21:28 10591
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 10592
            }
1193b4be Poul Sander 2025-02-03 21:28 10593
            else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x)
1193b4be Poul Sander 2025-02-03 21:28 10594
            {
1193b4be Poul Sander 2025-02-03 21:28 10595
                drag_dir = +1;
1193b4be Poul Sander 2025-02-03 21:28 10596
                drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10597
                TabBarQueueReorderFromMousePos(tab_bar, tab, g.IO.MousePos);
1193b4be Poul Sander 2025-02-03 21:28 10598
            }
1193b4be Poul Sander 2025-02-03 21:28 10599
        }
1193b4be Poul Sander 2025-02-03 21:28 10600
1193b4be Poul Sander 2025-02-03 21:28 10601
        // Extract a Dockable window out of it's tab bar
1193b4be Poul Sander 2025-02-03 21:28 10602
        const bool can_undock = docked_window != NULL && !(docked_window->Flags & ImGuiWindowFlags_NoMove) && !(node->MergedFlags & ImGuiDockNodeFlags_NoUndocking);
1193b4be Poul Sander 2025-02-03 21:28 10603
        if (can_undock)
1193b4be Poul Sander 2025-02-03 21:28 10604
        {
1193b4be Poul Sander 2025-02-03 21:28 10605
            // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar
1193b4be Poul Sander 2025-02-03 21:28 10606
            bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id);
1193b4be Poul Sander 2025-02-03 21:28 10607
            if (!undocking_tab) //&& (!g.IO.ConfigDockingWithShift || g.IO.KeyShift)
1193b4be Poul Sander 2025-02-03 21:28 10608
            {
1193b4be Poul Sander 2025-02-03 21:28 10609
                float threshold_base = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 10610
                float threshold_x = (threshold_base * 2.2f);
1193b4be Poul Sander 2025-02-03 21:28 10611
                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);
1193b4be Poul Sander 2025-02-03 21:28 10612
                //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]
1193b4be Poul Sander 2025-02-03 21:28 10613
1193b4be Poul Sander 2025-02-03 21:28 10614
                float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y);
1193b4be Poul Sander 2025-02-03 21:28 10615
                if (distance_from_edge_y >= threshold_y)
1193b4be Poul Sander 2025-02-03 21:28 10616
                    undocking_tab = true;
1193b4be Poul Sander 2025-02-03 21:28 10617
                if (drag_distance_from_edge_x > threshold_x)
1193b4be Poul Sander 2025-02-03 21:28 10618
                    if ((drag_dir < 0 && TabBarGetTabOrder(tab_bar, tab) == 0) || (drag_dir > 0 && TabBarGetTabOrder(tab_bar, tab) == tab_bar->Tabs.Size - 1))
1193b4be Poul Sander 2025-02-03 21:28 10619
                        undocking_tab = true;
1193b4be Poul Sander 2025-02-03 21:28 10620
            }
1193b4be Poul Sander 2025-02-03 21:28 10621
1193b4be Poul Sander 2025-02-03 21:28 10622
            if (undocking_tab)
1193b4be Poul Sander 2025-02-03 21:28 10623
            {
1193b4be Poul Sander 2025-02-03 21:28 10624
                // Undock
1193b4be Poul Sander 2025-02-03 21:28 10625
                // FIXME: refactor to share more code with e.g. StartMouseMovingWindow
1193b4be Poul Sander 2025-02-03 21:28 10626
                DockContextQueueUndockWindow(&g, docked_window);
1193b4be Poul Sander 2025-02-03 21:28 10627
                g.MovingWindow = docked_window;
1193b4be Poul Sander 2025-02-03 21:28 10628
                SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
1193b4be Poul Sander 2025-02-03 21:28 10629
                g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
1193b4be Poul Sander 2025-02-03 21:28 10630
                g.ActiveIdNoClearOnFocusLoss = true;
1193b4be Poul Sander 2025-02-03 21:28 10631
                SetActiveIdUsingAllKeyboardKeys();
1193b4be Poul Sander 2025-02-03 21:28 10632
            }
1193b4be Poul Sander 2025-02-03 21:28 10633
        }
1193b4be Poul Sander 2025-02-03 21:28 10634
    }
1193b4be Poul Sander 2025-02-03 21:28 10635
1193b4be Poul Sander 2025-02-03 21:28 10636
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10637
    if (hovered && g.HoveredIdNotActiveTimer > TOOLTIP_DELAY && bb.GetWidth() < tab->ContentWidth)
1193b4be Poul Sander 2025-02-03 21:28 10638
    {
1193b4be Poul Sander 2025-02-03 21:28 10639
        // Enlarge tab display when hovering
1193b4be Poul Sander 2025-02-03 21:28 10640
        bb.Max.x = bb.Min.x + IM_TRUNC(ImLerp(bb.GetWidth(), tab->ContentWidth, ImSaturate((g.HoveredIdNotActiveTimer - 0.40f) * 6.0f)));
1193b4be Poul Sander 2025-02-03 21:28 10641
        display_draw_list = GetForegroundDrawList(window);
1193b4be Poul Sander 2025-02-03 21:28 10642
        TabItemBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive));
1193b4be Poul Sander 2025-02-03 21:28 10643
    }
1193b4be Poul Sander 2025-02-03 21:28 10644
#endif
1193b4be Poul Sander 2025-02-03 21:28 10645
1193b4be Poul Sander 2025-02-03 21:28 10646
    // Render tab shape
4e9e2555 Poul Sander 2025-10-05 10:15 10647
    const bool is_visible = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) && !(flags & ImGuiTabItemFlags_Invisible);
4e9e2555 Poul Sander 2025-10-05 10:15 10648
    if (is_visible)
1193b4be Poul Sander 2025-02-03 21:28 10649
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10650
        ImDrawList* display_draw_list = window->DrawList;
4e9e2555 Poul Sander 2025-10-05 10:15 10651
        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));
4e9e2555 Poul Sander 2025-10-05 10:15 10652
        TabItemBackground(display_draw_list, bb, flags, tab_col);
4e9e2555 Poul Sander 2025-10-05 10:15 10653
        if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 10654
        {
4e9e2555 Poul Sander 2025-10-05 10:15 10655
            // Might be moved to TabItemBackground() ?
4e9e2555 Poul Sander 2025-10-05 10:15 10656
            ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale);
4e9e2555 Poul Sander 2025-10-05 10:15 10657
            ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale);
4e9e2555 Poul Sander 2025-10-05 10:15 10658
            ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline);
4e9e2555 Poul Sander 2025-10-05 10:15 10659
            if (style.TabRounding > 0.0f)
4e9e2555 Poul Sander 2025-10-05 10:15 10660
            {
4e9e2555 Poul Sander 2025-10-05 10:15 10661
                float rounding = style.TabRounding;
4e9e2555 Poul Sander 2025-10-05 10:15 10662
                display_draw_list->PathArcToFast(tl + ImVec2(+rounding, +rounding), rounding, 7, 9);
4e9e2555 Poul Sander 2025-10-05 10:15 10663
                display_draw_list->PathArcToFast(tr + ImVec2(-rounding, +rounding), rounding, 9, 11);
4e9e2555 Poul Sander 2025-10-05 10:15 10664
                display_draw_list->PathStroke(overline_col, 0, style.TabBarOverlineSize);
4e9e2555 Poul Sander 2025-10-05 10:15 10665
            }
4e9e2555 Poul Sander 2025-10-05 10:15 10666
            else
4e9e2555 Poul Sander 2025-10-05 10:15 10667
            {
4e9e2555 Poul Sander 2025-10-05 10:15 10668
                display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize);
4e9e2555 Poul Sander 2025-10-05 10:15 10669
            }
4e9e2555 Poul Sander 2025-10-05 10:15 10670
        }
4e9e2555 Poul Sander 2025-10-05 10:15 10671
        RenderNavCursor(bb, id);
1193b4be Poul Sander 2025-02-03 21:28 10672
4e9e2555 Poul Sander 2025-10-05 10:15 10673
        // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget.
4e9e2555 Poul Sander 2025-10-05 10:15 10674
        const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup);
4e9e2555 Poul Sander 2025-10-05 10:15 10675
        if (tab_bar->SelectedTabId != tab->ID && hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1)) && !is_tab_button)
4e9e2555 Poul Sander 2025-10-05 10:15 10676
            TabBarQueueFocus(tab_bar, tab);
1193b4be Poul Sander 2025-02-03 21:28 10677
4e9e2555 Poul Sander 2025-10-05 10:15 10678
        if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
4e9e2555 Poul Sander 2025-10-05 10:15 10679
            flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
1193b4be Poul Sander 2025-02-03 21:28 10680
4e9e2555 Poul Sander 2025-10-05 10:15 10681
        // Render tab label, process close button
4e9e2555 Poul Sander 2025-10-05 10:15 10682
        const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
4e9e2555 Poul Sander 2025-10-05 10:15 10683
        bool just_closed;
4e9e2555 Poul Sander 2025-10-05 10:15 10684
        bool text_clipped;
4e9e2555 Poul Sander 2025-10-05 10:15 10685
        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);
4e9e2555 Poul Sander 2025-10-05 10:15 10686
        if (just_closed && p_open != NULL)
4e9e2555 Poul Sander 2025-10-05 10:15 10687
        {
4e9e2555 Poul Sander 2025-10-05 10:15 10688
            *p_open = false;
4e9e2555 Poul Sander 2025-10-05 10:15 10689
            TabBarCloseTab(tab_bar, tab);
4e9e2555 Poul Sander 2025-10-05 10:15 10690
        }
4e9e2555 Poul Sander 2025-10-05 10:15 10691
4e9e2555 Poul Sander 2025-10-05 10:15 10692
        // Forward Hovered state so IsItemHovered() after Begin() can work (even though we are technically hovering our parent)
4e9e2555 Poul Sander 2025-10-05 10:15 10693
        // That state is copied to window->DockTabItemStatusFlags by our caller.
4e9e2555 Poul Sander 2025-10-05 10:15 10694
        if (docked_window && (hovered || g.HoveredId == close_button_id))
4e9e2555 Poul Sander 2025-10-05 10:15 10695
            g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
1193b4be Poul Sander 2025-02-03 21:28 10696
4e9e2555 Poul Sander 2025-10-05 10:15 10697
        // Tooltip
4e9e2555 Poul Sander 2025-10-05 10:15 10698
        // (Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer-> seems ok)
4e9e2555 Poul Sander 2025-10-05 10:15 10699
        // (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
4e9e2555 Poul Sander 2025-10-05 10:15 10700
        // FIXME: This is a mess.
4e9e2555 Poul Sander 2025-10-05 10:15 10701
        // FIXME: We may want disabled tab to still display the tooltip?
4e9e2555 Poul Sander 2025-10-05 10:15 10702
        if (text_clipped && g.HoveredId == id && !held)
4e9e2555 Poul Sander 2025-10-05 10:15 10703
            if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
4e9e2555 Poul Sander 2025-10-05 10:15 10704
                SetItemTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
4e9e2555 Poul Sander 2025-10-05 10:15 10705
    }
1193b4be Poul Sander 2025-02-03 21:28 10706
1193b4be Poul Sander 2025-02-03 21:28 10707
    // Restore main window position so user can draw there
1193b4be Poul Sander 2025-02-03 21:28 10708
    if (want_clip_rect)
1193b4be Poul Sander 2025-02-03 21:28 10709
        PopClipRect();
1193b4be Poul Sander 2025-02-03 21:28 10710
    window->DC.CursorPos = backup_main_cursor_pos;
1193b4be Poul Sander 2025-02-03 21:28 10711
1193b4be Poul Sander 2025-02-03 21:28 10712
    IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
1193b4be Poul Sander 2025-02-03 21:28 10713
    if (is_tab_button)
1193b4be Poul Sander 2025-02-03 21:28 10714
        return pressed;
1193b4be Poul Sander 2025-02-03 21:28 10715
    return tab_contents_visible;
1193b4be Poul Sander 2025-02-03 21:28 10716
}
1193b4be Poul Sander 2025-02-03 21:28 10717
1193b4be Poul Sander 2025-02-03 21:28 10718
// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed.
1193b4be Poul Sander 2025-02-03 21:28 10719
// To use it to need to call the function SetTabItemClosed() between BeginTabBar() and EndTabBar().
1193b4be Poul Sander 2025-02-03 21:28 10720
// Tabs closed by the close button will automatically be flagged to avoid this issue.
1193b4be Poul Sander 2025-02-03 21:28 10721
void    ImGui::SetTabItemClosed(const char* label)
1193b4be Poul Sander 2025-02-03 21:28 10722
{
1193b4be Poul Sander 2025-02-03 21:28 10723
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10724
    bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode);
1193b4be Poul Sander 2025-02-03 21:28 10725
    if (is_within_manual_tab_bar)
1193b4be Poul Sander 2025-02-03 21:28 10726
    {
1193b4be Poul Sander 2025-02-03 21:28 10727
        ImGuiTabBar* tab_bar = g.CurrentTabBar;
1193b4be Poul Sander 2025-02-03 21:28 10728
        ImGuiID tab_id = TabBarCalcTabID(tab_bar, label, NULL);
1193b4be Poul Sander 2025-02-03 21:28 10729
        if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id))
1193b4be Poul Sander 2025-02-03 21:28 10730
            tab->WantClose = true; // Will be processed by next call to TabBarLayout()
1193b4be Poul Sander 2025-02-03 21:28 10731
    }
1193b4be Poul Sander 2025-02-03 21:28 10732
    else if (ImGuiWindow* window = FindWindowByName(label))
1193b4be Poul Sander 2025-02-03 21:28 10733
    {
1193b4be Poul Sander 2025-02-03 21:28 10734
        if (window->DockIsActive)
1193b4be Poul Sander 2025-02-03 21:28 10735
            if (ImGuiDockNode* node = window->DockNode)
1193b4be Poul Sander 2025-02-03 21:28 10736
            {
1193b4be Poul Sander 2025-02-03 21:28 10737
                ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label, window);
1193b4be Poul Sander 2025-02-03 21:28 10738
                TabBarRemoveTab(node->TabBar, tab_id);
1193b4be Poul Sander 2025-02-03 21:28 10739
                window->DockTabWantClose = true;
1193b4be Poul Sander 2025-02-03 21:28 10740
            }
1193b4be Poul Sander 2025-02-03 21:28 10741
    }
1193b4be Poul Sander 2025-02-03 21:28 10742
}
1193b4be Poul Sander 2025-02-03 21:28 10743
1193b4be Poul Sander 2025-02-03 21:28 10744
ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
1193b4be Poul Sander 2025-02-03 21:28 10745
{
1193b4be Poul Sander 2025-02-03 21:28 10746
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10747
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 10748
    ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
1193b4be Poul Sander 2025-02-03 21:28 10749
    if (has_close_button_or_unsaved_marker)
1193b4be Poul Sander 2025-02-03 21:28 10750
        size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
1193b4be Poul Sander 2025-02-03 21:28 10751
    else
1193b4be Poul Sander 2025-02-03 21:28 10752
        size.x += g.Style.FramePadding.x + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10753
    return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
1193b4be Poul Sander 2025-02-03 21:28 10754
}
1193b4be Poul Sander 2025-02-03 21:28 10755
1193b4be Poul Sander 2025-02-03 21:28 10756
ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
1193b4be Poul Sander 2025-02-03 21:28 10757
{
1193b4be Poul Sander 2025-02-03 21:28 10758
    return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
1193b4be Poul Sander 2025-02-03 21:28 10759
}
1193b4be Poul Sander 2025-02-03 21:28 10760
1193b4be Poul Sander 2025-02-03 21:28 10761
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
1193b4be Poul Sander 2025-02-03 21:28 10762
{
1193b4be Poul Sander 2025-02-03 21:28 10763
    // 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.
1193b4be Poul Sander 2025-02-03 21:28 10764
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10765
    const float width = bb.GetWidth();
1193b4be Poul Sander 2025-02-03 21:28 10766
    IM_UNUSED(flags);
1193b4be Poul Sander 2025-02-03 21:28 10767
    IM_ASSERT(width > 0.0f);
1193b4be Poul Sander 2025-02-03 21:28 10768
    const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f));
1193b4be Poul Sander 2025-02-03 21:28 10769
    const float y1 = bb.Min.y + 1.0f;
1193b4be Poul Sander 2025-02-03 21:28 10770
    const float y2 = bb.Max.y - g.Style.TabBarBorderSize;
1193b4be Poul Sander 2025-02-03 21:28 10771
    draw_list->PathLineTo(ImVec2(bb.Min.x, y2));
1193b4be Poul Sander 2025-02-03 21:28 10772
    draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9);
1193b4be Poul Sander 2025-02-03 21:28 10773
    draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12);
1193b4be Poul Sander 2025-02-03 21:28 10774
    draw_list->PathLineTo(ImVec2(bb.Max.x, y2));
1193b4be Poul Sander 2025-02-03 21:28 10775
    draw_list->PathFillConvex(col);
1193b4be Poul Sander 2025-02-03 21:28 10776
    if (g.Style.TabBorderSize > 0.0f)
1193b4be Poul Sander 2025-02-03 21:28 10777
    {
1193b4be Poul Sander 2025-02-03 21:28 10778
        draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2));
1193b4be Poul Sander 2025-02-03 21:28 10779
        draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9);
1193b4be Poul Sander 2025-02-03 21:28 10780
        draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12);
1193b4be Poul Sander 2025-02-03 21:28 10781
        draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2));
1193b4be Poul Sander 2025-02-03 21:28 10782
        draw_list->PathStroke(GetColorU32(ImGuiCol_Border), 0, g.Style.TabBorderSize);
1193b4be Poul Sander 2025-02-03 21:28 10783
    }
1193b4be Poul Sander 2025-02-03 21:28 10784
}
1193b4be Poul Sander 2025-02-03 21:28 10785
1193b4be Poul Sander 2025-02-03 21:28 10786
// Render text label (with custom clipping) + Unsaved Document marker + Close Button logic
1193b4be Poul Sander 2025-02-03 21:28 10787
// We tend to lock style.FramePadding for a given tab-bar, hence the 'frame_padding' parameter.
1193b4be Poul Sander 2025-02-03 21:28 10788
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)
1193b4be Poul Sander 2025-02-03 21:28 10789
{
1193b4be Poul Sander 2025-02-03 21:28 10790
    ImGuiContext& g = *GImGui;
1193b4be Poul Sander 2025-02-03 21:28 10791
    ImVec2 label_size = CalcTextSize(label, NULL, true);
1193b4be Poul Sander 2025-02-03 21:28 10792
1193b4be Poul Sander 2025-02-03 21:28 10793
    if (out_just_closed)
1193b4be Poul Sander 2025-02-03 21:28 10794
        *out_just_closed = false;
1193b4be Poul Sander 2025-02-03 21:28 10795
    if (out_text_clipped)
1193b4be Poul Sander 2025-02-03 21:28 10796
        *out_text_clipped = false;
1193b4be Poul Sander 2025-02-03 21:28 10797
1193b4be Poul Sander 2025-02-03 21:28 10798
    if (bb.GetWidth() <= 1.0f)
1193b4be Poul Sander 2025-02-03 21:28 10799
        return;
1193b4be Poul Sander 2025-02-03 21:28 10800
1193b4be Poul Sander 2025-02-03 21:28 10801
    // In Style V2 we'll have full override of all colors per state (e.g. focused, selected)
1193b4be Poul Sander 2025-02-03 21:28 10802
    // But right now if you want to alter text color of tabs this is what you need to do.
1193b4be Poul Sander 2025-02-03 21:28 10803
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10804
    const float backup_alpha = g.Style.Alpha;
1193b4be Poul Sander 2025-02-03 21:28 10805
    if (!is_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10806
        g.Style.Alpha *= 0.7f;
1193b4be Poul Sander 2025-02-03 21:28 10807
#endif
1193b4be Poul Sander 2025-02-03 21:28 10808
1193b4be Poul Sander 2025-02-03 21:28 10809
    // Render text label (with clipping + alpha gradient) + unsaved marker
4e9e2555 Poul Sander 2025-10-05 10:15 10810
    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);
1193b4be Poul Sander 2025-02-03 21:28 10811
1193b4be Poul Sander 2025-02-03 21:28 10812
    // Return clipped state ignoring the close button
1193b4be Poul Sander 2025-02-03 21:28 10813
    if (out_text_clipped)
1193b4be Poul Sander 2025-02-03 21:28 10814
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10815
        *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_ellipsis_clip_bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10816
        //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));
1193b4be Poul Sander 2025-02-03 21:28 10817
    }
1193b4be Poul Sander 2025-02-03 21:28 10818
1193b4be Poul Sander 2025-02-03 21:28 10819
    const float button_sz = g.FontSize;
1193b4be Poul Sander 2025-02-03 21:28 10820
    const ImVec2 button_pos(ImMax(bb.Min.x, bb.Max.x - frame_padding.x - button_sz), bb.Min.y + frame_padding.y);
1193b4be Poul Sander 2025-02-03 21:28 10821
1193b4be Poul Sander 2025-02-03 21:28 10822
    // Close Button & Unsaved Marker
1193b4be Poul Sander 2025-02-03 21:28 10823
    // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap()
1193b4be Poul Sander 2025-02-03 21:28 10824
    //  'hovered' will be true when hovering the Tab but NOT when hovering the close button
1193b4be Poul Sander 2025-02-03 21:28 10825
    //  'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button
1193b4be Poul Sander 2025-02-03 21:28 10826
    //  'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false
1193b4be Poul Sander 2025-02-03 21:28 10827
    bool close_button_pressed = false;
1193b4be Poul Sander 2025-02-03 21:28 10828
    bool close_button_visible = false;
4e9e2555 Poul Sander 2025-10-05 10:15 10829
    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.
4e9e2555 Poul Sander 2025-10-05 10:15 10830
1193b4be Poul Sander 2025-02-03 21:28 10831
    if (close_button_id != 0)
4e9e2555 Poul Sander 2025-10-05 10:15 10832
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10833
        if (is_contents_visible)
4e9e2555 Poul Sander 2025-10-05 10:15 10834
            close_button_visible = (g.Style.TabCloseButtonMinWidthSelected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthSelected));
4e9e2555 Poul Sander 2025-10-05 10:15 10835
        else
4e9e2555 Poul Sander 2025-10-05 10:15 10836
            close_button_visible = (g.Style.TabCloseButtonMinWidthUnselected < 0.0f) ? true : (is_hovered && bb.GetWidth() >= ImMax(button_sz, g.Style.TabCloseButtonMinWidthUnselected));
4e9e2555 Poul Sander 2025-10-05 10:15 10837
    }
1193b4be Poul Sander 2025-02-03 21:28 10838
4e9e2555 Poul Sander 2025-10-05 10:15 10839
    // When tabs/document is unsaved, the unsaved marker takes priority over the close button.
4e9e2555 Poul Sander 2025-10-05 10:15 10840
    const bool unsaved_marker_visible = (flags & ImGuiTabItemFlags_UnsavedDocument) != 0 && (button_pos.x + button_sz <= bb.Max.x) && (!close_button_visible || !is_hovered);
4e9e2555 Poul Sander 2025-10-05 10:15 10841
    if (unsaved_marker_visible)
4e9e2555 Poul Sander 2025-10-05 10:15 10842
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10843
        const ImRect bullet_bb(button_pos, button_pos + ImVec2(button_sz, button_sz));
4e9e2555 Poul Sander 2025-10-05 10:15 10844
        RenderBullet(draw_list, bullet_bb.GetCenter(), GetColorU32(ImGuiCol_Text));
4e9e2555 Poul Sander 2025-10-05 10:15 10845
    }
4e9e2555 Poul Sander 2025-10-05 10:15 10846
    else if (close_button_visible)
1193b4be Poul Sander 2025-02-03 21:28 10847
    {
1193b4be Poul Sander 2025-02-03 21:28 10848
        ImGuiLastItemData last_item_backup = g.LastItemData;
1193b4be Poul Sander 2025-02-03 21:28 10849
        if (CloseButton(close_button_id, button_pos))
1193b4be Poul Sander 2025-02-03 21:28 10850
            close_button_pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 10851
        g.LastItemData = last_item_backup;
1193b4be Poul Sander 2025-02-03 21:28 10852
1193b4be Poul Sander 2025-02-03 21:28 10853
        // Close with middle mouse button
4e9e2555 Poul Sander 2025-10-05 10:15 10854
        if (is_hovered && !(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2))
1193b4be Poul Sander 2025-02-03 21:28 10855
            close_button_pressed = true;
1193b4be Poul Sander 2025-02-03 21:28 10856
    }
1193b4be Poul Sander 2025-02-03 21:28 10857
1193b4be Poul Sander 2025-02-03 21:28 10858
    // This is all rather complicated
1193b4be Poul Sander 2025-02-03 21:28 10859
    // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
1193b4be Poul Sander 2025-02-03 21:28 10860
    // 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..
4e9e2555 Poul Sander 2025-10-05 10:15 10861
    float ellipsis_max_x = text_ellipsis_clip_bb.Max.x;
1193b4be Poul Sander 2025-02-03 21:28 10862
    if (close_button_visible || unsaved_marker_visible)
1193b4be Poul Sander 2025-02-03 21:28 10863
    {
4e9e2555 Poul Sander 2025-10-05 10:15 10864
        const bool visible_without_hover = unsaved_marker_visible || (is_contents_visible ? g.Style.TabCloseButtonMinWidthSelected : g.Style.TabCloseButtonMinWidthUnselected) < 0.0f;
4e9e2555 Poul Sander 2025-10-05 10:15 10865
        if (visible_without_hover)
4e9e2555 Poul Sander 2025-10-05 10:15 10866
        {
4e9e2555 Poul Sander 2025-10-05 10:15 10867
            text_ellipsis_clip_bb.Max.x -= button_sz * 0.90f;
4e9e2555 Poul Sander 2025-10-05 10:15 10868
            ellipsis_max_x -= button_sz * 0.90f;
4e9e2555 Poul Sander 2025-10-05 10:15 10869
        }
4e9e2555 Poul Sander 2025-10-05 10:15 10870
        else
4e9e2555 Poul Sander 2025-10-05 10:15 10871
        {
4e9e2555 Poul Sander 2025-10-05 10:15 10872
            text_ellipsis_clip_bb.Max.x -= button_sz * 1.00f;
4e9e2555 Poul Sander 2025-10-05 10:15 10873
        }
1193b4be Poul Sander 2025-02-03 21:28 10874
    }
4e9e2555 Poul Sander 2025-10-05 10:15 10875
    LogSetNextTextDecoration("/", "\\");
4e9e2555 Poul Sander 2025-10-05 10:15 10876
    RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size);
1193b4be Poul Sander 2025-02-03 21:28 10877
1193b4be Poul Sander 2025-02-03 21:28 10878
#if 0
1193b4be Poul Sander 2025-02-03 21:28 10879
    if (!is_contents_visible)
1193b4be Poul Sander 2025-02-03 21:28 10880
        g.Style.Alpha = backup_alpha;
1193b4be Poul Sander 2025-02-03 21:28 10881
#endif
1193b4be Poul Sander 2025-02-03 21:28 10882
1193b4be Poul Sander 2025-02-03 21:28 10883
    if (out_just_closed)
1193b4be Poul Sander 2025-02-03 21:28 10884
        *out_just_closed = close_button_pressed;
1193b4be Poul Sander 2025-02-03 21:28 10885
}
1193b4be Poul Sander 2025-02-03 21:28 10886
1193b4be Poul Sander 2025-02-03 21:28 10887
1193b4be Poul Sander 2025-02-03 21:28 10888
#endif // #ifndef IMGUI_DISABLE
1970-01-01 00:00 10889